Unverified Commit 93a1484a authored by chunhtai's avatar chunhtai Committed by GitHub

Add a hook for scroll position to notify scrolling context when dimen… (#87076)

parent 02bb8a43
......@@ -525,7 +525,6 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin, R
_gestureDetectorKey.currentState!.replaceSemanticsActions(actions);
}
// GESTURE RECOGNITION AND POINTER IGNORING
final GlobalKey<RawGestureDetectorState> _gestureDetectorKey = GlobalKey<RawGestureDetectorState>();
......@@ -719,6 +718,15 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin, R
}
}
bool _handleScrollMetricsNotification(ScrollMetricsNotification notification) {
if (notification.depth == 0) {
final RenderObject? scrollSemanticsRenderObject = _scrollSemanticsKey.currentContext?.findRenderObject();
if (scrollSemanticsRenderObject != null)
scrollSemanticsRenderObject.markNeedsSemanticsUpdate();
}
return false;
}
// DESCRIPTION
@override
......@@ -757,12 +765,15 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin, R
);
if (!widget.excludeFromSemantics) {
result = _ScrollSemantics(
key: _scrollSemanticsKey,
position: position,
allowImplicitScrolling: _physics!.allowImplicitScrolling,
semanticChildCount: widget.semanticChildCount,
child: result,
result = NotificationListener<ScrollMetricsNotification>(
onNotification: _handleScrollMetricsNotification,
child: _ScrollSemantics(
key: _scrollSemanticsKey,
position: position,
allowImplicitScrolling: _physics!.allowImplicitScrolling,
semanticChildCount: widget.semanticChildCount,
child: result,
)
);
}
......
......@@ -787,7 +787,7 @@ void main() {
));
expect(controller.page, 0);
controller.jumpToPage(2);
expect(await tester.pumpAndSettle(const Duration(minutes: 1)), 1);
expect(await tester.pumpAndSettle(const Duration(minutes: 1)), 2);
expect(controller.page, 2);
await tester.pumpWidget(
PageStorage(
......
......@@ -122,7 +122,7 @@ void main() {
final TestGesture drag1 = await tester.startGesture(const Offset(10.0, 500.0));
expect(await tester.pumpAndSettle(), 1); // Nothing to animate
await drag1.moveTo(const Offset(10.0, 0.0));
expect(await tester.pumpAndSettle(), 1); // Nothing to animate
expect(await tester.pumpAndSettle(), 2); // Nothing to animate, only one semantics update
await drag1.up();
expect(await tester.pumpAndSettle(), 1); // Nothing to animate
expect(position.pixels, moreOrLessEquals(500.0));
......@@ -132,14 +132,14 @@ void main() {
final TestGesture drag2 = await tester.startGesture(const Offset(10.0, 500.0));
expect(await tester.pumpAndSettle(), 1); // Nothing to animate
await drag2.moveTo(const Offset(10.0, 100.0));
expect(await tester.pumpAndSettle(), 1); // Nothing to animate
expect(await tester.pumpAndSettle(), 2); // Nothing to animate, only one semantics update
expect(position.maxScrollExtent, 900.0);
expect(position.pixels, lessThanOrEqualTo(900.0));
expect(position.activity, isInstanceOf<DragScrollActivity>());
final _ExpandingBoxState expandingBoxState = tester.state<_ExpandingBoxState>(find.byType(ExpandingBox));
expandingBoxState.toggleSize();
expect(await tester.pumpAndSettle(), 1); // Nothing to animate
expect(await tester.pumpAndSettle(), 2); // Nothing to animate, only one semantics update
expect(position.activity, isInstanceOf<DragScrollActivity>());
expect(position.minScrollExtent, 0.0);
expect(position.maxScrollExtent, 100.0);
......@@ -150,7 +150,7 @@ void main() {
expect(position.minScrollExtent, 0.0);
expect(position.maxScrollExtent, 100.0);
expect(position.pixels, 50.0);
expect(await tester.pumpAndSettle(), 1); // Nothing to animate
expect(await tester.pumpAndSettle(), 2); // Nothing to animate, only one semantics update
expect(position.minScrollExtent, 0.0);
expect(position.maxScrollExtent, 100.0);
expect(position.pixels, 50.0);
......
......@@ -23,7 +23,7 @@ void main() {
await tester.pump();
await tester.pumpWidget(MaterialApp(home: ListView(controller: controller, children: children(31))));
expect(controller.position.pixels, thirty + 200.0); // same distance past the end
expect(await tester.pumpAndSettle(), 7); // now it goes ballistic...
expect(await tester.pumpAndSettle(), 8); // now it goes ballistic...
expect(controller.position.pixels, thirty + 100.0); // and ends up at the end
});
......
......@@ -446,7 +446,6 @@ void main() {
expect(find.text('Tile 12'), findsNothing);
final TestRestorationData initialData = await tester.getRestorationData();
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(ListView)));
await gesture.moveBy(const Offset(0, -525));
await tester.pump();
......@@ -457,12 +456,12 @@ void main() {
expect(find.text('Tile 11'), findsOneWidget);
expect(find.text('Tile 12'), findsOneWidget);
// Restoration data hasn't changed and no frame is scheduled.
// Restoration data hasn't changed.
expect(await tester.getRestorationData(), initialData);
expect(tester.binding.hasScheduledFrame, isFalse);
// Restoration data changes with up event.
await gesture.up();
await tester.pump();
expect(await tester.getRestorationData(), isNot(initialData));
});
}
......
......@@ -152,7 +152,7 @@ void main() {
expect(getScrollOffset(tester), heldPosition);
await gesture.up();
// Once the hold is let go, it should still snap back to origin.
expect(await tester.pumpAndSettle(const Duration(minutes: 1)), 2);
expect(await tester.pumpAndSettle(const Duration(minutes: 1)), 3);
expect(getScrollOffset(tester), 0.0);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
......@@ -1313,6 +1313,64 @@ void main() {
await gesture.removePointer();
await tester.pump();
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS, TargetPlatform.android }));
testWidgets('Updated content dimensions correctly reflect in semantics', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/40419.
final SemanticsHandle handle = tester.ensureSemantics();
final UniqueKey listView = UniqueKey();
await tester.pumpWidget(MaterialApp(
home: TickerMode(
enabled: true,
child: ListView.builder(
key: listView,
itemCount: 100,
itemBuilder: (BuildContext context, int index) {
return Text('Item $index');
},
),
),
));
SemanticsNode scrollableNode = tester.getSemantics(find.descendant(of: find.byKey(listView), matching: find.byType(RawGestureDetector)));
SemanticsNode? syntheticScrollableNode;
scrollableNode.visitChildren((SemanticsNode node) {
syntheticScrollableNode = node;
return true;
});
expect(syntheticScrollableNode!.hasFlag(ui.SemanticsFlag.hasImplicitScrolling), isTrue);
// Disabled the ticker mode to trigger didChangeDependencies on Scrollable.
// This can happen when a route is push or pop from top.
// It will reconstruct the scroll position and apply content dimensions.
await tester.pumpWidget(MaterialApp(
home: TickerMode(
enabled: false,
child: ListView.builder(
key: listView,
itemCount: 100,
itemBuilder: (BuildContext context, int index) {
return Text('Item $index');
},
),
),
));
await tester.pump();
// The correct workflow will be the following:
// 1. _RenderScrollSemantics receives a new scroll position without content
// dimensions and creates a SemanticsNode without implicit scroll.
// 2. The content dimensions are applied to the scroll position during the
// layout phase, and the scroll position marks the semantics node of
// _RenderScrollSemantics dirty.
// 3. The _RenderScrollSemantics rebuilds its semantics node with implicit
// scroll.
scrollableNode = tester.getSemantics(find.descendant(of: find.byKey(listView), matching: find.byType(RawGestureDetector)));
syntheticScrollableNode = null;
scrollableNode.visitChildren((SemanticsNode node) {
syntheticScrollableNode = node;
return true;
});
expect(syntheticScrollableNode!.hasFlag(ui.SemanticsFlag.hasImplicitScrolling), isTrue);
handle.dispose();
});
}
// ignore: must_be_immutable
......
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