mirror of
https://github.com/flutter/flutter
synced 2024-10-13 03:32:55 +00:00
Move declaration of semantic handlers from detectors to recognizers (#33475)
- A refactor that moves the semantics declaration from detectors to recognizers to allow custom recognizers to respond to semantic gectures. - Renames all handlers related to semantics from Gesture* to Semantics*.
This commit is contained in:
parent
90500a5dc4
commit
572f349f5c
|
@ -28,6 +28,7 @@ export 'src/gestures/pointer_router.dart';
|
|||
export 'src/gestures/pointer_signal_resolver.dart';
|
||||
export 'src/gestures/recognizer.dart';
|
||||
export 'src/gestures/scale.dart';
|
||||
export 'src/gestures/semantics.dart';
|
||||
export 'src/gestures/tap.dart';
|
||||
export 'src/gestures/team.dart';
|
||||
export 'src/gestures/velocity_tracker.dart';
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'arena.dart';
|
|||
import 'constants.dart';
|
||||
import 'events.dart';
|
||||
import 'recognizer.dart';
|
||||
import 'semantics.dart';
|
||||
|
||||
/// Callback signature for [LongPressGestureRecognizer.onLongPress].
|
||||
///
|
||||
|
@ -214,6 +215,23 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
|
|||
/// callback.
|
||||
GestureLongPressEndCallback onLongPressEnd;
|
||||
|
||||
@override
|
||||
SemanticsGestureConfiguration get semanticsConfiguration {
|
||||
return _semanticsConfiguration ??= SemanticsGestureConfiguration(
|
||||
onLongPress: () {
|
||||
if (onLongPressStart != null)
|
||||
onLongPressStart(const LongPressStartDetails());
|
||||
if (onLongPress != null)
|
||||
onLongPress();
|
||||
if (onLongPressEnd != null)
|
||||
onLongPressEnd(const LongPressEndDetails());
|
||||
if (onLongPressUp != null)
|
||||
onLongPressUp();
|
||||
},
|
||||
);
|
||||
}
|
||||
SemanticsGestureConfiguration _semanticsConfiguration;
|
||||
|
||||
@override
|
||||
bool isPointerAllowed(PointerDownEvent event) {
|
||||
switch (event.buttons) {
|
||||
|
|
|
@ -10,6 +10,7 @@ import 'constants.dart';
|
|||
import 'drag_details.dart';
|
||||
import 'events.dart';
|
||||
import 'recognizer.dart';
|
||||
import 'semantics.dart';
|
||||
import 'velocity_tracker.dart';
|
||||
|
||||
enum _DragState {
|
||||
|
@ -456,6 +457,23 @@ class VerticalDragGestureRecognizer extends DragGestureRecognizer {
|
|||
PointerDeviceKind kind,
|
||||
}) : super(debugOwner: debugOwner, kind: kind);
|
||||
|
||||
@override
|
||||
SemanticsGestureConfiguration get semanticsConfiguration {
|
||||
return _semanticsConfiguration ??= SemanticsGestureConfiguration(
|
||||
onVerticalDragUpdate: (DragUpdateDetails updateDetails) {
|
||||
if (onDown != null)
|
||||
onDown(DragDownDetails());
|
||||
if (onStart != null)
|
||||
onStart(DragStartDetails());
|
||||
if (onUpdate != null)
|
||||
onUpdate(updateDetails);
|
||||
if (onEnd != null)
|
||||
onEnd(DragEndDetails(primaryVelocity: 0.0));
|
||||
},
|
||||
);
|
||||
}
|
||||
SemanticsGestureConfiguration _semanticsConfiguration;
|
||||
|
||||
@override
|
||||
bool _isFlingGesture(VelocityEstimate estimate) {
|
||||
final double minVelocity = minFlingVelocity ?? kMinFlingVelocity;
|
||||
|
@ -495,6 +513,23 @@ class HorizontalDragGestureRecognizer extends DragGestureRecognizer {
|
|||
PointerDeviceKind kind,
|
||||
}) : super(debugOwner: debugOwner, kind: kind);
|
||||
|
||||
@override
|
||||
SemanticsGestureConfiguration get semanticsConfiguration {
|
||||
return _semanticsConfiguration ??= SemanticsGestureConfiguration(
|
||||
onHorizontalDragUpdate: (DragUpdateDetails updateDetails) {
|
||||
if (onDown != null)
|
||||
onDown(DragDownDetails());
|
||||
if (onStart != null)
|
||||
onStart(DragStartDetails());
|
||||
if (onUpdate != null)
|
||||
onUpdate(updateDetails);
|
||||
if (onEnd != null)
|
||||
onEnd(DragEndDetails(primaryVelocity: 0.0));
|
||||
},
|
||||
);
|
||||
}
|
||||
SemanticsGestureConfiguration _semanticsConfiguration;
|
||||
|
||||
@override
|
||||
bool _isFlingGesture(VelocityEstimate estimate) {
|
||||
final double minVelocity = minFlingVelocity ?? kMinFlingVelocity;
|
||||
|
@ -528,6 +563,33 @@ class PanGestureRecognizer extends DragGestureRecognizer {
|
|||
/// Create a gesture recognizer for tracking movement on a plane.
|
||||
PanGestureRecognizer({ Object debugOwner }) : super(debugOwner: debugOwner);
|
||||
|
||||
@override
|
||||
SemanticsGestureConfiguration get semanticsConfiguration {
|
||||
return _semanticsConfiguration ??= SemanticsGestureConfiguration(
|
||||
onHorizontalDragUpdate: (DragUpdateDetails updateDetails) {
|
||||
if (onDown != null)
|
||||
onDown(DragDownDetails());
|
||||
if (onStart != null)
|
||||
onStart(DragStartDetails());
|
||||
if (onUpdate != null)
|
||||
onUpdate(updateDetails);
|
||||
if (onEnd != null)
|
||||
onEnd(DragEndDetails());
|
||||
},
|
||||
onVerticalDragUpdate: (DragUpdateDetails updateDetails) {
|
||||
if (onDown != null)
|
||||
onDown(DragDownDetails());
|
||||
if (onStart != null)
|
||||
onStart(DragStartDetails());
|
||||
if (onUpdate != null)
|
||||
onUpdate(updateDetails);
|
||||
if (onEnd != null)
|
||||
onEnd(DragEndDetails());
|
||||
},
|
||||
);
|
||||
}
|
||||
SemanticsGestureConfiguration _semanticsConfiguration;
|
||||
|
||||
@override
|
||||
bool _isFlingGesture(VelocityEstimate estimate) {
|
||||
final double minVelocity = minFlingVelocity ?? kMinFlingVelocity;
|
||||
|
|
|
@ -15,6 +15,7 @@ import 'constants.dart';
|
|||
import 'debug.dart';
|
||||
import 'events.dart';
|
||||
import 'pointer_router.dart';
|
||||
import 'semantics.dart';
|
||||
import 'team.dart';
|
||||
|
||||
export 'pointer_router.dart' show PointerRouter;
|
||||
|
@ -142,6 +143,21 @@ abstract class GestureRecognizer extends GestureArenaMember with DiagnosticableT
|
|||
return _pointerToKind[pointer];
|
||||
}
|
||||
|
||||
/// Returns the semantics configuration of this gesture recognizer, for example
|
||||
/// for accessibility purposes. It is queried by the recognizer's
|
||||
/// [RawGestureDetector] to build a collective semantics annotation.
|
||||
///
|
||||
/// This method should be overridden by subclasses that are interested in
|
||||
/// semantic gestures.
|
||||
///
|
||||
/// The returned [SemanticsGestureConfiguration] object should be annotated in
|
||||
/// a manner that describes the current state, and should remain consistant
|
||||
/// behaviors across different calls. It is recommended to cache the returned
|
||||
/// object.
|
||||
SemanticsGestureConfiguration get semanticsConfiguration {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Releases any resources used by the object.
|
||||
///
|
||||
/// This method is called by the owner of this gesture recognizer
|
||||
|
|
69
packages/flutter/lib/src/gestures/semantics.dart
Normal file
69
packages/flutter/lib/src/gestures/semantics.dart
Normal file
|
@ -0,0 +1,69 @@
|
|||
// Copyright 2019 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'drag_details.dart';
|
||||
|
||||
/// Called when the user taps with a semantics device.
|
||||
typedef SemanticsTapCallback = void Function();
|
||||
/// Called when the user presses for a long period of time with a semantics
|
||||
/// device.
|
||||
typedef SemanticsLongPressCallback = void Function();
|
||||
/// Called when the user drags with a semantics device.
|
||||
typedef SemanticsDragUpdateCallback = void Function(DragUpdateDetails details);
|
||||
|
||||
/// Describes the semantics configuration of a gesture recognizer, for example
|
||||
/// for accessibility purposes. It is queried by the recognizer's
|
||||
/// [RawGestureDetector] to build a collective semantics annotation.
|
||||
///
|
||||
/// When a [RawGestureDetector] receives a semantics gesture, it will invoke
|
||||
/// the corresponding method that each recognizer reports in the configuration.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [GestureRecognizer.semanticsConfiguration], a method that returns this
|
||||
/// class.
|
||||
class SemanticsGestureConfiguration {
|
||||
/// Initialize the semantics handler configuration by declaring the handlers
|
||||
/// for each kind of semantics events.
|
||||
SemanticsGestureConfiguration({
|
||||
this.onTap,
|
||||
this.onLongPress,
|
||||
this.onHorizontalDragUpdate,
|
||||
this.onVerticalDragUpdate,
|
||||
});
|
||||
|
||||
/// Called when the user taps with a semantics device.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [RenderSemanticsGestureHandler.onTap], which calls this handler with
|
||||
/// the help of [RawGestureRecognizer].
|
||||
final SemanticsTapCallback onTap;
|
||||
|
||||
/// Called when the user presses for a long period of time with a semantics
|
||||
/// device.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [RenderSemanticsGestureHandler.onLongPress], which calls this handler
|
||||
/// with the help of [RawGestureRecognizer].
|
||||
final SemanticsLongPressCallback onLongPress;
|
||||
|
||||
/// Called when the user scrolls to the left or to the right with a semantics
|
||||
/// device.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [RenderSemanticsGestureHandler.onHorizontalDragUpdate], which calls
|
||||
/// this handler with the help of [RawGestureRecognizer].
|
||||
final SemanticsDragUpdateCallback onHorizontalDragUpdate;
|
||||
|
||||
/// Called when the user scrolls up or down with a semantics device.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [RenderSemanticsGestureHandler.onVerticalDragUpdate], which calls
|
||||
/// this handler with the help of [RawGestureRecognizer].
|
||||
final SemanticsDragUpdateCallback onVerticalDragUpdate;
|
||||
}
|
|
@ -8,6 +8,7 @@ import 'arena.dart';
|
|||
import 'constants.dart';
|
||||
import 'events.dart';
|
||||
import 'recognizer.dart';
|
||||
import 'semantics.dart';
|
||||
|
||||
/// Details for [GestureTapDownCallback], such as position
|
||||
///
|
||||
|
@ -237,6 +238,25 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
|
|||
// different set of buttons, the gesture is canceled.
|
||||
int _initialButtons;
|
||||
|
||||
@override
|
||||
SemanticsGestureConfiguration get semanticsConfiguration {
|
||||
// This method must always return a non-null configuration with a non-null
|
||||
// onTap, even when there are no handlers related to semantics, because the
|
||||
// handler properties are mutable, and assigning properties will not notify
|
||||
// RawGestureDetector to update its combined configuration.
|
||||
return _semanticsConfiguration ??= SemanticsGestureConfiguration(
|
||||
onTap: () {
|
||||
if (onTapDown != null)
|
||||
onTapDown(TapDownDetails());
|
||||
if (onTapUp != null)
|
||||
onTapUp(TapUpDetails());
|
||||
if (onTap != null)
|
||||
onTap();
|
||||
},
|
||||
);
|
||||
}
|
||||
SemanticsGestureConfiguration _semanticsConfiguration;
|
||||
|
||||
@override
|
||||
bool isPointerAllowed(PointerDownEvent event) {
|
||||
switch (event.buttons) {
|
||||
|
|
|
@ -3234,10 +3234,10 @@ class RenderSemanticsGestureHandler extends RenderProxyBox {
|
|||
/// The [scrollFactor] argument must not be null.
|
||||
RenderSemanticsGestureHandler({
|
||||
RenderBox child,
|
||||
GestureTapCallback onTap,
|
||||
GestureLongPressCallback onLongPress,
|
||||
GestureDragUpdateCallback onHorizontalDragUpdate,
|
||||
GestureDragUpdateCallback onVerticalDragUpdate,
|
||||
SemanticsTapCallback onTap,
|
||||
SemanticsLongPressCallback onLongPress,
|
||||
SemanticsDragUpdateCallback onHorizontalDragUpdate,
|
||||
SemanticsDragUpdateCallback onVerticalDragUpdate,
|
||||
this.scrollFactor = 0.8,
|
||||
}) : assert(scrollFactor != null),
|
||||
_onTap = onTap,
|
||||
|
@ -3269,9 +3269,9 @@ class RenderSemanticsGestureHandler extends RenderProxyBox {
|
|||
}
|
||||
|
||||
/// Called when the user taps on the render object.
|
||||
GestureTapCallback get onTap => _onTap;
|
||||
GestureTapCallback _onTap;
|
||||
set onTap(GestureTapCallback value) {
|
||||
SemanticsTapCallback get onTap => _onTap;
|
||||
SemanticsTapCallback _onTap;
|
||||
set onTap(SemanticsTapCallback value) {
|
||||
if (_onTap == value)
|
||||
return;
|
||||
final bool hadHandler = _onTap != null;
|
||||
|
@ -3281,9 +3281,9 @@ class RenderSemanticsGestureHandler extends RenderProxyBox {
|
|||
}
|
||||
|
||||
/// Called when the user presses on the render object for a long period of time.
|
||||
GestureLongPressCallback get onLongPress => _onLongPress;
|
||||
GestureLongPressCallback _onLongPress;
|
||||
set onLongPress(GestureLongPressCallback value) {
|
||||
SemanticsLongPressCallback get onLongPress => _onLongPress;
|
||||
SemanticsLongPressCallback _onLongPress;
|
||||
set onLongPress(SemanticsLongPressCallback value) {
|
||||
if (_onLongPress == value)
|
||||
return;
|
||||
final bool hadHandler = _onLongPress != null;
|
||||
|
@ -3293,9 +3293,9 @@ class RenderSemanticsGestureHandler extends RenderProxyBox {
|
|||
}
|
||||
|
||||
/// Called when the user scrolls to the left or to the right.
|
||||
GestureDragUpdateCallback get onHorizontalDragUpdate => _onHorizontalDragUpdate;
|
||||
GestureDragUpdateCallback _onHorizontalDragUpdate;
|
||||
set onHorizontalDragUpdate(GestureDragUpdateCallback value) {
|
||||
SemanticsDragUpdateCallback get onHorizontalDragUpdate => _onHorizontalDragUpdate;
|
||||
SemanticsDragUpdateCallback _onHorizontalDragUpdate;
|
||||
set onHorizontalDragUpdate(SemanticsDragUpdateCallback value) {
|
||||
if (_onHorizontalDragUpdate == value)
|
||||
return;
|
||||
final bool hadHandler = _onHorizontalDragUpdate != null;
|
||||
|
@ -3305,9 +3305,9 @@ class RenderSemanticsGestureHandler extends RenderProxyBox {
|
|||
}
|
||||
|
||||
/// Called when the user scrolls up or down.
|
||||
GestureDragUpdateCallback get onVerticalDragUpdate => _onVerticalDragUpdate;
|
||||
GestureDragUpdateCallback _onVerticalDragUpdate;
|
||||
set onVerticalDragUpdate(GestureDragUpdateCallback value) {
|
||||
SemanticsDragUpdateCallback get onVerticalDragUpdate => _onVerticalDragUpdate;
|
||||
SemanticsDragUpdateCallback _onVerticalDragUpdate;
|
||||
set onVerticalDragUpdate(SemanticsDragUpdateCallback value) {
|
||||
if (_onVerticalDragUpdate == value)
|
||||
return;
|
||||
final bool hadHandler = _onVerticalDragUpdate != null;
|
||||
|
|
|
@ -105,6 +105,87 @@ class GestureRecognizerFactoryWithHandlers<T extends GestureRecognizer> extends
|
|||
void initializer(T instance) => _initializer(instance);
|
||||
}
|
||||
|
||||
// Retrieve semantics handlers from the given recognizers and combine them into
|
||||
// collective callbacks, one for each kind of semantics gesture.
|
||||
//
|
||||
// This class stores the handlers instead of the recognizers, therefore any
|
||||
// change to the recognzier list needs to be followed by [buildHandlers].
|
||||
class _CombinedSemanticsHandlers {
|
||||
_CombinedSemanticsHandlers({
|
||||
Iterable<GestureRecognizer> recognizers,
|
||||
}) {
|
||||
buildHandlers(recognizers);
|
||||
}
|
||||
|
||||
final List<SemanticsTapCallback> _tapHandlers = <SemanticsTapCallback>[];
|
||||
final List<SemanticsLongPressCallback> _longPressHandlers =
|
||||
<SemanticsLongPressCallback>[];
|
||||
final List<SemanticsDragUpdateCallback> _horizontalHandlers =
|
||||
<SemanticsDragUpdateCallback>[];
|
||||
final List<SemanticsDragUpdateCallback> _verticalHandlers =
|
||||
<SemanticsDragUpdateCallback>[];
|
||||
|
||||
// The following getters of handlers return null unless any recognizers
|
||||
// show interest in the corresponding kind of semantics gestures.
|
||||
SemanticsTapCallback get onTap {
|
||||
return _tapHandlers.isEmpty ? null : _handleTap;
|
||||
}
|
||||
SemanticsLongPressCallback get onLongPress {
|
||||
return _longPressHandlers.isEmpty ? null : _handleLongPress;
|
||||
}
|
||||
SemanticsDragUpdateCallback get onHorizontalDragUpdate {
|
||||
return _horizontalHandlers.isEmpty ? null : _handleHorizontalDragUpdate;
|
||||
}
|
||||
SemanticsDragUpdateCallback get onVerticalDragUpdate {
|
||||
return _verticalHandlers.isEmpty ? null : _handleVerticalDragUpdate;
|
||||
}
|
||||
|
||||
// Clear internal cache of lists of callbacks, and build anew from the given
|
||||
// recognziers.
|
||||
void buildHandlers(Iterable<GestureRecognizer> recognizers) {
|
||||
_tapHandlers.clear();
|
||||
_longPressHandlers.clear();
|
||||
_horizontalHandlers.clear();
|
||||
_verticalHandlers.clear();
|
||||
if (recognizers == null)
|
||||
return;
|
||||
for (GestureRecognizer recognizer in recognizers) {
|
||||
final SemanticsGestureConfiguration configuration =
|
||||
recognizer.semanticsConfiguration;
|
||||
if (configuration == null)
|
||||
continue;
|
||||
if (configuration.onTap != null)
|
||||
_tapHandlers.add(configuration.onTap);
|
||||
if (configuration.onLongPress != null)
|
||||
_longPressHandlers.add(configuration.onLongPress);
|
||||
if (configuration.onHorizontalDragUpdate != null)
|
||||
_horizontalHandlers.add(configuration.onHorizontalDragUpdate);
|
||||
if (configuration.onVerticalDragUpdate != null)
|
||||
_verticalHandlers.add(configuration.onVerticalDragUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
void _handleTap() {
|
||||
for (SemanticsTapCallback callback in _tapHandlers)
|
||||
callback();
|
||||
}
|
||||
|
||||
void _handleLongPress() {
|
||||
for (SemanticsTapCallback callback in _longPressHandlers)
|
||||
callback();
|
||||
}
|
||||
|
||||
void _handleHorizontalDragUpdate(DragUpdateDetails details) {
|
||||
for (SemanticsDragUpdateCallback callback in _horizontalHandlers)
|
||||
callback(details);
|
||||
}
|
||||
|
||||
void _handleVerticalDragUpdate(DragUpdateDetails details) {
|
||||
for (SemanticsDragUpdateCallback callback in _verticalHandlers)
|
||||
callback(details);
|
||||
}
|
||||
}
|
||||
|
||||
/// A widget that detects gestures.
|
||||
///
|
||||
/// Attempts to recognize gestures that correspond to its non-null callbacks.
|
||||
|
@ -811,6 +892,8 @@ class RawGestureDetector extends StatefulWidget {
|
|||
/// State for a [RawGestureDetector].
|
||||
class RawGestureDetectorState extends State<RawGestureDetector> {
|
||||
Map<Type, GestureRecognizer> _recognizers = const <Type, GestureRecognizer>{};
|
||||
final _CombinedSemanticsHandlers _semanticsHandlers =
|
||||
_CombinedSemanticsHandlers();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -912,6 +995,7 @@ class RawGestureDetectorState extends State<RawGestureDetector> {
|
|||
if (!_recognizers.containsKey(type))
|
||||
oldRecognizers[type].dispose();
|
||||
}
|
||||
_semanticsHandlers.buildHandlers(_recognizers.values);
|
||||
}
|
||||
|
||||
void _handlePointerDown(PointerDownEvent event) {
|
||||
|
@ -924,92 +1008,6 @@ class RawGestureDetectorState extends State<RawGestureDetector> {
|
|||
return widget.child == null ? HitTestBehavior.translucent : HitTestBehavior.deferToChild;
|
||||
}
|
||||
|
||||
void _handleSemanticsTap() {
|
||||
final TapGestureRecognizer recognizer = _recognizers[TapGestureRecognizer];
|
||||
assert(recognizer != null);
|
||||
if (recognizer.onTapDown != null)
|
||||
recognizer.onTapDown(TapDownDetails());
|
||||
if (recognizer.onTapUp != null)
|
||||
recognizer.onTapUp(TapUpDetails());
|
||||
if (recognizer.onTap != null)
|
||||
recognizer.onTap();
|
||||
}
|
||||
|
||||
void _handleSemanticsLongPress() {
|
||||
final LongPressGestureRecognizer recognizer = _recognizers[LongPressGestureRecognizer];
|
||||
assert(recognizer != null);
|
||||
if (recognizer.onLongPressStart != null)
|
||||
recognizer.onLongPressStart(const LongPressStartDetails());
|
||||
if (recognizer.onLongPress != null)
|
||||
recognizer.onLongPress();
|
||||
if (recognizer.onLongPressEnd != null)
|
||||
recognizer.onLongPressEnd(const LongPressEndDetails());
|
||||
if (recognizer.onLongPressUp != null)
|
||||
recognizer.onLongPressUp();
|
||||
}
|
||||
|
||||
void _handleSemanticsHorizontalDragUpdate(DragUpdateDetails updateDetails) {
|
||||
{
|
||||
final HorizontalDragGestureRecognizer recognizer = _recognizers[HorizontalDragGestureRecognizer];
|
||||
if (recognizer != null) {
|
||||
if (recognizer.onDown != null)
|
||||
recognizer.onDown(DragDownDetails());
|
||||
if (recognizer.onStart != null)
|
||||
recognizer.onStart(DragStartDetails());
|
||||
if (recognizer.onUpdate != null)
|
||||
recognizer.onUpdate(updateDetails);
|
||||
if (recognizer.onEnd != null)
|
||||
recognizer.onEnd(DragEndDetails(primaryVelocity: 0.0));
|
||||
return;
|
||||
}
|
||||
}
|
||||
{
|
||||
final PanGestureRecognizer recognizer = _recognizers[PanGestureRecognizer];
|
||||
if (recognizer != null) {
|
||||
if (recognizer.onDown != null)
|
||||
recognizer.onDown(DragDownDetails());
|
||||
if (recognizer.onStart != null)
|
||||
recognizer.onStart(DragStartDetails());
|
||||
if (recognizer.onUpdate != null)
|
||||
recognizer.onUpdate(updateDetails);
|
||||
if (recognizer.onEnd != null)
|
||||
recognizer.onEnd(DragEndDetails());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _handleSemanticsVerticalDragUpdate(DragUpdateDetails updateDetails) {
|
||||
{
|
||||
final VerticalDragGestureRecognizer recognizer = _recognizers[VerticalDragGestureRecognizer];
|
||||
if (recognizer != null) {
|
||||
if (recognizer.onDown != null)
|
||||
recognizer.onDown(DragDownDetails());
|
||||
if (recognizer.onStart != null)
|
||||
recognizer.onStart(DragStartDetails());
|
||||
if (recognizer.onUpdate != null)
|
||||
recognizer.onUpdate(updateDetails);
|
||||
if (recognizer.onEnd != null)
|
||||
recognizer.onEnd(DragEndDetails(primaryVelocity: 0.0));
|
||||
return;
|
||||
}
|
||||
}
|
||||
{
|
||||
final PanGestureRecognizer recognizer = _recognizers[PanGestureRecognizer];
|
||||
if (recognizer != null) {
|
||||
if (recognizer.onDown != null)
|
||||
recognizer.onDown(DragDownDetails());
|
||||
if (recognizer.onStart != null)
|
||||
recognizer.onStart(DragStartDetails());
|
||||
if (recognizer.onUpdate != null)
|
||||
recognizer.onUpdate(updateDetails);
|
||||
if (recognizer.onEnd != null)
|
||||
recognizer.onEnd(DragEndDetails());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget result = Listener(
|
||||
|
@ -1068,21 +1066,16 @@ class _GestureSemantics extends SingleChildRenderObjectWidget {
|
|||
_updateHandlers(renderObject);
|
||||
}
|
||||
|
||||
GestureTapCallback get _onTapHandler {
|
||||
return owner._recognizers.containsKey(TapGestureRecognizer) ? owner._handleSemanticsTap : null;
|
||||
SemanticsTapCallback get _onTapHandler {
|
||||
return owner._semanticsHandlers.onTap;
|
||||
}
|
||||
|
||||
GestureTapCallback get _onLongPressHandler {
|
||||
return owner._recognizers.containsKey(LongPressGestureRecognizer) ? owner._handleSemanticsLongPress : null;
|
||||
SemanticsTapCallback get _onLongPressHandler {
|
||||
return owner._semanticsHandlers.onLongPress;
|
||||
}
|
||||
|
||||
GestureDragUpdateCallback get _onHorizontalDragUpdateHandler {
|
||||
return owner._recognizers.containsKey(HorizontalDragGestureRecognizer) ||
|
||||
owner._recognizers.containsKey(PanGestureRecognizer) ? owner._handleSemanticsHorizontalDragUpdate : null;
|
||||
SemanticsDragUpdateCallback get _onHorizontalDragUpdateHandler {
|
||||
return owner._semanticsHandlers.onHorizontalDragUpdate;
|
||||
}
|
||||
|
||||
GestureDragUpdateCallback get _onVerticalDragUpdateHandler {
|
||||
return owner._recognizers.containsKey(VerticalDragGestureRecognizer) ||
|
||||
owner._recognizers.containsKey(PanGestureRecognizer) ? owner._handleSemanticsVerticalDragUpdate : null;
|
||||
SemanticsDragUpdateCallback get _onVerticalDragUpdateHandler {
|
||||
return owner._semanticsHandlers.onVerticalDragUpdate;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -845,4 +845,120 @@ void main() {
|
|||
tap.dispose();
|
||||
recognized.clear();
|
||||
});
|
||||
|
||||
group('Semantic gesture configuration:', () {
|
||||
test('HorizontalDragGestureRecognizer\'s semantic handlers are correctly called', () {
|
||||
final List<String> logs = <String>[];
|
||||
final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer()
|
||||
..onDown = (_) { logs.add('onDown'); }
|
||||
..onStart = (_) { logs.add('onStart'); }
|
||||
..onUpdate = (_) { logs.add('onUpdate'); }
|
||||
..onEnd = (_) { logs.add('onEnd'); };
|
||||
final SemanticsGestureConfiguration configuration = drag.semanticsConfiguration;
|
||||
|
||||
expect(configuration, isNotNull);
|
||||
expect(configuration.onTap, isNull);
|
||||
expect(configuration.onLongPress, isNull);
|
||||
expect(configuration.onHorizontalDragUpdate, isNotNull);
|
||||
expect(configuration.onVerticalDragUpdate, isNull);
|
||||
|
||||
configuration.onHorizontalDragUpdate(DragUpdateDetails(
|
||||
globalPosition: const Offset(11, 13),
|
||||
));
|
||||
expect(logs, <String>['onDown', 'onStart', 'onUpdate', 'onEnd']);
|
||||
logs.clear();
|
||||
|
||||
// Assigning handler should update the configuration handler's behavior but
|
||||
// keep the configuration object
|
||||
drag..onDown = null
|
||||
..onStart = null
|
||||
..onUpdate = null
|
||||
..onEnd = null;
|
||||
final SemanticsGestureConfiguration configuration2 = drag.semanticsConfiguration;
|
||||
expect(configuration, same(configuration2));
|
||||
configuration.onHorizontalDragUpdate(DragUpdateDetails(
|
||||
globalPosition: const Offset(12, 14),
|
||||
));
|
||||
expect(logs, <String>[]);
|
||||
});
|
||||
|
||||
test('VerticalDragGestureRecognizer\'s semantic handlers are correctly called', () {
|
||||
final List<String> logs = <String>[];
|
||||
final VerticalDragGestureRecognizer drag = VerticalDragGestureRecognizer()
|
||||
..onDown = (_) { logs.add('onDown'); }
|
||||
..onStart = (_) { logs.add('onStart'); }
|
||||
..onUpdate = (_) { logs.add('onUpdate'); }
|
||||
..onEnd = (_) { logs.add('onEnd'); };
|
||||
final SemanticsGestureConfiguration configuration = drag.semanticsConfiguration;
|
||||
|
||||
expect(configuration, isNotNull);
|
||||
expect(configuration.onTap, isNull);
|
||||
expect(configuration.onLongPress, isNull);
|
||||
expect(configuration.onHorizontalDragUpdate, isNull);
|
||||
expect(configuration.onVerticalDragUpdate, isNotNull);
|
||||
|
||||
configuration.onVerticalDragUpdate(DragUpdateDetails(
|
||||
globalPosition: const Offset(11, 13),
|
||||
));
|
||||
expect(logs, <String>['onDown', 'onStart', 'onUpdate', 'onEnd']);
|
||||
logs.clear();
|
||||
|
||||
// Assigning handler should update the configuration handler's behavior but
|
||||
// keep the configuration object
|
||||
drag..onDown = null
|
||||
..onStart = null
|
||||
..onUpdate = null
|
||||
..onEnd = null;
|
||||
final SemanticsGestureConfiguration configuration2 = drag.semanticsConfiguration;
|
||||
expect(configuration, same(configuration2));
|
||||
configuration.onVerticalDragUpdate(DragUpdateDetails(
|
||||
globalPosition: const Offset(12, 14),
|
||||
));
|
||||
expect(logs, <String>[]);
|
||||
});
|
||||
|
||||
test('PanGestureRecognizer\'s semantic handlers are correctly called', () {
|
||||
final List<String> logs = <String>[];
|
||||
final PanGestureRecognizer drag = PanGestureRecognizer()
|
||||
..onDown = (_) { logs.add('onDown'); }
|
||||
..onStart = (_) { logs.add('onStart'); }
|
||||
..onUpdate = (_) { logs.add('onUpdate'); }
|
||||
..onEnd = (_) { logs.add('onEnd'); };
|
||||
final SemanticsGestureConfiguration configuration = drag.semanticsConfiguration;
|
||||
|
||||
expect(configuration, isNotNull);
|
||||
expect(configuration.onTap, isNull);
|
||||
expect(configuration.onLongPress, isNull);
|
||||
expect(configuration.onHorizontalDragUpdate, isNotNull);
|
||||
expect(configuration.onVerticalDragUpdate, isNotNull);
|
||||
|
||||
configuration.onVerticalDragUpdate(DragUpdateDetails(
|
||||
globalPosition: const Offset(11, 13),
|
||||
));
|
||||
expect(logs, <String>['onDown', 'onStart', 'onUpdate', 'onEnd']);
|
||||
logs.clear();
|
||||
|
||||
configuration.onHorizontalDragUpdate(DragUpdateDetails(
|
||||
globalPosition: const Offset(11, 13),
|
||||
));
|
||||
expect(logs, <String>['onDown', 'onStart', 'onUpdate', 'onEnd']);
|
||||
logs.clear();
|
||||
|
||||
// Assigning handler should update the configuration handler's behavior but
|
||||
// keep the configuration object
|
||||
drag..onDown = null
|
||||
..onStart = null
|
||||
..onUpdate = null
|
||||
..onEnd = null;
|
||||
final SemanticsGestureConfiguration configuration2 = drag.semanticsConfiguration;
|
||||
expect(configuration, same(configuration2));
|
||||
configuration.onVerticalDragUpdate(DragUpdateDetails(
|
||||
globalPosition: const Offset(12, 14),
|
||||
));
|
||||
configuration.onHorizontalDragUpdate(DragUpdateDetails(
|
||||
globalPosition: const Offset(12, 14),
|
||||
));
|
||||
expect(logs, <String>[]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -588,4 +588,35 @@ void main() {
|
|||
longPress.dispose();
|
||||
recognized.clear();
|
||||
});
|
||||
|
||||
test('Semantic onLongPress correctly calls handlers', () {
|
||||
final List<String> logs = <String>[];
|
||||
final LongPressGestureRecognizer longPress = LongPressGestureRecognizer()
|
||||
..onLongPressStart = (_) { logs.add('onLongPressStart'); }
|
||||
..onLongPress = () { logs.add('onLongPress'); }
|
||||
..onLongPressEnd = (_) { logs.add('onLongPressEnd'); }
|
||||
..onLongPressUp = () { logs.add('onLongPressUp'); };
|
||||
final SemanticsGestureConfiguration configuration = longPress.semanticsConfiguration;
|
||||
|
||||
expect(configuration, isNotNull);
|
||||
expect(configuration.onTap, isNull);
|
||||
expect(configuration.onLongPress, isNotNull);
|
||||
expect(configuration.onHorizontalDragUpdate, isNull);
|
||||
expect(configuration.onVerticalDragUpdate, isNull);
|
||||
|
||||
configuration.onLongPress();
|
||||
expect(logs, <String>['onLongPressStart', 'onLongPress', 'onLongPressEnd', 'onLongPressUp']);
|
||||
logs.clear();
|
||||
|
||||
// Assigning handler should update the configuration handler's behavior but
|
||||
// keep the configuration object
|
||||
longPress..onLongPressStart = null
|
||||
..onLongPress = null
|
||||
..onLongPressEnd = null
|
||||
..onLongPressUp = null;
|
||||
final SemanticsGestureConfiguration configuration2 = longPress.semanticsConfiguration;
|
||||
expect(configuration, same(configuration2));
|
||||
configuration.onLongPress();
|
||||
expect(logs, <String>[]);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -799,4 +799,33 @@ void main() {
|
|||
expect(recognized, <String>['secondaryCancel']);
|
||||
});
|
||||
});
|
||||
|
||||
test('Semantic onTap correctly calls handlers', () {
|
||||
final List<String> logs = <String>[];
|
||||
final TapGestureRecognizer tap = TapGestureRecognizer()
|
||||
..onTapDown = (_) { logs.add('onTapDown'); }
|
||||
..onTapUp = (_) { logs.add('onTapUp'); }
|
||||
..onTap = () { logs.add('onTap'); };
|
||||
final SemanticsGestureConfiguration configuration = tap.semanticsConfiguration;
|
||||
|
||||
expect(configuration, isNotNull);
|
||||
expect(configuration.onTap, isNotNull);
|
||||
expect(configuration.onLongPress, isNull);
|
||||
expect(configuration.onHorizontalDragUpdate, isNull);
|
||||
expect(configuration.onVerticalDragUpdate, isNull);
|
||||
|
||||
configuration.onTap();
|
||||
expect(logs, <String>['onTapDown', 'onTapUp', 'onTap']);
|
||||
logs.clear();
|
||||
|
||||
// Assigning handler should update the configuration handler's behavior but
|
||||
// keep the configuration object
|
||||
tap..onTapDown = null
|
||||
..onTapUp = null
|
||||
..onTap = null;
|
||||
final SemanticsGestureConfiguration configuration2 = tap.semanticsConfiguration;
|
||||
expect(configuration, same(configuration2));
|
||||
configuration.onTap();
|
||||
expect(logs, <String>[]);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
@ -76,4 +77,124 @@ void main() {
|
|||
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('All registered handlers for the gesture kind are called', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = SemanticsTester(tester);
|
||||
|
||||
final Set<String> logs = <String>{};
|
||||
final GlobalKey detectorKey = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(
|
||||
Center(
|
||||
child: GestureDetector(
|
||||
key: detectorKey,
|
||||
onHorizontalDragStart: (_) { logs.add('horizontal'); },
|
||||
onPanStart: (_) { logs.add('pan'); },
|
||||
child: Container(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final int detectorId = detectorKey.currentContext.findRenderObject().debugSemantics.id;
|
||||
tester.binding.pipelineOwner.semanticsOwner.performAction(detectorId, SemanticsAction.scrollLeft);
|
||||
expect(logs, <String>{'horizontal', 'pan'});
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('Replacing recognizers should update semantic handlers', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = SemanticsTester(tester);
|
||||
|
||||
// How the test is set up:
|
||||
// - Base state: RawGestureDetector with a HorizontalGR
|
||||
// - Calling `introduceLayoutPerformer()` adds a `TestLayoutPerformer` as
|
||||
// child of RawGestureDetector.
|
||||
// - TestLayoutPerformer calls RawGestureDetector.replaceGestureRecognizers
|
||||
// during layout phase, which replaces the recognizers with a TapGR.
|
||||
|
||||
final Set<String> logs = <String>{};
|
||||
final GlobalKey<RawGestureDetectorState> detectorKey = GlobalKey();
|
||||
final VoidCallback performLayout = () {
|
||||
detectorKey.currentState.replaceGestureRecognizers(<Type, GestureRecognizerFactory>{
|
||||
TapGestureRecognizer: GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
|
||||
() => TapGestureRecognizer(),
|
||||
(TapGestureRecognizer instance) {
|
||||
instance
|
||||
..onTap = () { logs.add('tap'); };
|
||||
},
|
||||
)
|
||||
});
|
||||
};
|
||||
|
||||
bool hasLayoutPerformer = false;
|
||||
VoidCallback introduceLayoutPerformer;
|
||||
await tester.pumpWidget(
|
||||
StatefulBuilder(
|
||||
builder: (BuildContext context, StateSetter setter) {
|
||||
introduceLayoutPerformer = () {
|
||||
setter(() {
|
||||
hasLayoutPerformer = true;
|
||||
});
|
||||
};
|
||||
return Center(
|
||||
child: RawGestureDetector(
|
||||
key: detectorKey,
|
||||
gestures: <Type, GestureRecognizerFactory>{
|
||||
HorizontalDragGestureRecognizer: GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(
|
||||
() => HorizontalDragGestureRecognizer(),
|
||||
(HorizontalDragGestureRecognizer instance) {
|
||||
instance
|
||||
..onStart = (_) { logs.add('horizontal'); };
|
||||
},
|
||||
)
|
||||
},
|
||||
child: hasLayoutPerformer ? TestLayoutPerformer(performLayout: performLayout) : null,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
final int detectorId = detectorKey.currentContext.findRenderObject().debugSemantics.id;
|
||||
tester.binding.pipelineOwner.semanticsOwner.performAction(detectorId, SemanticsAction.scrollLeft);
|
||||
expect(logs, <String>{'horizontal'});
|
||||
logs.clear();
|
||||
|
||||
introduceLayoutPerformer();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
tester.binding.pipelineOwner.semanticsOwner.performAction(detectorId, SemanticsAction.scrollLeft);
|
||||
tester.binding.pipelineOwner.semanticsOwner.performAction(detectorId, SemanticsAction.tap);
|
||||
expect(logs, <String>{'tap'});
|
||||
logs.clear();
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
class TestLayoutPerformer extends SingleChildRenderObjectWidget {
|
||||
const TestLayoutPerformer({
|
||||
Key key,
|
||||
this.performLayout,
|
||||
}) : super(key: key);
|
||||
|
||||
final VoidCallback performLayout;
|
||||
|
||||
@override
|
||||
RenderTestLayoutPerformer createRenderObject(BuildContext context) {
|
||||
return RenderTestLayoutPerformer(performLayout: performLayout);
|
||||
}
|
||||
}
|
||||
|
||||
class RenderTestLayoutPerformer extends RenderBox {
|
||||
RenderTestLayoutPerformer({VoidCallback performLayout}) : _performLayout = performLayout;
|
||||
|
||||
VoidCallback _performLayout;
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
size = const Size(1, 1);
|
||||
if (_performLayout != null)
|
||||
_performLayout();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue