mirror of
https://github.com/flutter/flutter
synced 2024-10-14 04:02:56 +00:00
Add ignoringFeedbackSemantics to Draggable/LongPressDraggable (#18643)
This commit is contained in:
parent
d65acfc867
commit
35291ae3b9
|
@ -101,8 +101,10 @@ class Draggable<T> extends StatefulWidget {
|
|||
this.onDragStarted,
|
||||
this.onDraggableCanceled,
|
||||
this.onDragCompleted,
|
||||
this.ignoringFeedbackSemantics = true,
|
||||
}) : assert(child != null),
|
||||
assert(feedback != null),
|
||||
assert(ignoringFeedbackSemantics != null),
|
||||
assert(maxSimultaneousDrags == null || maxSimultaneousDrags >= 0),
|
||||
super(key: key);
|
||||
|
||||
|
@ -164,6 +166,17 @@ class Draggable<T> extends StatefulWidget {
|
|||
/// Where this widget should be anchored during a drag.
|
||||
final DragAnchor dragAnchor;
|
||||
|
||||
/// Whether the semantics of the [feedback] widget is ignored when building
|
||||
/// the semantics tree.
|
||||
///
|
||||
/// This value should be set to false when the [feedback] widget is intended
|
||||
/// to be the same object as the [child]. Placing a [GlobalKey] on this
|
||||
/// widget will ensure semantic focus is kept on the element as it moves in
|
||||
/// and out of the feedback position.
|
||||
///
|
||||
/// Defaults to true.
|
||||
final bool ignoringFeedbackSemantics;
|
||||
|
||||
/// Controls how this widget competes with other gestures to initiate a drag.
|
||||
///
|
||||
/// If affinity is null, this widget initiates a drag as soon as it recognizes
|
||||
|
@ -253,7 +266,8 @@ class LongPressDraggable<T> extends Draggable<T> {
|
|||
int maxSimultaneousDrags,
|
||||
VoidCallback onDragStarted,
|
||||
DraggableCanceledCallback onDraggableCanceled,
|
||||
VoidCallback onDragCompleted
|
||||
VoidCallback onDragCompleted,
|
||||
bool ignoringFeedbackSemantics = true,
|
||||
}) : super(
|
||||
key: key,
|
||||
child: child,
|
||||
|
@ -266,7 +280,8 @@ class LongPressDraggable<T> extends Draggable<T> {
|
|||
maxSimultaneousDrags: maxSimultaneousDrags,
|
||||
onDragStarted: onDragStarted,
|
||||
onDraggableCanceled: onDraggableCanceled,
|
||||
onDragCompleted: onDragCompleted
|
||||
onDragCompleted: onDragCompleted,
|
||||
ignoringFeedbackSemantics: ignoringFeedbackSemantics,
|
||||
);
|
||||
|
||||
@override
|
||||
|
@ -343,6 +358,7 @@ class _DraggableState<T> extends State<Draggable<T>> {
|
|||
dragStartPoint: dragStartPoint,
|
||||
feedback: widget.feedback,
|
||||
feedbackOffset: widget.feedbackOffset,
|
||||
ignoringFeedbackSemantics: widget.ignoringFeedbackSemantics,
|
||||
onDragEnd: (Velocity velocity, Offset offset, bool wasAccepted) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
|
@ -496,8 +512,10 @@ class _DragAvatar<T> extends Drag {
|
|||
this.dragStartPoint = Offset.zero,
|
||||
this.feedback,
|
||||
this.feedbackOffset = Offset.zero,
|
||||
this.onDragEnd
|
||||
this.onDragEnd,
|
||||
@required this.ignoringFeedbackSemantics,
|
||||
}) : assert(overlayState != null),
|
||||
assert(ignoringFeedbackSemantics != null),
|
||||
assert(dragStartPoint != null),
|
||||
assert(feedbackOffset != null) {
|
||||
_entry = new OverlayEntry(builder: _build);
|
||||
|
@ -513,6 +531,7 @@ class _DragAvatar<T> extends Drag {
|
|||
final Offset feedbackOffset;
|
||||
final _OnDragEnd onDragEnd;
|
||||
final OverlayState overlayState;
|
||||
final bool ignoringFeedbackSemantics;
|
||||
|
||||
_DragTargetState<T> _activeTarget;
|
||||
final List<_DragTargetState<T>> _enteredTargets = <_DragTargetState<T>>[];
|
||||
|
@ -619,7 +638,8 @@ class _DragAvatar<T> extends Drag {
|
|||
left: _lastOffset.dx - overlayTopLeft.dx,
|
||||
top: _lastOffset.dy - overlayTopLeft.dy,
|
||||
child: new IgnorePointer(
|
||||
child: feedback
|
||||
child: feedback,
|
||||
ignoringSemantics: ignoringFeedbackSemantics,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,10 +2,13 @@
|
|||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/semantics.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
|
||||
import 'semantics_tester.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Drag and drop - control test', (WidgetTester tester) async {
|
||||
final List<int> accepted = <int>[];
|
||||
|
@ -1639,6 +1642,155 @@ void main() {
|
|||
testWidgets('Drag feedback with child anchor within a non-global Overlay positions correctly', (WidgetTester tester) async {
|
||||
await _testChildAnchorFeedbackPosition(tester: tester, left: 100.0, top: 100.0);
|
||||
});
|
||||
|
||||
|
||||
testWidgets('Drag and drop can contribute semantics', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
await tester.pumpWidget(new MaterialApp(
|
||||
home: new ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
children: <Widget>[
|
||||
new DragTarget<int>(
|
||||
builder: (BuildContext context, List<int> data, List<dynamic> rejects) {
|
||||
return const Text('Target');
|
||||
},
|
||||
),
|
||||
new Container(width: 400.0),
|
||||
const Draggable<int>(
|
||||
data: 1,
|
||||
child: const Text('H'),
|
||||
feedback: const Text('H'),
|
||||
childWhenDragging: const SizedBox(),
|
||||
axis: Axis.horizontal,
|
||||
ignoringFeedbackSemantics: false,
|
||||
),
|
||||
const Draggable<int>(
|
||||
data: 2,
|
||||
child: const Text('V'),
|
||||
feedback: const Text('V'),
|
||||
childWhenDragging: const SizedBox(),
|
||||
axis: Axis.vertical,
|
||||
ignoringFeedbackSemantics: false,
|
||||
),
|
||||
const Draggable<int>(
|
||||
data: 3,
|
||||
child: const Text('N'),
|
||||
feedback: const Text('N'),
|
||||
childWhenDragging: const SizedBox(),
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 1,
|
||||
textDirection: TextDirection.ltr,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 2,
|
||||
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 3,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 8,
|
||||
actions: <SemanticsAction>[SemanticsAction.scrollLeft],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 4,
|
||||
tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
|
||||
label: 'Target',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 5,
|
||||
tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
|
||||
label: 'H',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 6,
|
||||
tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
|
||||
label: 'V',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 7,
|
||||
tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
|
||||
label: 'N',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
), ignoreTransform: true, ignoreRect: true));
|
||||
|
||||
final Offset firstLocation = tester.getTopLeft(find.text('N'));
|
||||
final Offset secondLocation = firstLocation + const Offset(300.0, 300.0);
|
||||
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
|
||||
await tester.pump();
|
||||
await gesture.moveTo(secondLocation);
|
||||
await tester.pump();
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 1,
|
||||
textDirection: TextDirection.ltr,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 2,
|
||||
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 3,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 8,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 4,
|
||||
tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
|
||||
label: 'Target',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 5,
|
||||
tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
|
||||
label: 'H',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 6,
|
||||
tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
|
||||
label: 'V',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
/// N is moved offscreen.
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
), ignoreTransform: true, ignoreRect: true));
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
Future<Null> _testChildAnchorFeedbackPosition({WidgetTester tester, double top = 0.0, double left = 0.0}) async {
|
||||
|
|
Loading…
Reference in a new issue