Encode scrolling status into tree (#14536)
This commit is contained in:
parent
248919fa9f
commit
72517f0a0a
@ -1 +1 @@
|
|||||||
a031239a5d4e44e60d0ebc62b8c544a9f592fc22
|
8ac6f6efa177fb548dcdc81f1501f060b2ad1115
|
||||||
|
@ -12,7 +12,7 @@ bool nearEqual(double a, double b, double epsilon) {
|
|||||||
assert(epsilon >= 0.0);
|
assert(epsilon >= 0.0);
|
||||||
if (a == null || b == null)
|
if (a == null || b == null)
|
||||||
return a == b;
|
return a == b;
|
||||||
return (a > (b - epsilon)) && (a < (b + epsilon));
|
return (a > (b - epsilon)) && (a < (b + epsilon)) || a == b;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether a double is within a given distance of zero.
|
/// Whether a double is within a given distance of zero.
|
||||||
|
@ -95,6 +95,9 @@ class SemanticsData extends Diagnosticable {
|
|||||||
@required this.nextNodeId,
|
@required this.nextNodeId,
|
||||||
@required this.rect,
|
@required this.rect,
|
||||||
@required this.textSelection,
|
@required this.textSelection,
|
||||||
|
@required this.scrollPosition,
|
||||||
|
@required this.scrollExtentMax,
|
||||||
|
@required this.scrollExtentMin,
|
||||||
this.tags,
|
this.tags,
|
||||||
this.transform,
|
this.transform,
|
||||||
}) : assert(flags != null),
|
}) : assert(flags != null),
|
||||||
@ -156,6 +159,38 @@ class SemanticsData extends Diagnosticable {
|
|||||||
/// if this node represents a text field.
|
/// if this node represents a text field.
|
||||||
final TextSelection textSelection;
|
final TextSelection textSelection;
|
||||||
|
|
||||||
|
/// Indicates the current scrolling position in logical pixels if the node is
|
||||||
|
/// scrollable.
|
||||||
|
///
|
||||||
|
/// The properties [scrollExtentMin] and [scrollExtentMax] indicate the valid
|
||||||
|
/// in-range values for this property. The value for [scrollPosition] may
|
||||||
|
/// (temporarily) be outside that range, e.g. during an overscroll.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [ScrollPosition.pixels], from where this value is usually taken.
|
||||||
|
final double scrollPosition;
|
||||||
|
|
||||||
|
/// Indicates the maximum in-range value for [scrollPosition] if the node is
|
||||||
|
/// scrollable.
|
||||||
|
///
|
||||||
|
/// This value may be infinity if the scroll is unbound.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [ScrollPosition.maxScrollExtent], from where this value is usually taken.
|
||||||
|
final double scrollExtentMax;
|
||||||
|
|
||||||
|
/// Indicates the mimimum in-range value for [scrollPosition] if the node is
|
||||||
|
/// scrollable.
|
||||||
|
///
|
||||||
|
/// This value may be infinity if the scroll is unbound.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [ScrollPosition.minScrollExtent], from where this value is usually taken.
|
||||||
|
final double scrollExtentMin;
|
||||||
|
|
||||||
/// The bounding box for this node in its coordinate system.
|
/// The bounding box for this node in its coordinate system.
|
||||||
final Rect rect;
|
final Rect rect;
|
||||||
|
|
||||||
@ -204,7 +239,10 @@ class SemanticsData extends Diagnosticable {
|
|||||||
properties.add(new EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
|
properties.add(new EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
|
||||||
properties.add(new IntProperty('nextNodeId', nextNodeId, defaultValue: null));
|
properties.add(new IntProperty('nextNodeId', nextNodeId, defaultValue: null));
|
||||||
if (textSelection?.isValid == true)
|
if (textSelection?.isValid == true)
|
||||||
properties.add(new MessageProperty('text selection', '[${textSelection.start}, ${textSelection.end}]'));
|
properties.add(new MessageProperty('textSelection', '[${textSelection.start}, ${textSelection.end}]'));
|
||||||
|
properties.add(new DoubleProperty('scrollExtentMin', scrollExtentMin, defaultValue: null));
|
||||||
|
properties.add(new DoubleProperty('scrollPosition', scrollPosition, defaultValue: null));
|
||||||
|
properties.add(new DoubleProperty('scrollExtentMax', scrollExtentMax, defaultValue: null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -224,11 +262,14 @@ class SemanticsData extends Diagnosticable {
|
|||||||
&& typedOther.rect == rect
|
&& typedOther.rect == rect
|
||||||
&& setEquals(typedOther.tags, tags)
|
&& setEquals(typedOther.tags, tags)
|
||||||
&& typedOther.textSelection == textSelection
|
&& typedOther.textSelection == textSelection
|
||||||
|
&& typedOther.scrollPosition == scrollPosition
|
||||||
|
&& typedOther.scrollExtentMax == scrollExtentMax
|
||||||
|
&& typedOther.scrollExtentMin == scrollExtentMin
|
||||||
&& typedOther.transform == transform;
|
&& typedOther.transform == transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => ui.hashValues(flags, actions, label, value, increasedValue, decreasedValue, hint, textDirection, nextNodeId, rect, tags, textSelection, transform);
|
int get hashCode => ui.hashValues(flags, actions, label, value, increasedValue, decreasedValue, hint, textDirection, nextNodeId, rect, tags, textSelection, scrollPosition, scrollExtentMax, scrollExtentMin, transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SemanticsDiagnosticableNode extends DiagnosticableNode<SemanticsNode> {
|
class _SemanticsDiagnosticableNode extends DiagnosticableNode<SemanticsNode> {
|
||||||
@ -915,6 +956,9 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
|||||||
_textDirection != config.textDirection ||
|
_textDirection != config.textDirection ||
|
||||||
_sortOrder != config._sortOrder ||
|
_sortOrder != config._sortOrder ||
|
||||||
_textSelection != config._textSelection ||
|
_textSelection != config._textSelection ||
|
||||||
|
_scrollPosition != config._scrollPosition ||
|
||||||
|
_scrollExtentMax != config._scrollExtentMax ||
|
||||||
|
_scrollExtentMin != config._scrollExtentMin ||
|
||||||
_actionsAsBits != config._actionsAsBits ||
|
_actionsAsBits != config._actionsAsBits ||
|
||||||
_mergeAllDescendantsIntoThisNode != config.isMergingSemanticsOfDescendants;
|
_mergeAllDescendantsIntoThisNode != config.isMergingSemanticsOfDescendants;
|
||||||
}
|
}
|
||||||
@ -1011,6 +1055,42 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
|||||||
TextSelection get textSelection => _textSelection;
|
TextSelection get textSelection => _textSelection;
|
||||||
TextSelection _textSelection;
|
TextSelection _textSelection;
|
||||||
|
|
||||||
|
/// Indicates the current scrolling position in logical pixels if the node is
|
||||||
|
/// scrollable.
|
||||||
|
///
|
||||||
|
/// The properties [scrollExtentMin] and [scrollExtentMax] indicate the valid
|
||||||
|
/// in-range values for this property. The value for [scrollPosition] may
|
||||||
|
/// (temporarily) be outside that range, e.g. during an overscroll.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [ScrollPosition.pixels], from where this value is usually taken.
|
||||||
|
double get scrollPosition => _scrollPosition;
|
||||||
|
double _scrollPosition;
|
||||||
|
|
||||||
|
|
||||||
|
/// Indicates the maximum in-range value for [scrollPosition] if the node is
|
||||||
|
/// scrollable.
|
||||||
|
///
|
||||||
|
/// This value may be infinity if the scroll is unbound.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [ScrollPosition.maxScrollExtent], from where this value is usually taken.
|
||||||
|
double get scrollExtentMax => _scrollExtentMax;
|
||||||
|
double _scrollExtentMax;
|
||||||
|
|
||||||
|
/// Indicates the mimimum in-range value for [scrollPosition] if the node is
|
||||||
|
/// scrollable.
|
||||||
|
///
|
||||||
|
/// This value may be infinity if the scroll is unbound.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [ScrollPosition.minScrollExtent] from where this value is usually taken.
|
||||||
|
double get scrollExtentMin => _scrollExtentMin;
|
||||||
|
double _scrollExtentMin;
|
||||||
|
|
||||||
bool _canPerformAction(SemanticsAction action) => _actions.containsKey(action);
|
bool _canPerformAction(SemanticsAction action) => _actions.containsKey(action);
|
||||||
|
|
||||||
static final SemanticsConfiguration _kEmptyConfig = new SemanticsConfiguration();
|
static final SemanticsConfiguration _kEmptyConfig = new SemanticsConfiguration();
|
||||||
@ -1043,6 +1123,9 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
|||||||
_actions = new Map<SemanticsAction, _SemanticsActionHandler>.from(config._actions);
|
_actions = new Map<SemanticsAction, _SemanticsActionHandler>.from(config._actions);
|
||||||
_actionsAsBits = config._actionsAsBits;
|
_actionsAsBits = config._actionsAsBits;
|
||||||
_textSelection = config._textSelection;
|
_textSelection = config._textSelection;
|
||||||
|
_scrollPosition = config._scrollPosition;
|
||||||
|
_scrollExtentMax = config._scrollExtentMax;
|
||||||
|
_scrollExtentMin = config._scrollExtentMin;
|
||||||
_mergeAllDescendantsIntoThisNode = config.isMergingSemanticsOfDescendants;
|
_mergeAllDescendantsIntoThisNode = config.isMergingSemanticsOfDescendants;
|
||||||
_replaceChildren(childrenInInversePaintOrder ?? const <SemanticsNode>[]);
|
_replaceChildren(childrenInInversePaintOrder ?? const <SemanticsNode>[]);
|
||||||
|
|
||||||
@ -1074,6 +1157,9 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
|||||||
int nextNodeId = _nextNodeId;
|
int nextNodeId = _nextNodeId;
|
||||||
Set<SemanticsTag> mergedTags = tags == null ? null : new Set<SemanticsTag>.from(tags);
|
Set<SemanticsTag> mergedTags = tags == null ? null : new Set<SemanticsTag>.from(tags);
|
||||||
TextSelection textSelection = _textSelection;
|
TextSelection textSelection = _textSelection;
|
||||||
|
double scrollPosition = _scrollPosition;
|
||||||
|
double scrollExtentMax = _scrollExtentMax;
|
||||||
|
double scrollExtentMin = _scrollExtentMin;
|
||||||
|
|
||||||
if (mergeAllDescendantsIntoThisNode) {
|
if (mergeAllDescendantsIntoThisNode) {
|
||||||
_visitDescendants((SemanticsNode node) {
|
_visitDescendants((SemanticsNode node) {
|
||||||
@ -1083,6 +1169,9 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
|||||||
textDirection ??= node._textDirection;
|
textDirection ??= node._textDirection;
|
||||||
nextNodeId ??= node._nextNodeId;
|
nextNodeId ??= node._nextNodeId;
|
||||||
textSelection ??= node._textSelection;
|
textSelection ??= node._textSelection;
|
||||||
|
scrollPosition ??= node._scrollPosition;
|
||||||
|
scrollExtentMax ??= node._scrollExtentMax;
|
||||||
|
scrollExtentMin ??= node._scrollExtentMin;
|
||||||
if (value == '' || value == null)
|
if (value == '' || value == null)
|
||||||
value = node._value;
|
value = node._value;
|
||||||
if (increasedValue == '' || increasedValue == null)
|
if (increasedValue == '' || increasedValue == null)
|
||||||
@ -1123,6 +1212,9 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
|||||||
transform: transform,
|
transform: transform,
|
||||||
tags: mergedTags,
|
tags: mergedTags,
|
||||||
textSelection: textSelection,
|
textSelection: textSelection,
|
||||||
|
scrollPosition: scrollPosition,
|
||||||
|
scrollExtentMax: scrollExtentMax,
|
||||||
|
scrollExtentMin: scrollExtentMin,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1160,6 +1252,9 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
|||||||
nextNodeId: data.nextNodeId,
|
nextNodeId: data.nextNodeId,
|
||||||
textSelectionBase: data.textSelection != null ? data.textSelection.baseOffset : -1,
|
textSelectionBase: data.textSelection != null ? data.textSelection.baseOffset : -1,
|
||||||
textSelectionExtent: data.textSelection != null ? data.textSelection.extentOffset : -1,
|
textSelectionExtent: data.textSelection != null ? data.textSelection.extentOffset : -1,
|
||||||
|
scrollPosition: data.scrollPosition != null ? data.scrollPosition : double.nan,
|
||||||
|
scrollExtentMax: data.scrollExtentMax != null ? data.scrollExtentMax : double.nan,
|
||||||
|
scrollExtentMin: data.scrollExtentMin != null ? data.scrollExtentMin : double.nan,
|
||||||
transform: data.transform?.storage ?? _kIdentityTransform,
|
transform: data.transform?.storage ?? _kIdentityTransform,
|
||||||
children: children,
|
children: children,
|
||||||
);
|
);
|
||||||
@ -1232,6 +1327,9 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
|||||||
properties.add(new DiagnosticsProperty<SemanticsSortOrder>('sortOrder', sortOrder, defaultValue: null));
|
properties.add(new DiagnosticsProperty<SemanticsSortOrder>('sortOrder', sortOrder, defaultValue: null));
|
||||||
if (_textSelection?.isValid == true)
|
if (_textSelection?.isValid == true)
|
||||||
properties.add(new MessageProperty('text selection', '[${_textSelection.start}, ${_textSelection.end}]'));
|
properties.add(new MessageProperty('text selection', '[${_textSelection.start}, ${_textSelection.end}]'));
|
||||||
|
properties.add(new DoubleProperty('scrollExtentMin', scrollExtentMin, defaultValue: null));
|
||||||
|
properties.add(new DoubleProperty('scrollPosition', scrollPosition, defaultValue: null));
|
||||||
|
properties.add(new DoubleProperty('scrollExtentMax', scrollExtentMax, defaultValue: null));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a string representation of this node and its descendants.
|
/// Returns a string representation of this node and its descendants.
|
||||||
@ -2088,6 +2186,56 @@ class SemanticsConfiguration {
|
|||||||
_hasBeenAnnotated = true;
|
_hasBeenAnnotated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Indicates the current scrolling position in logical pixels if the node is
|
||||||
|
/// scrollable.
|
||||||
|
///
|
||||||
|
/// The properties [scrollExtentMin] and [scrollExtentMax] indicate the valid
|
||||||
|
/// in-range values for this property. The value for [scrollPosition] may
|
||||||
|
/// (temporarily) be outside that range, e.g. during an overscroll.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [ScrollPosition.pixels], from where this value is usually taken.
|
||||||
|
double get scrollPosition => _scrollPosition;
|
||||||
|
double _scrollPosition;
|
||||||
|
set scrollPosition(double value) {
|
||||||
|
assert(value != null);
|
||||||
|
_scrollPosition = value;
|
||||||
|
_hasBeenAnnotated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indicates the maximum in-range value for [scrollPosition] if the node is
|
||||||
|
/// scrollable.
|
||||||
|
///
|
||||||
|
/// This value may be infinity if the scroll is unbound.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [ScrollPosition.maxScrollExtent], from where this value is usually taken.
|
||||||
|
double get scrollExtentMax => _scrollExtentMax;
|
||||||
|
double _scrollExtentMax;
|
||||||
|
set scrollExtentMax(double value) {
|
||||||
|
assert(value != null);
|
||||||
|
_scrollExtentMax = value;
|
||||||
|
_hasBeenAnnotated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indicates the minimum in-range value for [scrollPosition] if the node is
|
||||||
|
/// scrollable.
|
||||||
|
///
|
||||||
|
/// This value may be infinity if the scroll is unbound.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [ScrollPosition.minScrollExtent], from where this value is usually taken.
|
||||||
|
double get scrollExtentMin => _scrollExtentMin;
|
||||||
|
double _scrollExtentMin;
|
||||||
|
set scrollExtentMin(double value) {
|
||||||
|
assert(value != null);
|
||||||
|
_scrollExtentMin = value;
|
||||||
|
_hasBeenAnnotated = true;
|
||||||
|
}
|
||||||
|
|
||||||
// TAGS
|
// TAGS
|
||||||
|
|
||||||
/// The set of tags that this configuration wants to add to all child
|
/// The set of tags that this configuration wants to add to all child
|
||||||
@ -2171,6 +2319,9 @@ class SemanticsConfiguration {
|
|||||||
_actionsAsBits |= other._actionsAsBits;
|
_actionsAsBits |= other._actionsAsBits;
|
||||||
_flags |= other._flags;
|
_flags |= other._flags;
|
||||||
_textSelection ??= other._textSelection;
|
_textSelection ??= other._textSelection;
|
||||||
|
_scrollPosition ??= other._scrollPosition;
|
||||||
|
_scrollExtentMax ??= other._scrollExtentMax;
|
||||||
|
_scrollExtentMin ??= other._scrollExtentMin;
|
||||||
|
|
||||||
textDirection ??= other.textDirection;
|
textDirection ??= other.textDirection;
|
||||||
_sortOrder = _sortOrder?.merge(other._sortOrder);
|
_sortOrder = _sortOrder?.merge(other._sortOrder);
|
||||||
@ -2214,6 +2365,9 @@ class SemanticsConfiguration {
|
|||||||
.._flags = _flags
|
.._flags = _flags
|
||||||
.._tagsForChildren = _tagsForChildren
|
.._tagsForChildren = _tagsForChildren
|
||||||
.._textSelection = _textSelection
|
.._textSelection = _textSelection
|
||||||
|
.._scrollPosition = _scrollPosition
|
||||||
|
.._scrollExtentMax = _scrollExtentMax
|
||||||
|
.._scrollExtentMin = _scrollExtentMin
|
||||||
.._actionsAsBits = _actionsAsBits
|
.._actionsAsBits = _actionsAsBits
|
||||||
.._actions.addAll(_actions);
|
.._actions.addAll(_actions);
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/painting.dart';
|
import 'package:flutter/painting.dart';
|
||||||
|
|
||||||
/// An event sent by the application to notify interested listeners that
|
/// An event sent by the application to notify interested listeners that
|
||||||
@ -53,75 +52,6 @@ abstract class SemanticsEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Notifies that a scroll action has been completed.
|
|
||||||
///
|
|
||||||
/// This event translates into a `AccessibilityEvent.TYPE_VIEW_SCROLLED` on
|
|
||||||
/// Android and a `UIAccessibilityPageScrolledNotification` on iOS. It is
|
|
||||||
/// processed by the accessibility systems of the operating system to provide
|
|
||||||
/// additional feedback to the user about the state of a scrollable view (e.g.
|
|
||||||
/// on Android, a ping sound is played to indicate that a scroll action was
|
|
||||||
/// successful).
|
|
||||||
class ScrollCompletedSemanticsEvent extends SemanticsEvent {
|
|
||||||
/// Creates a [ScrollCompletedSemanticsEvent].
|
|
||||||
///
|
|
||||||
/// This event should be sent after a scroll action is completed. It is
|
|
||||||
/// interpreted by assistive technologies to provide additional feedback about
|
|
||||||
/// the just completed scroll action to the user.
|
|
||||||
///
|
|
||||||
/// The parameters [axis], [pixels], [minScrollExtent], and [maxScrollExtent] are
|
|
||||||
/// required and may not be null.
|
|
||||||
ScrollCompletedSemanticsEvent({
|
|
||||||
@required this.axis,
|
|
||||||
@required this.pixels,
|
|
||||||
@required this.maxScrollExtent,
|
|
||||||
@required this.minScrollExtent
|
|
||||||
}) : assert(axis != null),
|
|
||||||
assert(pixels != null),
|
|
||||||
assert(maxScrollExtent != null),
|
|
||||||
assert(minScrollExtent != null),
|
|
||||||
super('scroll');
|
|
||||||
|
|
||||||
/// The axis in which the scroll view was scrolled.
|
|
||||||
///
|
|
||||||
/// See also [ScrollPosition.axis].
|
|
||||||
final Axis axis;
|
|
||||||
|
|
||||||
/// The current scroll position, in logical pixels.
|
|
||||||
///
|
|
||||||
/// See also [ScrollPosition.pixels].
|
|
||||||
final double pixels;
|
|
||||||
|
|
||||||
/// The minimum in-range value for [pixels].
|
|
||||||
///
|
|
||||||
/// See also [ScrollPosition.minScrollExtent].
|
|
||||||
final double minScrollExtent;
|
|
||||||
|
|
||||||
/// The maximum in-range value for [pixels].
|
|
||||||
///
|
|
||||||
/// See also [ScrollPosition.maxScrollExtent].
|
|
||||||
final double maxScrollExtent;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> getDataMap() {
|
|
||||||
final Map<String, dynamic> map = <String, dynamic>{
|
|
||||||
'pixels': pixels.clamp(minScrollExtent, maxScrollExtent),
|
|
||||||
'minScrollExtent': minScrollExtent,
|
|
||||||
'maxScrollExtent': maxScrollExtent,
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (axis) {
|
|
||||||
case Axis.horizontal:
|
|
||||||
map['axis'] = 'h';
|
|
||||||
break;
|
|
||||||
case Axis.vertical:
|
|
||||||
map['axis'] = 'v';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An event for a semantic announcement.
|
/// An event for a semantic announcement.
|
||||||
///
|
///
|
||||||
/// This should be used for announcement that are not seamlessly announced by
|
/// This should be used for announcement that are not seamlessly announced by
|
||||||
|
@ -59,11 +59,15 @@ abstract class ScrollMetrics {
|
|||||||
/// The minimum in-range value for [pixels].
|
/// The minimum in-range value for [pixels].
|
||||||
///
|
///
|
||||||
/// The actual [pixels] value might be [outOfRange].
|
/// The actual [pixels] value might be [outOfRange].
|
||||||
|
///
|
||||||
|
/// This value can be negative infinity, if the scroll is unbounded.
|
||||||
double get minScrollExtent;
|
double get minScrollExtent;
|
||||||
|
|
||||||
/// The maximum in-range value for [pixels].
|
/// The maximum in-range value for [pixels].
|
||||||
///
|
///
|
||||||
/// The actual [pixels] value might be [outOfRange].
|
/// The actual [pixels] value might be [outOfRange].
|
||||||
|
///
|
||||||
|
/// This value can be infinity, if the scroll is unbounded.
|
||||||
double get maxScrollExtent;
|
double get maxScrollExtent;
|
||||||
|
|
||||||
/// The current scroll position, in logical pixels along the [axisDirection].
|
/// The current scroll position, in logical pixels along the [axisDirection].
|
||||||
|
@ -275,7 +275,6 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin
|
|||||||
final ScrollPosition oldPosition = position;
|
final ScrollPosition oldPosition = position;
|
||||||
if (oldPosition != null) {
|
if (oldPosition != null) {
|
||||||
controller?.detach(oldPosition);
|
controller?.detach(oldPosition);
|
||||||
oldPosition.removeListener(_sendSemanticsScrollEvent);
|
|
||||||
// It's important that we not dispose the old position until after the
|
// It's important that we not dispose the old position until after the
|
||||||
// viewport has had a chance to unregister its listeners from the old
|
// viewport has had a chance to unregister its listeners from the old
|
||||||
// position. So, schedule a microtask to do it.
|
// position. So, schedule a microtask to do it.
|
||||||
@ -284,30 +283,10 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin
|
|||||||
|
|
||||||
_position = controller?.createScrollPosition(_physics, this, oldPosition)
|
_position = controller?.createScrollPosition(_physics, this, oldPosition)
|
||||||
?? new ScrollPositionWithSingleContext(physics: _physics, context: this, oldPosition: oldPosition);
|
?? new ScrollPositionWithSingleContext(physics: _physics, context: this, oldPosition: oldPosition);
|
||||||
_position.addListener(_sendSemanticsScrollEvent);
|
|
||||||
|
|
||||||
assert(position != null);
|
assert(position != null);
|
||||||
controller?.attach(position);
|
controller?.attach(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _semanticsScrollEventScheduled = false;
|
|
||||||
|
|
||||||
void _sendSemanticsScrollEvent() {
|
|
||||||
if (_semanticsScrollEventScheduled)
|
|
||||||
return;
|
|
||||||
SchedulerBinding.instance.addPostFrameCallback((Duration timestamp) {
|
|
||||||
final _RenderExcludableScrollSemantics render = _excludableScrollSemanticsKey.currentContext?.findRenderObject();
|
|
||||||
render?.sendSemanticsEvent(new ScrollCompletedSemanticsEvent(
|
|
||||||
axis: position.axis,
|
|
||||||
pixels: position.pixels,
|
|
||||||
minScrollExtent: position.minScrollExtent,
|
|
||||||
maxScrollExtent: position.maxScrollExtent,
|
|
||||||
));
|
|
||||||
_semanticsScrollEventScheduled = false;
|
|
||||||
});
|
|
||||||
_semanticsScrollEventScheduled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
@ -529,6 +508,7 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin
|
|||||||
result = new _ExcludableScrollSemantics(
|
result = new _ExcludableScrollSemantics(
|
||||||
key: _excludableScrollSemanticsKey,
|
key: _excludableScrollSemanticsKey,
|
||||||
child: result,
|
child: result,
|
||||||
|
position: position,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -557,28 +537,61 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin
|
|||||||
/// node, which is annotated with the scrolling actions, will house the
|
/// node, which is annotated with the scrolling actions, will house the
|
||||||
/// scrollable children.
|
/// scrollable children.
|
||||||
class _ExcludableScrollSemantics extends SingleChildRenderObjectWidget {
|
class _ExcludableScrollSemantics extends SingleChildRenderObjectWidget {
|
||||||
const _ExcludableScrollSemantics({ Key key, Widget child }) : super(key: key, child: child);
|
const _ExcludableScrollSemantics({
|
||||||
|
Key key,
|
||||||
|
@required this.position,
|
||||||
|
Widget child
|
||||||
|
}) : assert(position != null), super(key: key, child: child);
|
||||||
|
|
||||||
|
final ScrollPosition position;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_RenderExcludableScrollSemantics createRenderObject(BuildContext context) => new _RenderExcludableScrollSemantics();
|
_RenderExcludableScrollSemantics createRenderObject(BuildContext context) => new _RenderExcludableScrollSemantics(position: position);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void updateRenderObject(BuildContext context, _RenderExcludableScrollSemantics renderObject) {
|
||||||
|
renderObject.position = position;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RenderExcludableScrollSemantics extends RenderProxyBox {
|
class _RenderExcludableScrollSemantics extends RenderProxyBox {
|
||||||
_RenderExcludableScrollSemantics({ RenderBox child }) : super(child);
|
_RenderExcludableScrollSemantics({
|
||||||
|
@required ScrollPosition position,
|
||||||
|
RenderBox child,
|
||||||
|
}) : _position = position, assert(position != null), super(child) {
|
||||||
|
position.addListener(markNeedsSemanticsUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether this render object is excluded from the semantic tree.
|
||||||
|
ScrollPosition get position => _position;
|
||||||
|
ScrollPosition _position;
|
||||||
|
set position(ScrollPosition value) {
|
||||||
|
assert(value != null);
|
||||||
|
if (value == _position)
|
||||||
|
return;
|
||||||
|
_position.removeListener(markNeedsSemanticsUpdate);
|
||||||
|
_position = value;
|
||||||
|
_position.addListener(markNeedsSemanticsUpdate);
|
||||||
|
markNeedsSemanticsUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
||||||
super.describeSemanticsConfiguration(config);
|
super.describeSemanticsConfiguration(config);
|
||||||
config.isSemanticBoundary = true;
|
config.isSemanticBoundary = true;
|
||||||
|
if (position.haveDimensions) {
|
||||||
|
config
|
||||||
|
..scrollPosition = _position.pixels
|
||||||
|
..scrollExtentMax = _position.maxScrollExtent
|
||||||
|
..scrollExtentMin = _position.minScrollExtent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SemanticsNode _innerNode;
|
SemanticsNode _innerNode;
|
||||||
SemanticsNode _annotatedNode;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void assembleSemanticsNode(SemanticsNode node, SemanticsConfiguration config, Iterable<SemanticsNode> children) {
|
void assembleSemanticsNode(SemanticsNode node, SemanticsConfiguration config, Iterable<SemanticsNode> children) {
|
||||||
if (children.isEmpty || !children.first.isTagged(RenderViewport.useTwoPaneSemantics)) {
|
if (children.isEmpty || !children.first.isTagged(RenderViewport.useTwoPaneSemantics)) {
|
||||||
_annotatedNode = node;
|
|
||||||
super.assembleSemanticsNode(node, config, children);
|
super.assembleSemanticsNode(node, config, children);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -587,7 +600,6 @@ class _RenderExcludableScrollSemantics extends RenderProxyBox {
|
|||||||
_innerNode
|
_innerNode
|
||||||
..isMergedIntoParent = node.isPartOfNodeMerging
|
..isMergedIntoParent = node.isPartOfNodeMerging
|
||||||
..rect = Offset.zero & node.rect.size;
|
..rect = Offset.zero & node.rect.size;
|
||||||
_annotatedNode = _innerNode;
|
|
||||||
|
|
||||||
final List<SemanticsNode> excluded = <SemanticsNode>[_innerNode];
|
final List<SemanticsNode> excluded = <SemanticsNode>[_innerNode];
|
||||||
final List<SemanticsNode> included = <SemanticsNode>[];
|
final List<SemanticsNode> included = <SemanticsNode>[];
|
||||||
@ -606,12 +618,5 @@ class _RenderExcludableScrollSemantics extends RenderProxyBox {
|
|||||||
void clearSemantics() {
|
void clearSemantics() {
|
||||||
super.clearSemantics();
|
super.clearSemantics();
|
||||||
_innerNode = null;
|
_innerNode = null;
|
||||||
_annotatedNode = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sends a [SemanticsEvent] in the context of the [SemanticsNode] that is
|
|
||||||
/// annotated with this object's semantics information.
|
|
||||||
void sendSemanticsEvent(SemanticsEvent event) {
|
|
||||||
_annotatedNode?.sendEvent(event);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
19
packages/flutter/test/physics/utils_test.dart
Normal file
19
packages/flutter/test/physics/utils_test.dart
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// Copyright 2017 The Chromium 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 'package:flutter/physics.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('nearEquals', () {
|
||||||
|
expect(nearEqual(double.infinity, double.infinity, 0.1), isTrue);
|
||||||
|
expect(nearEqual(double.negativeInfinity, double.negativeInfinity, 0.1), isTrue);
|
||||||
|
|
||||||
|
expect(nearEqual(double.infinity, double.negativeInfinity, 0.1), isFalse);
|
||||||
|
|
||||||
|
expect(nearEqual(0.1, 0.11, 0.001), isFalse);
|
||||||
|
expect(nearEqual(0.1, 0.11, 0.1), isTrue);
|
||||||
|
expect(nearEqual(0.1, 0.1, 0.0000001), isTrue);
|
||||||
|
});
|
||||||
|
}
|
@ -348,7 +348,7 @@ void main() {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
minimalProperties.toStringDeep(minLevel: DiagnosticLevel.hidden),
|
minimalProperties.toStringDeep(minLevel: DiagnosticLevel.hidden),
|
||||||
'SemanticsNode#1(owner: null, isMergedIntoParent: false, mergeAllDescendantsIntoThisNode: false, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), actions: [], isInMutuallyExcusiveGroup: false, isSelected: false, isFocused: false, isButton: false, isTextField: false, invisible, label: "", value: "", increasedValue: "", decreasedValue: "", hint: "", textDirection: null, nextNodeId: null, sortOrder: null)\n'
|
'SemanticsNode#1(owner: null, isMergedIntoParent: false, mergeAllDescendantsIntoThisNode: false, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), actions: [], isInMutuallyExcusiveGroup: false, isSelected: false, isFocused: false, isButton: false, isTextField: false, invisible, label: "", value: "", increasedValue: "", decreasedValue: "", hint: "", textDirection: null, nextNodeId: null, sortOrder: null, scrollExtentMin: null, scrollPosition: null, scrollExtentMax: null)\n'
|
||||||
);
|
);
|
||||||
|
|
||||||
final SemanticsConfiguration config = new SemanticsConfiguration()
|
final SemanticsConfiguration config = new SemanticsConfiguration()
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
import 'semantics_tester.dart';
|
import 'semantics_tester.dart';
|
||||||
@ -204,12 +203,7 @@ void main() {
|
|||||||
expect(tester.getTopLeft(find.byWidget(children[1])).dy, kToolbarHeight);
|
expect(tester.getTopLeft(find.byWidget(children[1])).dy, kToolbarHeight);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('vertical scrolling sends ScrollCompletedSemanticsEvent', (WidgetTester tester) async {
|
testWidgets('correct scrollProgress', (WidgetTester tester) async {
|
||||||
final List<dynamic> messages = <dynamic>[];
|
|
||||||
SystemChannels.accessibility.setMockMessageHandler((dynamic message) {
|
|
||||||
messages.add(message);
|
|
||||||
});
|
|
||||||
|
|
||||||
semantics = new SemanticsTester(tester);
|
semantics = new SemanticsTester(tester);
|
||||||
|
|
||||||
final List<Widget> textWidgets = <Widget>[];
|
final List<Widget> textWidgets = <Widget>[];
|
||||||
@ -220,74 +214,84 @@ void main() {
|
|||||||
child: new ListView(children: textWidgets),
|
child: new ListView(children: textWidgets),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
expect(semantics, includesNodeWith(
|
||||||
|
scrollExtentMin: 0.0,
|
||||||
|
scrollPosition: 0.0,
|
||||||
|
scrollExtentMax: 520.0,
|
||||||
|
actions: <SemanticsAction>[
|
||||||
|
SemanticsAction.scrollUp,
|
||||||
|
],
|
||||||
|
));
|
||||||
|
|
||||||
await flingUp(tester);
|
await flingUp(tester);
|
||||||
|
|
||||||
expect(messages, isNot(hasLength(0)));
|
expect(semantics, includesNodeWith(
|
||||||
expect(messages.every((dynamic message) => message['type'] == 'scroll'), isTrue);
|
scrollExtentMin: 0.0,
|
||||||
|
scrollPosition: 380.2,
|
||||||
|
scrollExtentMax: 520.0,
|
||||||
|
actions: <SemanticsAction>[
|
||||||
|
SemanticsAction.scrollUp,
|
||||||
|
SemanticsAction.scrollDown,
|
||||||
|
],
|
||||||
|
));
|
||||||
|
|
||||||
Map<Object, Object> message = messages.last['data'];
|
await flingUp(tester);
|
||||||
expect(message['axis'], 'v');
|
|
||||||
expect(message['pixels'], isPositive);
|
|
||||||
expect(message['minScrollExtent'], 0.0);
|
|
||||||
expect(message['maxScrollExtent'], 520.0);
|
|
||||||
|
|
||||||
messages.clear();
|
expect(semantics, includesNodeWith(
|
||||||
await flingDown(tester);
|
scrollExtentMin: 0.0,
|
||||||
|
scrollPosition: 520.0,
|
||||||
expect(messages, isNot(hasLength(0)));
|
scrollExtentMax: 520.0,
|
||||||
expect(messages.every((dynamic message) => message['type'] == 'scroll'), isTrue);
|
actions: <SemanticsAction>[
|
||||||
|
SemanticsAction.scrollDown,
|
||||||
message = messages.last['data'];
|
],
|
||||||
expect(message['axis'], 'v');
|
));
|
||||||
expect(message['pixels'], isNonNegative);
|
|
||||||
expect(message['minScrollExtent'], 0.0);
|
|
||||||
expect(message['maxScrollExtent'], 520.0);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('horizontal scrolling sends ScrollCompletedSemanticsEvent', (WidgetTester tester) async {
|
|
||||||
final List<dynamic> messages = <dynamic>[];
|
|
||||||
SystemChannels.accessibility.setMockMessageHandler((dynamic message) {
|
|
||||||
messages.add(message);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('correct scrollProgress for unbound', (WidgetTester tester) async {
|
||||||
semantics = new SemanticsTester(tester);
|
semantics = new SemanticsTester(tester);
|
||||||
|
|
||||||
final List<Widget> children = <Widget>[];
|
|
||||||
for (int i = 0; i < 80; i++)
|
|
||||||
children.add(new Container(
|
|
||||||
child: new Text('$i'),
|
|
||||||
width: 100.0,
|
|
||||||
));
|
|
||||||
await tester.pumpWidget(new Directionality(
|
await tester.pumpWidget(new Directionality(
|
||||||
textDirection: TextDirection.ltr,
|
textDirection: TextDirection.ltr,
|
||||||
child: new ListView(
|
child: new ListView.builder(
|
||||||
children: children,
|
itemExtent: 20.0,
|
||||||
scrollDirection: Axis.horizontal,
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return new Text('entry $index');
|
||||||
|
},
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
await flingLeft(tester);
|
expect(semantics, includesNodeWith(
|
||||||
|
scrollExtentMin: 0.0,
|
||||||
|
scrollPosition: 0.0,
|
||||||
|
scrollExtentMax: double.infinity,
|
||||||
|
actions: <SemanticsAction>[
|
||||||
|
SemanticsAction.scrollUp,
|
||||||
|
],
|
||||||
|
));
|
||||||
|
|
||||||
expect(messages, isNot(hasLength(0)));
|
await flingUp(tester);
|
||||||
expect(messages.every((dynamic message) => message['type'] == 'scroll'), isTrue);
|
|
||||||
|
|
||||||
Map<Object, Object> message = messages.last['data'];
|
expect(semantics, includesNodeWith(
|
||||||
expect(message['axis'], 'h');
|
scrollExtentMin: 0.0,
|
||||||
expect(message['pixels'], isPositive);
|
scrollPosition: 380.2,
|
||||||
expect(message['minScrollExtent'], 0.0);
|
scrollExtentMax: double.infinity,
|
||||||
expect(message['maxScrollExtent'], 7200.0);
|
actions: <SemanticsAction>[
|
||||||
|
SemanticsAction.scrollUp,
|
||||||
|
SemanticsAction.scrollDown,
|
||||||
|
],
|
||||||
|
));
|
||||||
|
|
||||||
messages.clear();
|
await flingUp(tester);
|
||||||
await flingRight(tester);
|
|
||||||
|
|
||||||
expect(messages, isNot(hasLength(0)));
|
expect(semantics, includesNodeWith(
|
||||||
expect(messages.every((dynamic message) => message['type'] == 'scroll'), isTrue);
|
scrollExtentMin: 0.0,
|
||||||
|
scrollPosition: 760.4,
|
||||||
message = messages.last['data'];
|
scrollExtentMax: double.infinity,
|
||||||
expect(message['axis'], 'h');
|
actions: <SemanticsAction>[
|
||||||
expect(message['pixels'], isNonNegative);
|
SemanticsAction.scrollUp,
|
||||||
expect(message['minScrollExtent'], 0.0);
|
SemanticsAction.scrollDown,
|
||||||
expect(message['maxScrollExtent'], 7200.0);
|
],
|
||||||
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Semantics tree is populated mid-scroll', (WidgetTester tester) async {
|
testWidgets('Semantics tree is populated mid-scroll', (WidgetTester tester) async {
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
import 'dart:ui' show SemanticsFlag;
|
import 'dart:ui' show SemanticsFlag;
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/physics.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
@ -369,6 +370,9 @@ class SemanticsTester {
|
|||||||
TextDirection textDirection,
|
TextDirection textDirection,
|
||||||
List<SemanticsAction> actions,
|
List<SemanticsAction> actions,
|
||||||
List<SemanticsFlag> flags,
|
List<SemanticsFlag> flags,
|
||||||
|
double scrollPosition,
|
||||||
|
double scrollExtentMax,
|
||||||
|
double scrollExtentMin,
|
||||||
SemanticsNode ancestor,
|
SemanticsNode ancestor,
|
||||||
}) {
|
}) {
|
||||||
bool checkNode(SemanticsNode node) {
|
bool checkNode(SemanticsNode node) {
|
||||||
@ -390,6 +394,12 @@ class SemanticsTester {
|
|||||||
if (expectedFlags != actualFlags)
|
if (expectedFlags != actualFlags)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (scrollPosition != null && !nearEqual(node.scrollPosition, scrollPosition, 0.1))
|
||||||
|
return false;
|
||||||
|
if (scrollExtentMax != null && !nearEqual(node.scrollExtentMax, scrollExtentMax, 0.1))
|
||||||
|
return false;
|
||||||
|
if (scrollExtentMin != null && !nearEqual(node.scrollExtentMin, scrollExtentMin, 0.1))
|
||||||
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -578,13 +588,19 @@ class _IncludesNodeWith extends Matcher {
|
|||||||
this.textDirection,
|
this.textDirection,
|
||||||
this.actions,
|
this.actions,
|
||||||
this.flags,
|
this.flags,
|
||||||
}) : assert(label != null || value != null || actions != null || flags != null);
|
this.scrollPosition,
|
||||||
|
this.scrollExtentMax,
|
||||||
|
this.scrollExtentMin,
|
||||||
|
}) : assert(label != null || value != null || actions != null || flags != null || scrollPosition != null || scrollExtentMax != null || scrollExtentMin != null);
|
||||||
|
|
||||||
final String label;
|
final String label;
|
||||||
final String value;
|
final String value;
|
||||||
final TextDirection textDirection;
|
final TextDirection textDirection;
|
||||||
final List<SemanticsAction> actions;
|
final List<SemanticsAction> actions;
|
||||||
final List<SemanticsFlag> flags;
|
final List<SemanticsFlag> flags;
|
||||||
|
final double scrollPosition;
|
||||||
|
final double scrollExtentMax;
|
||||||
|
final double scrollExtentMin;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool matches(covariant SemanticsTester item, Map<dynamic, dynamic> matchState) {
|
bool matches(covariant SemanticsTester item, Map<dynamic, dynamic> matchState) {
|
||||||
@ -594,6 +610,9 @@ class _IncludesNodeWith extends Matcher {
|
|||||||
textDirection: textDirection,
|
textDirection: textDirection,
|
||||||
actions: actions,
|
actions: actions,
|
||||||
flags: flags,
|
flags: flags,
|
||||||
|
scrollPosition: scrollPosition,
|
||||||
|
scrollExtentMax: scrollExtentMax,
|
||||||
|
scrollExtentMin: scrollExtentMin,
|
||||||
).isNotEmpty;
|
).isNotEmpty;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -619,6 +638,12 @@ class _IncludesNodeWith extends Matcher {
|
|||||||
strings.add('actions "${actions.join(', ')}"');
|
strings.add('actions "${actions.join(', ')}"');
|
||||||
if (flags != null)
|
if (flags != null)
|
||||||
strings.add('flags "${flags.join(', ')}"');
|
strings.add('flags "${flags.join(', ')}"');
|
||||||
|
if (scrollPosition != null)
|
||||||
|
strings.add('scrollPosition "$scrollPosition"');
|
||||||
|
if (scrollExtentMax != null)
|
||||||
|
strings.add('scrollExtentMax "$scrollExtentMax"');
|
||||||
|
if (scrollExtentMin != null)
|
||||||
|
strings.add('scrollExtentMin "$scrollExtentMin"');
|
||||||
return strings.join(', ');
|
return strings.join(', ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -633,6 +658,9 @@ Matcher includesNodeWith({
|
|||||||
TextDirection textDirection,
|
TextDirection textDirection,
|
||||||
List<SemanticsAction> actions,
|
List<SemanticsAction> actions,
|
||||||
List<SemanticsFlag> flags,
|
List<SemanticsFlag> flags,
|
||||||
|
double scrollPosition,
|
||||||
|
double scrollExtentMax,
|
||||||
|
double scrollExtentMin,
|
||||||
}) {
|
}) {
|
||||||
return new _IncludesNodeWith(
|
return new _IncludesNodeWith(
|
||||||
label: label,
|
label: label,
|
||||||
@ -640,5 +668,8 @@ Matcher includesNodeWith({
|
|||||||
textDirection: textDirection,
|
textDirection: textDirection,
|
||||||
actions: actions,
|
actions: actions,
|
||||||
flags: flags,
|
flags: flags,
|
||||||
|
scrollPosition: scrollPosition,
|
||||||
|
scrollExtentMax: scrollExtentMax,
|
||||||
|
scrollExtentMin: scrollExtentMin,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user