Add ignoringFeedbackSemantics to Draggable/LongPressDraggable (#18643)

This commit is contained in:
Jonah Williams 2018-07-09 17:02:21 -07:00 committed by GitHub
parent d65acfc867
commit 35291ae3b9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 176 additions and 4 deletions

View file

@ -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,
)
);
}

View file

@ -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 {