Commit 26707862 authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

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
parent 31fe65e2
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 }) => 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> flingDown(WidgetTester tester, { int repetitions: 1 }) async {
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));
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment