Send scroll progress with ScrollCompletedSemanticsEvent (#12263)
* Send scroll progress with ScrollCompletedSemanticsEvent This requires engine change https://github.com/flutter/engine/pull/4144 * fix analyze warning * review comment * Roll engine to 45b11f742d38ebf564a5a832b1af00661d1a31fa * fix test
This commit is contained in:
parent
31fe65e23b
commit
2670786210
@ -1 +1 @@
|
||||
90ba98e741007cf249db26517ff8efea1a56057e
|
||||
45b11f742d38ebf564a5a832b1af00661d1a31fa
|
||||
|
@ -2,6 +2,9 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/painting.dart';
|
||||
|
||||
/// An event that can be send by the application to notify interested listeners
|
||||
/// that something happened to the user interface (e.g. a view scrolled).
|
||||
///
|
||||
@ -39,9 +42,57 @@ class ScrollCompletedSemanticsEvent extends SemanticsEvent {
|
||||
/// 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.
|
||||
// TODO(goderbauer): add more metadata to this event (e.g. how far are we scrolled?).
|
||||
ScrollCompletedSemanticsEvent() : super('scroll');
|
||||
///
|
||||
/// 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> toMap() => <String, dynamic>{};
|
||||
Map<String, dynamic> toMap() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -281,7 +281,12 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin
|
||||
if (_semanticsScrollEventScheduled)
|
||||
return;
|
||||
SchedulerBinding.instance.addPostFrameCallback((Duration timestamp) {
|
||||
_gestureDetectorKey.currentState?.sendSemanticsEvent(new ScrollCompletedSemanticsEvent());
|
||||
_gestureDetectorKey.currentState?.sendSemanticsEvent(new ScrollCompletedSemanticsEvent(
|
||||
axis: position.axis,
|
||||
pixels: position.pixels,
|
||||
minScrollExtent: position.minScrollExtent,
|
||||
maxScrollExtent: position.maxScrollExtent,
|
||||
));
|
||||
_semanticsScrollEventScheduled = false;
|
||||
});
|
||||
_semanticsScrollEventScheduled = true;
|
||||
|
@ -197,7 +197,7 @@ void main() {
|
||||
expect(tester.getTopLeft(find.byWidget(semantics[1])).dy, kToolbarHeight);
|
||||
});
|
||||
|
||||
testWidgets('scrolling sends ScrollCompletedSemanticsEvent', (WidgetTester tester) async {
|
||||
testWidgets('vertical scrolling sends ScrollCompletedSemanticsEvent', (WidgetTester tester) async {
|
||||
final List<dynamic> messages = <dynamic>[];
|
||||
SystemChannels.accessibility.setMockMessageHandler((dynamic message) {
|
||||
messages.add(message);
|
||||
@ -218,12 +218,72 @@ void main() {
|
||||
expect(messages, isNot(hasLength(0)));
|
||||
expect(messages.every((dynamic message) => message['type'] == 'scroll'), isTrue);
|
||||
|
||||
Map<String, Object> message = messages.last['data'];
|
||||
expect(message['axis'], 'v');
|
||||
expect(message['pixels'], isPositive);
|
||||
expect(message['minScrollExtent'], 0.0);
|
||||
expect(message['maxScrollExtent'], 520.0);
|
||||
|
||||
messages.clear();
|
||||
await flingDown(tester);
|
||||
|
||||
expect(messages, isNot(hasLength(0)));
|
||||
expect(messages.every((dynamic message) => message['type'] == 'scroll'), isTrue);
|
||||
|
||||
message = messages.last['data'];
|
||||
expect(message['axis'], 'v');
|
||||
expect(message['pixels'], isNonNegative);
|
||||
expect(message['minScrollExtent'], 0.0);
|
||||
expect(message['maxScrollExtent'], 520.0);
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('horizontal scrolling sends ScrollCompletedSemanticsEvent', (WidgetTester tester) async {
|
||||
final List<dynamic> messages = <dynamic>[];
|
||||
SystemChannels.accessibility.setMockMessageHandler((dynamic message) {
|
||||
messages.add(message);
|
||||
});
|
||||
|
||||
final SemanticsTester 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(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new ListView(
|
||||
children: children,
|
||||
scrollDirection: Axis.horizontal,
|
||||
),
|
||||
));
|
||||
|
||||
await flingLeft(tester);
|
||||
|
||||
expect(messages, isNot(hasLength(0)));
|
||||
expect(messages.every((dynamic message) => message['type'] == 'scroll'), isTrue);
|
||||
|
||||
Map<String, Object> message = messages.last['data'];
|
||||
expect(message['axis'], 'h');
|
||||
expect(message['pixels'], isPositive);
|
||||
expect(message['minScrollExtent'], 0.0);
|
||||
expect(message['maxScrollExtent'], 7200.0);
|
||||
|
||||
messages.clear();
|
||||
await flingRight(tester);
|
||||
|
||||
expect(messages, isNot(hasLength(0)));
|
||||
expect(messages.every((dynamic message) => message['type'] == 'scroll'), isTrue);
|
||||
|
||||
message = messages.last['data'];
|
||||
expect(message['axis'], 'h');
|
||||
expect(message['pixels'], isNonNegative);
|
||||
expect(message['minScrollExtent'], 0.0);
|
||||
expect(message['maxScrollExtent'], 7200.0);
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
@ -255,17 +315,17 @@ void main() {
|
||||
});
|
||||
}
|
||||
|
||||
Future<Null> flingUp(WidgetTester tester, { int repetitions: 1 }) async {
|
||||
while (repetitions-- > 0) {
|
||||
await tester.fling(find.byType(ListView), const Offset(0.0, -200.0), 1000.0);
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 5));
|
||||
}
|
||||
}
|
||||
Future<Null> flingUp(WidgetTester tester, { int repetitions: 1 }) => fling(tester, const Offset(0.0, -200.0), repetitions);
|
||||
|
||||
Future<Null> flingDown(WidgetTester tester, { int repetitions: 1 }) async {
|
||||
Future<Null> flingDown(WidgetTester tester, { int repetitions: 1 }) => fling(tester, const Offset(0.0, 200.0), repetitions);
|
||||
|
||||
Future<Null> flingRight(WidgetTester tester, { int repetitions: 1 }) => fling(tester, const Offset(200.0, 0.0), repetitions);
|
||||
|
||||
Future<Null> flingLeft(WidgetTester tester, { int repetitions: 1 }) => fling(tester, const Offset(-200.0, 0.0), repetitions);
|
||||
|
||||
Future<Null> fling(WidgetTester tester, Offset offset, int repetitions) async {
|
||||
while (repetitions-- > 0) {
|
||||
await tester.fling(find.byType(ListView), const Offset(0.0, 200.0), 1000.0);
|
||||
await tester.fling(find.byType(ListView), offset, 1000.0);
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 5));
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user