Skip to content

Conversation

@lukemmtt
Copy link
Contributor

@lukemmtt lukemmtt commented Jul 25, 2025

Description

This PR introduces ReorderableListController with an animateItemToIndex() method, enabling smooth animated transitions when programmatically moving list items. The implementation leverages the existing drag animation infrastructure to ensure visual consistency with manual reordering.

demo2

Demonstration of programmatic item movement with smooth animations matching manual drag behavior.

Motivation

Currently, programmatically reordering items in ReorderableListView requires direct state updates, resulting in jarring instant transitions. Users lose context when items suddenly jump to new positions without visual feedback.

This API addresses a fundamental gap in Flutter's reordering capabilities, enabling:

  • Smooth transitions that help users track changes
  • Consistent visual behavior between manual and programmatic reordering
  • Common UI patterns like "move to top/bottom" actions and keyboard navigation

This PR is part of a series of ReorderableList enhancements I've developed for TimeFinder:

Community Demand

The Flutter community has created numerous packages to address this gap:

Native platforms provide similar APIs:

This PR resolves #72673.

Usage Example

final controller = ReorderableListController();

ReorderableListView(
  controller: controller,
  onReorder: (oldIndex, newIndex) {
    // Handle final reorder
  },
  children: [...],
)

// Animate item from index 2 to index 0
await controller.animateItemToIndex(
  fromIndex: 2,
  toIndex: 0,
  duration: const Duration(milliseconds: 500),
  curve: Curves.easeInOut,
);

Implementation Details

Core Components

  1. ReorderableListController - Manages the connection to ReorderableListView
  2. animateItemToIndex() - Public API for triggering animated moves
  3. Reuses _DragInfo - Leverages existing drag animation system for consistency

Key Design Decisions

  • Single item focus: This PR specifically addresses moving one item at a time. Batch operations or declarative reordering would require different approaches and are out of scope.
  • Callback integration: Properly triggers onReorderStart, onReorderEnd, and onReorder callbacks, maintaining consistency with manual drag operations.
  • Animation control: Provides duration and curve parameters for customizable motion.

Index Handling

The API uses final position indices (where the item ends up after the move) rather than Flutter's internal insertion point convention (see #24786). This provides an intuitive developer experience where toIndex represents the item's final position in the list.

Testing

  • Tested on iOS simulator
  • Tested on macOS desktop
  • Tested on Android emulator
  • Tested on Web
  • Tested edge cases (first/last items, rapid animations, interruption handling)
  • Added unit tests for controller behavior
  • Added widget tests for animation and callback verification
  • Verified compatibility with existing ReorderableListView usage

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

Breaking Change

Does your PR require Flutter developers to manually update their apps to accommodate your change?

  • Yes, this is a breaking change
  • No, this is not a breaking change

@flutter-dashboard
Copy link

It looks like this pull request may not have tests. Please make sure to add tests or get an explicit test exemption before merging.

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.If you believe this PR qualifies for a test exemption, contact "@test-exemption-reviewer" in the #hackers channel in Discord (don't just cc them here, they won't see it!). The test exemption team is a small volunteer group, so all reviewers should feel empowered to ask for tests, without delegating that responsibility entirely to the test exemption group.

@github-actions github-actions bot added framework flutter/packages/flutter repository. See also f: labels. f: material design flutter/packages/flutter/material repository. labels Jul 25, 2025
@lukemmtt lukemmtt marked this pull request as draft July 25, 2025 01:44
@lukemmtt lukemmtt force-pushed the timefinder/programmatic-reordering-api branch 3 times, most recently from 3435b65 to f2a0461 Compare July 26, 2025 12:03
@lukemmtt lukemmtt changed the title Add programmatic reordering API to ReorderableListView Add animated item movement API to ReorderableListView Jul 26, 2025
@lukemmtt lukemmtt force-pushed the timefinder/programmatic-reordering-api branch 2 times, most recently from 1dcd213 to f3557aa Compare July 29, 2025 00:23
@lukemmtt lukemmtt changed the title Add animated item movement API to ReorderableListView Add animated item movement API to ReorderableListView Aug 4, 2025
@lukemmtt lukemmtt force-pushed the timefinder/programmatic-reordering-api branch 3 times, most recently from 468c379 to 1bb1a38 Compare August 7, 2025 05:00
@fluttergithubbot
Copy link
Contributor

