Add mouseCursor parameter to ReorderableListView (#160246)
Part of https://github.com/flutter/flutter/issues/58192#issue-626789189 <table> <tr> <th>Defautl</th> <th>Customized</th> </tr> <tr> <td> https://github.com/user-attachments/assets/91819d7e-472b-481d-84ff-bec0d812ab53 </td> <td> https://github.com/user-attachments/assets/61a6841f-1845-4444-a03a-8870f0b67969 </td> </tr> </table> ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [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 [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
@@ -103,6 +103,7 @@ class ReorderableListView extends StatefulWidget {
|
||||
this.clipBehavior = Clip.hardEdge,
|
||||
this.autoScrollerVelocityScalar,
|
||||
this.dragBoundaryProvider,
|
||||
this.mouseCursor,
|
||||
}) : assert(
|
||||
(itemExtent == null && prototypeItem == null) ||
|
||||
(itemExtent == null && itemExtentBuilder == null) ||
|
||||
@@ -173,6 +174,7 @@ class ReorderableListView extends StatefulWidget {
|
||||
this.clipBehavior = Clip.hardEdge,
|
||||
this.autoScrollerVelocityScalar,
|
||||
this.dragBoundaryProvider,
|
||||
this.mouseCursor,
|
||||
}) : assert(itemCount >= 0),
|
||||
assert(
|
||||
(itemExtent == null && prototypeItem == null) ||
|
||||
@@ -297,11 +299,25 @@ class ReorderableListView extends StatefulWidget {
|
||||
/// {@macro flutter.widgets.reorderable_list.dragBoundaryProvider}
|
||||
final ReorderDragBoundaryProvider? dragBoundaryProvider;
|
||||
|
||||
/// The cursor for a mouse pointer when it enters or is hovering over the drag
|
||||
/// handle.
|
||||
///
|
||||
/// If [mouseCursor] is a [WidgetStateMouseCursor],
|
||||
/// [WidgetStateProperty.resolve] is used for the following [WidgetState]s:
|
||||
///
|
||||
/// * [WidgetState.dragged].
|
||||
///
|
||||
/// If this property is null, [SystemMouseCursors.grab] will be used when
|
||||
/// hovering, and [SystemMouseCursors.grabbing] when dragging.
|
||||
final MouseCursor? mouseCursor;
|
||||
|
||||
@override
|
||||
State<ReorderableListView> createState() => _ReorderableListViewState();
|
||||
}
|
||||
|
||||
class _ReorderableListViewState extends State<ReorderableListView> {
|
||||
final ValueNotifier<bool> _dragging = ValueNotifier<bool>(false);
|
||||
|
||||
Widget _itemBuilder(BuildContext context, int index) {
|
||||
final Widget item = widget.itemBuilder(context, index);
|
||||
assert(() {
|
||||
@@ -318,6 +334,21 @@ class _ReorderableListViewState extends State<ReorderableListView> {
|
||||
case TargetPlatform.linux:
|
||||
case TargetPlatform.windows:
|
||||
case TargetPlatform.macOS:
|
||||
final ListenableBuilder dragHandle = ListenableBuilder(
|
||||
listenable: _dragging,
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
final MouseCursor effectiveMouseCursor = WidgetStateProperty.resolveAs<MouseCursor>(
|
||||
widget.mouseCursor ??
|
||||
const WidgetStateMouseCursor.fromMap(<WidgetStatesConstraint, MouseCursor>{
|
||||
WidgetState.dragged: SystemMouseCursors.grabbing,
|
||||
WidgetState.any: SystemMouseCursors.grab,
|
||||
}),
|
||||
<WidgetState>{if (_dragging.value) WidgetState.dragged},
|
||||
);
|
||||
return MouseRegion(cursor: effectiveMouseCursor, child: child);
|
||||
},
|
||||
child: const Icon(Icons.drag_handle),
|
||||
);
|
||||
switch (widget.scrollDirection) {
|
||||
case Axis.horizontal:
|
||||
return Stack(
|
||||
@@ -331,10 +362,7 @@ class _ReorderableListViewState extends State<ReorderableListView> {
|
||||
bottom: 8,
|
||||
child: Align(
|
||||
alignment: AlignmentDirectional.bottomCenter,
|
||||
child: ReorderableDragStartListener(
|
||||
index: index,
|
||||
child: const Icon(Icons.drag_handle),
|
||||
),
|
||||
child: ReorderableDragStartListener(index: index, child: dragHandle),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -351,10 +379,7 @@ class _ReorderableListViewState extends State<ReorderableListView> {
|
||||
end: 8,
|
||||
child: Align(
|
||||
alignment: AlignmentDirectional.centerEnd,
|
||||
child: ReorderableDragStartListener(
|
||||
index: index,
|
||||
child: const Icon(Icons.drag_handle),
|
||||
),
|
||||
child: ReorderableDragStartListener(index: index, child: dragHandle),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -383,6 +408,12 @@ class _ReorderableListViewState extends State<ReorderableListView> {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_dragging.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(debugCheckHasMaterialLocalizations(context));
|
||||
@@ -440,8 +471,14 @@ class _ReorderableListViewState extends State<ReorderableListView> {
|
||||
prototypeItem: widget.prototypeItem,
|
||||
itemCount: widget.itemCount,
|
||||
onReorder: widget.onReorder,
|
||||
onReorderStart: widget.onReorderStart,
|
||||
onReorderEnd: widget.onReorderEnd,
|
||||
onReorderStart: (int index) {
|
||||
_dragging.value = true;
|
||||
widget.onReorderStart?.call(index);
|
||||
},
|
||||
onReorderEnd: (int index) {
|
||||
_dragging.value = false;
|
||||
widget.onReorderEnd?.call(index);
|
||||
},
|
||||
proxyDecorator: widget.proxyDecorator ?? _proxyDecorator,
|
||||
autoScrollerVelocityScalar: widget.autoScrollerVelocityScalar,
|
||||
dragBoundaryProvider: widget.dragBoundaryProvider,
|
||||
|
||||
@@ -2318,6 +2318,101 @@ void main() {
|
||||
await drag.up();
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
testWidgets('Mouse cursor behavior on the drag handle', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: ReorderableListView.builder(
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return ReorderableDragStartListener(
|
||||
key: ValueKey<int>(index),
|
||||
index: index,
|
||||
child: Text('$index'),
|
||||
);
|
||||
},
|
||||
itemCount: 5,
|
||||
onReorder: (int fromIndex, int toIndex) {},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final TestGesture gesture = await tester.createGesture(
|
||||
kind: PointerDeviceKind.mouse,
|
||||
pointer: 1,
|
||||
);
|
||||
await gesture.addPointer(location: tester.getCenter(find.byIcon(Icons.drag_handle).first));
|
||||
await tester.pump();
|
||||
expect(
|
||||
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
||||
SystemMouseCursors.grab,
|
||||
);
|
||||
await gesture.down(tester.getCenter(find.byIcon(Icons.drag_handle).first));
|
||||
await tester.pump(kLongPressTimeout);
|
||||
expect(
|
||||
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
||||
SystemMouseCursors.grabbing,
|
||||
);
|
||||
await gesture.up();
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
||||
SystemMouseCursors.grab,
|
||||
);
|
||||
}, variant: TargetPlatformVariant.desktop());
|
||||
|
||||
testWidgets(
|
||||
'Mouse cursor behavior on the drag handle can be provided',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: ReorderableListView.builder(
|
||||
mouseCursor:
|
||||
const WidgetStateMouseCursor.fromMap(<WidgetStatesConstraint, MouseCursor>{
|
||||
WidgetState.dragged: SystemMouseCursors.copy,
|
||||
WidgetState.any: SystemMouseCursors.resizeColumn,
|
||||
}),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return ReorderableDragStartListener(
|
||||
key: ValueKey<int>(index),
|
||||
index: index,
|
||||
child: Text('$index'),
|
||||
);
|
||||
},
|
||||
itemCount: 5,
|
||||
onReorder: (int fromIndex, int toIndex) {},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final TestGesture gesture = await tester.createGesture(
|
||||
kind: PointerDeviceKind.mouse,
|
||||
pointer: 1,
|
||||
);
|
||||
await gesture.addPointer(location: tester.getCenter(find.byIcon(Icons.drag_handle).first));
|
||||
await tester.pump();
|
||||
expect(
|
||||
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
||||
SystemMouseCursors.resizeColumn,
|
||||
);
|
||||
await gesture.down(tester.getCenter(find.byIcon(Icons.drag_handle).first));
|
||||
await tester.pump(kLongPressTimeout);
|
||||
expect(
|
||||
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
||||
SystemMouseCursors.copy,
|
||||
);
|
||||
await gesture.up();
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
||||
SystemMouseCursors.resizeColumn,
|
||||
);
|
||||
},
|
||||
variant: TargetPlatformVariant.desktop(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> longPressDrag(WidgetTester tester, Offset start, Offset end) async {
|
||||
|
||||
Reference in New Issue
Block a user