Do not implicitly scroll a PageView when showOnScreen is called (#19735)
Fixes #19523
This commit is contained in:
parent
1b5dd718d8
commit
4509ad5978
@ -877,6 +877,15 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
|
||||
Duration duration = Duration.zero,
|
||||
Curve curve = Curves.ease,
|
||||
}) {
|
||||
if (!offset.allowImplicitScrolling) {
|
||||
return super.showOnScreen(
|
||||
descendant: descendant,
|
||||
rect: rect,
|
||||
duration: duration,
|
||||
curve: curve,
|
||||
);
|
||||
}
|
||||
|
||||
final Rect newRect = RenderViewportBase.showInViewport(
|
||||
descendant: descendant,
|
||||
viewport: this,
|
||||
|
@ -195,6 +195,15 @@ abstract class ViewportOffset extends ChangeNotifier {
|
||||
/// offset direction.
|
||||
ScrollDirection get userScrollDirection;
|
||||
|
||||
/// Whether a viewport is allowed to change [pixels] implicitly to respond to
|
||||
/// a call to [RenderObject.showOnScreen].
|
||||
///
|
||||
/// [RenderObject.showOnScreen] is for example used to bring a text field
|
||||
/// fully on screen after it has received focus. This property controls
|
||||
/// whether the viewport associated with this offset is allowed to change the
|
||||
/// offset's [pixels] value to fulfill such a request.
|
||||
bool get allowImplicitScrolling;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final List<String> description = <String>[];
|
||||
@ -250,4 +259,7 @@ class _FixedViewportOffset extends ViewportOffset {
|
||||
|
||||
@override
|
||||
ScrollDirection get userScrollDirection => ScrollDirection.idle;
|
||||
|
||||
@override
|
||||
bool get allowImplicitScrolling => false;
|
||||
}
|
||||
|
@ -372,6 +372,9 @@ class PageScrollPhysics extends ScrollPhysics {
|
||||
return new ScrollSpringSimulation(spring, position.pixels, target, velocity, tolerance: tolerance);
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
bool get allowImplicitScrolling => false;
|
||||
}
|
||||
|
||||
// Having this global (mutable) page controller is a bit of a hack. We need it
|
||||
|
@ -225,6 +225,15 @@ class ScrollPhysics {
|
||||
/// If null, no minimum threshold is enforced.
|
||||
double get dragStartDistanceMotionThreshold => parent?.dragStartDistanceMotionThreshold;
|
||||
|
||||
/// Whether a viewport is allowed to change its scroll position implicitly in
|
||||
/// responds to a call to [RenderObject.showOnScreen].
|
||||
///
|
||||
/// [RenderObject.showOnScreen] is for example used to bring a text field
|
||||
/// fully on screen after it has received focus. This property controls
|
||||
/// whether the viewport associated with this object is allowed to change the
|
||||
/// scroll position to fulfill such a request.
|
||||
bool get allowImplicitScrolling => true;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
if (parent == null)
|
||||
@ -485,4 +494,7 @@ class NeverScrollableScrollPhysics extends ScrollPhysics {
|
||||
|
||||
@override
|
||||
bool shouldAcceptUserOffset(ScrollMetrics position) => false;
|
||||
|
||||
@override
|
||||
bool get allowImplicitScrolling => false;
|
||||
}
|
||||
|
@ -562,6 +562,9 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
|
||||
@override
|
||||
void jumpTo(double value);
|
||||
|
||||
@override
|
||||
bool get allowImplicitScrolling => physics.allowImplicitScrolling;
|
||||
|
||||
/// Deprecated. Use [jumpTo] or a custom [ScrollPosition] instead.
|
||||
@Deprecated('This will lead to bugs.')
|
||||
void jumpToWithoutSettling(double value);
|
||||
|
@ -596,6 +596,15 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix
|
||||
Duration duration = Duration.zero,
|
||||
Curve curve = Curves.ease,
|
||||
}) {
|
||||
if (!offset.allowImplicitScrolling) {
|
||||
return super.showOnScreen(
|
||||
descendant: descendant,
|
||||
rect: rect,
|
||||
duration: duration,
|
||||
curve: curve,
|
||||
);
|
||||
}
|
||||
|
||||
final Rect newRect = RenderViewportBase.showInViewport(
|
||||
descendant: descendant,
|
||||
viewport: this,
|
||||
|
@ -142,6 +142,97 @@ void main() {
|
||||
expect(find.byType(EditableText), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('entering text does not scroll when scrollPhysics.allowImplicitScrolling = false', (WidgetTester tester) async {
|
||||
// regression test for https://github.com/flutter/flutter/issues/19523
|
||||
|
||||
final ScrollController scrollController = new ScrollController(initialScrollOffset: 100.0);
|
||||
final TextEditingController controller = new TextEditingController();
|
||||
final FocusNode focusNode = new FocusNode();
|
||||
|
||||
await tester.pumpWidget(new MaterialApp(
|
||||
home: new Center(
|
||||
child: new Container(
|
||||
height: 300.0,
|
||||
child: new ListView(
|
||||
physics: const NoImplicitScrollPhysics(),
|
||||
controller: scrollController,
|
||||
children: <Widget>[
|
||||
new Container(
|
||||
height: 350.0,
|
||||
),
|
||||
new EditableText(
|
||||
controller: controller,
|
||||
focusNode: focusNode,
|
||||
style: textStyle,
|
||||
cursorColor: cursorColor,
|
||||
),
|
||||
new Container(
|
||||
height: 350.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
// Focus the EditableText and scroll it off screen.
|
||||
await tester.showKeyboard(find.byType(EditableText));
|
||||
await tester.pumpAndSettle();
|
||||
expect(focusNode.hasFocus, isTrue);
|
||||
scrollController.jumpTo(0.0);
|
||||
await tester.pumpAndSettle();
|
||||
expect(scrollController.offset, 0.0);
|
||||
expect(find.byType(EditableText), findsNothing);
|
||||
|
||||
// Entering text brings it not back on screen.
|
||||
tester.testTextInput.enterText('Hello');
|
||||
await tester.pumpAndSettle();
|
||||
expect(scrollController.offset, 0.0);
|
||||
expect(find.byType(EditableText), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('entering text does not scroll a sourrounding PageView', (WidgetTester tester) async {
|
||||
// regression test for https://github.com/flutter/flutter/issues/19523
|
||||
|
||||
final TextEditingController textController = new TextEditingController();
|
||||
final PageController pageController = new PageController(initialPage: 1);
|
||||
|
||||
await tester.pumpWidget(new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Material(
|
||||
child: new PageView(
|
||||
controller: pageController,
|
||||
children: <Widget>[
|
||||
new Container(
|
||||
color: Colors.red,
|
||||
),
|
||||
new Container(
|
||||
child: new TextField(
|
||||
controller: textController,
|
||||
),
|
||||
color: Colors.green,
|
||||
),
|
||||
new Container(
|
||||
color: Colors.red,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
await tester.showKeyboard(find.byType(EditableText));
|
||||
await tester.pumpAndSettle();
|
||||
expect(textController.text, '');
|
||||
tester.testTextInput.enterText('H');
|
||||
final int frames = await tester.pumpAndSettle();
|
||||
|
||||
// The text input should not trigger any animations, which would indicate
|
||||
// that the surrounding PageView is incorrectly scrolling back-and-forth.
|
||||
expect(frames, 1);
|
||||
|
||||
expect(textController.text, 'H');
|
||||
});
|
||||
|
||||
testWidgets('focused multi-line editable scrolls caret back into view when typing', (WidgetTester tester) async {
|
||||
final ScrollController scrollController = new ScrollController();
|
||||
final TextEditingController controller = new TextEditingController();
|
||||
@ -234,3 +325,15 @@ void main() {
|
||||
expect(find.byKey(container), findsNothing);
|
||||
});
|
||||
}
|
||||
|
||||
class NoImplicitScrollPhysics extends AlwaysScrollableScrollPhysics {
|
||||
const NoImplicitScrollPhysics({ ScrollPhysics parent }) : super(parent: parent);
|
||||
|
||||
@override
|
||||
bool get allowImplicitScrolling => false;
|
||||
|
||||
@override
|
||||
NoImplicitScrollPhysics applyTo(ScrollPhysics ancestor) {
|
||||
return new NoImplicitScrollPhysics(parent: buildParent(ancestor));
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user