An existing Git SHA, 1bb1a382db5b985c048cd61bac74f207871203d4, was detected, and no actions were taken.

To re-trigger presubmits after closing or re-opeing a PR, or pushing a HEAD commit (i.e. with --force) that already was pushed before, push a blank commit (git commit --allow-empty -m "Trigger Build") or rebase to continue.

@lukemmtt lukemmtt force-pushed the timefinder/programmatic-reordering-api branch 3 times, most recently from 31e1ca3 to 19244db Compare August 7, 2025 15:35
Introduces ReorderableListController with animateToPosition method to programmatically reorder list items. The API mirrors the visual behavior of user drag operations, animating the selected item to its new position while other items shift to accommodate.
Fixes issue where rapid succession calls to animateItemToIndex would
conflict and cause undefined state. Now animations execute sequentially
with proper visual separation.

Key improvements:
- Queue-based sequential execution prevents animation conflicts
- 16ms frame delay between animations prevents visual morphing glitch
- Manual drag operations clear pending animations (user intent priority)
- Individual completers track each animation's completion
- Comprehensive test coverage for rapid succession scenarios

This enhancement ensures reliable behavior when multiple items are
programmatically reordered in quick succession, such as marking
multiple tasks as complete in a focus mode interface.
@lukemmtt lukemmtt force-pushed the timefinder/programmatic-reordering-api branch from 19244db to 2223c8e Compare August 8, 2025 16:26
@Piinks
Copy link
Contributor

Piinks commented Oct 20, 2025

Hey @lukemmtt is this change still on your radar?

@lukemmtt
Copy link
Contributor Author

For now I'm going to close these drafts (this and PR #172738); I think these are valuable enhancements—to the extent that I've vendored the framework files and implemented them in my own app—but I think they're too niche to justify the effort needed to make their PRs merge-worthy. Happy to revisit someday if it makes sense though ☀️

