Skip to content

Commit a30d816

Browse files
fix stretch effect with rtl support (#113214)
1 parent e76f883 commit a30d816

File tree

2 files changed

+139
-8
lines changed

2 files changed

+139
-8
lines changed

packages/flutter/lib/src/widgets/overscroll_indicator.dart

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -744,7 +744,7 @@ class _StretchingOverscrollIndicatorState extends State<StretchingOverscrollIndi
744744
return false;
745745
}
746746

747-
AlignmentDirectional _getAlignmentForAxisDirection(double overscroll) {
747+
AlignmentGeometry _getAlignmentForAxisDirection(double overscroll) {
748748
// Accounts for reversed scrollables by checking the AxisDirection
749749
switch (widget.axisDirection) {
750750
case AxisDirection.up:
@@ -753,16 +753,16 @@ class _StretchingOverscrollIndicatorState extends State<StretchingOverscrollIndi
753753
: AlignmentDirectional.bottomCenter;
754754
case AxisDirection.right:
755755
return overscroll > 0
756-
? AlignmentDirectional.centerEnd
757-
: AlignmentDirectional.centerStart;
756+
? Alignment.centerRight
757+
: Alignment.centerLeft;
758758
case AxisDirection.down:
759759
return overscroll > 0
760760
? AlignmentDirectional.bottomCenter
761761
: AlignmentDirectional.topCenter;
762762
case AxisDirection.left:
763763
return overscroll > 0
764-
? AlignmentDirectional.centerStart
765-
: AlignmentDirectional.centerEnd;
764+
? Alignment.centerLeft
765+
: Alignment.centerRight;
766766
}
767767
}
768768

@@ -796,7 +796,7 @@ class _StretchingOverscrollIndicatorState extends State<StretchingOverscrollIndi
796796
break;
797797
}
798798

