Skip to content

Conversation

@lukemmtt
Copy link
Contributor

@lukemmtt lukemmtt commented Jul 25, 2025

Note: The feature introduced in this PR is admittedly quite niche, and I don't expect it to be merged; I've filed it here mostly as an example of a visually interesting & intuitive feature made possible through the Flutter framework and its extensibility.

Description

This PR introduces a novel interaction pattern to ReorderableListView: the ability to insert new items by spreading two fingers (or pinching out on a trackpad) between existing list items. This gesture, first popularized by the Clear to-do list app, creates an intuitive, discoverable way for users to add items exactly where they want them in a list.

Interaction demo

The video above demonstrates the spread-to-add gesture on an iOS simulator, showing the animated placeholder and insertion behavior.

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

Key Features

  • Two-finger spread gesture on mobile devices
  • Trackpad pinch-out gesture on desktop (macOS)
  • Visual feedback with animated placeholder during gesture
  • Configurable threshold and visual appearance
  • Seamless integration with existing reordering functionality

This PR introduces a novel feature and does not address any existing issues or requests.

Implementation Details

The implementation includes:

  1. New SpreadGestureDetector widget that handles multi-touch and trackpad gestures
  2. Platform-specific gesture recognition:
    • Mobile: Direct pointer tracking for precise two-finger detection
    • Desktop: Scale gesture transformation for trackpad pinch
  3. Visual feedback system with customizable placeholders
  4. Configurable thresholds and behavior via SpreadConfig
  5. Proper integration with scrolling and existing reorder gestures

Design Considerations

  1. Threshold Design: The gesture requires sufficient spread distance to prevent accidental triggers
  2. Visual Feedback: Clear placeholder animation indicates when insertion will occur
  3. Scroll Integration: Gesture respects scroll boundaries and doesn't interfere with scrolling
  4. Reorder Compatibility: Works seamlessly alongside drag-to-reorder functionality

Technical Architecture

  • Gesture detection layer that doesn't interfere with scrolling or reordering
  • Dynamic calculation of insertion points based on item positions
  • Animated placeholder that responds to spread distance
  • State management for gesture lifecycle (start, update, end, cancel)

Platform Support

  • iOS - Two-finger spread gesture
  • Android - Two-finger spread gesture
  • macOS - Trackpad pinch-out gesture
  • Windows - Trackpad/touchpad pinch-out gesture
  • Linux - Trackpad pinch-out gesture
  • Web - Multi-touch on touch devices, trackpad on desktop

Testing

  • Tested on iOS devices (iPhone, iPad)
  • Tested on Android devices (phones, tablets)
  • Tested on macOS with trackpad
  • Tested on Windows with precision touchpad
  • Tested on Web (Chrome, Safari, Firefox)
  • Added unit tests for gesture detection
  • Added integration tests for insertion behavior
  • Tested interaction with scrolling and reordering
  • Verified accessibility with screen readers

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

This change is fully backward compatible. The spread-to-add functionality is disabled by default (spreadEnabled: false) and requires explicit opt-in.

@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:45
@lukemmtt lukemmtt force-pushed the timefinder/spread-to-add-gesture branch 2 times, most recently from 48c3a47 to 918b28a Compare July 25, 2025 02:16
@lukemmtt lukemmtt force-pushed the timefinder/spread-to-add-gesture branch from 918b28a to 5598ff8 Compare July 28, 2025 21:21
Adds SpreadGestureDetector widget that detects two-finger spread (mobile) or trackpad pinch (macOS) gestures to insert new items between list items.

Key features:
- Platform-specific gesture detection (Pointer API for mobile, Scale for Mac)
- Insertion point calculation based on item midpoints
- Spread threshold detection with visual feedback callbacks
- Auto-scrolling support during spread gesture
- Configurable thresholds and maximum gap height
@lukemmtt lukemmtt force-pushed the timefinder/spread-to-add-gesture branch from 5598ff8 to 0848d3e Compare July 29, 2025 00:24
@lukemmtt lukemmtt changed the title Add spread-to-add gesture for inserting items in ReorderableListView Add spread-to-add gesture for inserting items in ReorderableListView Aug 4, 2025
@lukemmtt lukemmtt closed this Aug 4, 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.

1 participant