@lukemmtt lukemmtt closed this Oct 20, 2025
github-merge-queue bot pushed a commit that referenced this pull request Nov 5, 2025
…animation (#173241)

When rapidly dragging items in a ReorderableList before animations
complete, items would jump to their expected positions rather than
smoothly transitioning.

## Problem

<p align="center">
<img
src="https://github.com/user-attachments/assets/0efb250c-2960-4942-959f-59eccc20cefb"
alt="demo2" width="250">
</p>

The issue occurs when a reorder animation is interrupted by starting a
new drag operation. The interrupted animation would reset to the
starting position of the original animation rather than capturing the
current animated position, causing a visual jump.

## Solution

This PR fixes the issue by:
1. Storing the previous target offset before updating to a new target
2. When an animation is interrupted, calculating the actual current
position based on the animation's progress
3. Using this calculated position as the new starting point for the next
animation

<p align="center">
<img
src="https://github.com/user-attachments/assets/c24e4834-1c6b-41c1-8f44-17b4c79e0993"
alt="demo2" width="250">
</p>

This PR is part of a series of `ReorderableList` enhancements I've
developed for [TimeFinder](https://timefinder.app):
- #172740
- #172380
- #172739
- #172738

Fixes #173243

## Code Changes

```dart
// Before
_startOffset = offset;

// After  
final double currentAnimValue = Curves.easeInOut.transform(_offsetAnimation\!.value);
final Offset currentPosition = Offset.lerp(_startOffset, previousTarget, currentAnimValue)\!;
_startOffset = currentPosition;
```

This ensures smooth transitions without visual jumping, making rapid
reordering gestures feel more responsive and natural.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [migration
guide] as needed.
- [x] All existing and new tests are passing.
- [x] The analyzer (`flutter analyze --flutter-repo`) does not report
any problems on my PR.
- [x] I am willing to follow-up on review comments in a timely manner.

[Contributor Guide]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[test-exempt]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[migration guide]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#create-a-migration-guide
IvoneDjaja pushed a commit to IvoneDjaja/flutter that referenced this pull request Nov 22, 2025
…animation (flutter#173241)

When rapidly dragging items in a ReorderableList before animations
complete, items would jump to their expected positions rather than
smoothly transitioning.

## Problem

<p align="center">
<img
src="https://github.com/user-attachments/assets/0efb250c-2960-4942-959f-59eccc20cefb"
alt="demo2" width="250">
</p>

The issue occurs when a reorder animation is interrupted by starting a
new drag operation. The interrupted animation would reset to the
starting position of the original animation rather than capturing the
current animated position, causing a visual jump.

## Solution

This PR fixes the issue by:
1. Storing the previous target offset before updating to a new target
2. When an animation is interrupted, calculating the actual current
position based on the animation's progress
3. Using this calculated position as the new starting point for the next
animation

<p align="center">
<img
src="https://github.com/user-attachments/assets/c24e4834-1c6b-41c1-8f44-17b4c79e0993"
alt="demo2" width="250">
</p>

This PR is part of a series of `ReorderableList` enhancements I've
developed for [TimeFinder](https://timefinder.app):
- flutter#172740
- flutter#172380
- flutter#172739
- flutter#172738

Fixes flutter#173243

## Code Changes

```dart
// Before
_startOffset = offset;

// After  
final double currentAnimValue = Curves.easeInOut.transform(_offsetAnimation\!.value);
final Offset currentPosition = Offset.lerp(_startOffset, previousTarget, currentAnimValue)\!;
_startOffset = currentPosition;
```

This ensures smooth transitions without visual jumping, making rapid
reordering gestures feel more responsive and natural.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [migration
guide] as needed.
- [x] All existing and new tests are passing.
- [x] The analyzer (`flutter analyze --flutter-repo`) does not report
any problems on my PR.
- [x] I am willing to follow-up on review comments in a timely manner.

[Contributor Guide]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[test-exempt]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[migration guide]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#create-a-migration-guide
reidbaker pushed a commit to AbdeMohlbi/flutter that referenced this pull request Dec 10, 2025
…animation (flutter#173241)

When rapidly dragging items in a ReorderableList before animations
complete, items would jump to their expected positions rather than
smoothly transitioning.

## Problem

<p align="center">
<img
src="https://github.com/user-attachments/assets/0efb250c-2960-4942-959f-59eccc20cefb"
alt="demo2" width="250">
</p>

The issue occurs when a reorder animation is interrupted by starting a
new drag operation. The interrupted animation would reset to the
starting position of the original animation rather than capturing the
current animated position, causing a visual jump.

## Solution

This PR fixes the issue by:
1. Storing the previous target offset before updating to a new target
2. When an animation is interrupted, calculating the actual current
position based on the animation's progress
3. Using this calculated position as the new starting point for the next
animation

<p align="center">
<img
src="https://github.com/user-attachments/assets/c24e4834-1c6b-41c1-8f44-17b4c79e0993"
alt="demo2" width="250">
</p>

This PR is part of a series of `ReorderableList` enhancements I've
developed for [TimeFinder](https://timefinder.app):
- flutter#172740
- flutter#172380
- flutter#172739
- flutter#172738

Fixes flutter#173243

## Code Changes

```dart
// Before
_startOffset = offset;

// After  
final double currentAnimValue = Curves.easeInOut.transform(_offsetAnimation\!.value);
final Offset currentPosition = Offset.lerp(_startOffset, previousTarget, currentAnimValue)\!;
_startOffset = currentPosition;
```

This ensures smooth transitions without visual jumping, making rapid
reordering gestures feel more responsive and natural.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [migration
guide] as needed.
- [x] All existing and new tests are passing.
- [x] The analyzer (`flutter analyze --flutter-repo`) does not report
any problems on my PR.
- [x] I am willing to follow-up on review comments in a timely manner.

[Contributor Guide]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[test-exempt]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[migration guide]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#create-a-migration-guide
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reorder widgets in ReorderableListView with a Controller

3 participants