799-
final AlignmentDirectional alignment = _getAlignmentForAxisDirection(
799+
final AlignmentGeometry alignment = _getAlignmentForAxisDirection(
800800
_lastOverscrollNotification?.overscroll ?? 0.0
801801
);
802802

packages/flutter/test/widgets/overscroll_stretch_indicator_test.dart

Lines changed: 133 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,24 @@ void main() {
1818
ScrollController controller, {
1919
Axis axis = Axis.vertical,
2020
bool reverse = false,
21+
TextDirection textDirection = TextDirection.ltr,
2122
}) {
2223
final AxisDirection axisDirection;
2324
switch (axis) {
2425
case Axis.horizontal:
25-
axisDirection = reverse ? AxisDirection.left : AxisDirection.right;
26+
if (textDirection == TextDirection.rtl) {
27+
axisDirection = reverse ? AxisDirection.right : AxisDirection.left;
28+
} else {
29+
axisDirection = reverse ? AxisDirection.left : AxisDirection.right;
30+
}
2631
break;
2732
case Axis.vertical:
2833
axisDirection = reverse ? AxisDirection.up : AxisDirection.down;
2934
break;
3035
}
3136

3237
return Directionality(
33-
textDirection: TextDirection.ltr,
38+
textDirection: textDirection,
3439
child: MediaQuery(
3540
data: const MediaQueryData(size: Size(800.0, 600.0)),
3641
child: ScrollConfiguration(
@@ -218,6 +223,92 @@ void main() {
218223
);
219224
});
220225

226+
testWidgets('Stretch overscroll works in reverse - horizontal - RTL', (WidgetTester tester) async {
227+
final GlobalKey box1Key = GlobalKey();
228+
final GlobalKey box2Key = GlobalKey();
229+
final GlobalKey box3Key = GlobalKey();
230+
final ScrollController controller = ScrollController();
231+
await tester.pumpWidget(
232+
buildTest(
233+
box1Key,
234+
box2Key,
235+
box3Key,
236+
controller,
237+
axis: Axis.horizontal,
238+
reverse: true,
239+
textDirection: TextDirection.rtl,
240+
)
241+
);
242+
243+
expect(find.byType(StretchingOverscrollIndicator), findsOneWidget);
244+
expect(find.byType(GlowingOverscrollIndicator), findsNothing);
245+
final RenderBox box1 = tester.renderObject(find.byKey(box1Key));
246+
final RenderBox box2 = tester.renderObject(find.byKey(box2Key));
247+
final RenderBox box3 = tester.renderObject(find.byKey(box3Key));
248+
249+
expect(controller.offset, 0.0);
250+
expect(box1.localToGlobal(Offset.zero), Offset.zero);
251+
expect(box2.localToGlobal(Offset.zero), const Offset(300.0, 0.0));
252+
expect(box3.localToGlobal(Offset.zero), const Offset(600.0, 0.0));
253+
await expectLater(
254+
find.byType(CustomScrollView),
255+
matchesGoldenFile('overscroll_stretch.horizontal.reverse.rtl.start.png'),
256+
);
257+
258+
TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(CustomScrollView)));
259+
// Overscroll the start
260+
await gesture.moveBy(const Offset(200.0, 0.0));
261+
await tester.pumpAndSettle();
262+
263+
expect(box1.localToGlobal(Offset.zero), Offset.zero);
264+
expect(box2.localToGlobal(Offset.zero).dx, greaterThan(305.0));
265+
expect(box3.localToGlobal(Offset.zero).dx, greaterThan(610.0));
266+
await expectLater(
267+
find.byType(CustomScrollView),
268+
matchesGoldenFile('overscroll_stretch.horizontal.reverse.rtl.start.stretched.png'),
269+
);
270+
271+
await gesture.up();
272+
await tester.pumpAndSettle();
273+
274+
// Stretch released back to the start
275+
expect(box1.localToGlobal(Offset.zero), Offset.zero);
276+
expect(box2.localToGlobal(Offset.zero), const Offset(300.0, 0.0));
277+
expect(box3.localToGlobal(Offset.zero), const Offset(600.0, 0.0));
278+
279+
// Jump to end of the list
280+
controller.jumpTo(controller.position.maxScrollExtent);
281+
await tester.pumpAndSettle();
282+
expect(controller.offset, 100.0);
283+
expect(box1.localToGlobal(Offset.zero).dx, -100.0);
284+
expect(box2.localToGlobal(Offset.zero).dx, 200.0);
285+
expect(box3.localToGlobal(Offset.zero).dx, 500.0);
286+
await expectLater(
287+
find.byType(CustomScrollView),
288+
matchesGoldenFile('overscroll_stretch.horizontal.reverse.rtl.end.png'),
289+
);
290+
291+
gesture = await tester.startGesture(tester.getCenter(find.byType(CustomScrollView)));
292+
// Overscroll the end
293+
await gesture.moveBy(const Offset(-200.0, 0.0));
294+
await tester.pumpAndSettle();
295+
expect(box1.localToGlobal(Offset.zero).dx, lessThan(-116.0));
296+
expect(box2.localToGlobal(Offset.zero).dx, lessThan(190.0));
297+
expect(box3.localToGlobal(Offset.zero).dx, lessThan(500.0));
298+
await expectLater(
299+
find.byType(CustomScrollView),
300+
matchesGoldenFile('overscroll_stretch.horizontal.reverse.rtl.end.stretched.png'),
301+
);
302+
303+
await gesture.up();
304+
await tester.pumpAndSettle();
305+
306+
// Stretch released back
307+
expect(box1.localToGlobal(Offset.zero).dx, -100.0);
308+
expect(box2.localToGlobal(Offset.zero).dx, 200.0);
309+
expect(box3.localToGlobal(Offset.zero).dx, 500.0);
310+
});
311+
221312
testWidgets('Stretch overscroll horizontally', (WidgetTester tester) async {
222313
final GlobalKey box1Key = GlobalKey();
223314
final GlobalKey box2Key = GlobalKey();
@@ -295,6 +386,46 @@ void main() {
295386
expect(box3.localToGlobal(Offset.zero).dx, 500.0);
296387
});
297388

389+
testWidgets('Stretch overscroll horizontally RTl', (WidgetTester tester) async {
390+
final GlobalKey box1Key = GlobalKey();
391+
final GlobalKey box2Key = GlobalKey();
392+
final GlobalKey box3Key = GlobalKey();
393+
final ScrollController controller = ScrollController();
394+
await tester.pumpWidget(
395+
buildTest(
396+
box1Key,
397+
box2Key,
398+
box3Key,
399+
controller,
400+
axis: Axis.horizontal,
401+
textDirection: TextDirection.rtl,
402+
)
403+
);
404+
405+
expect(find.byType(StretchingOverscrollIndicator), findsOneWidget);
406+
expect(find.byType(GlowingOverscrollIndicator), findsNothing);
407+
final RenderBox box1 = tester.renderObject(find.byKey(box1Key));
408+
final RenderBox box2 = tester.renderObject(find.byKey(box2Key));
409+
final RenderBox box3 = tester.renderObject(find.byKey(box3Key));
410+
411+
expect(controller.offset, 0.0);
412+
expect(box1.localToGlobal(Offset.zero), const Offset(500.0, 0.0));
413+
expect(box2.localToGlobal(Offset.zero), const Offset(200.0, 0.0));
414+
expect(box3.localToGlobal(Offset.zero), const Offset(-100.0, 0.0));
415+
416+
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(CustomScrollView)));
417+
// Overscroll
418+
await gesture.moveBy(const Offset(-200.0, 0.0));
419+
await tester.pumpAndSettle();
420+
expect(box1.localToGlobal(Offset.zero).dx, lessThan(500.0));
421+
expect(box2.localToGlobal(Offset.zero).dx, lessThan(200.0));
422+
expect(box3.localToGlobal(Offset.zero).dx, lessThan(-100.0));
423+
await expectLater(
424+
find.byType(CustomScrollView),
425+
matchesGoldenFile('overscroll_stretch.horizontal.rtl.png'),
426+
);
427+
});
428+
298429
testWidgets('Disallow stretching overscroll', (WidgetTester tester) async {
299430
final GlobalKey box1Key = GlobalKey();
300431
final GlobalKey box2Key = GlobalKey();

0 commit comments

Comments
 (0)