mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Make getOffsetToReveal work with nested Viewports (#26663)
Fixes #20893, the most-upvoted bug for framework. A detailed analysis explaining the cause of the bug has been posted at #20893 (comment).
This commit is contained in:
parent
7e22b5f2c5
commit
56039b4ba7
@ -57,9 +57,9 @@ abstract class RenderAbstractViewport extends RenderObject {
|
||||
/// edge of the viewport as possible. If `alignment` is 0.5, the child must be
|
||||
/// positioned as close to the center of the viewport as possible.
|
||||
///
|
||||
/// The target might not be a direct child of this viewport but it must be a
|
||||
/// descendant of the viewport and there must not be any other
|
||||
/// [RenderAbstractViewport] objects between the target and this object.
|
||||
/// The `target` might not be a direct child of this viewport but it must be a
|
||||
/// descendant of the viewport. Other viewports in between this viewport and
|
||||
/// the `target` will not be adjusted.
|
||||
///
|
||||
/// This method assumes that the content of the viewport moves linearly, i.e.
|
||||
/// when the offset of the viewport is changed by x then `target` also moves
|
||||
@ -585,26 +585,39 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
|
||||
|
||||
@override
|
||||
RevealedOffset getOffsetToReveal(RenderObject target, double alignment, {Rect rect}) {
|
||||
double leadingScrollOffset;
|
||||
double leadingScrollOffset = 0.0;
|
||||
double targetMainAxisExtent;
|
||||
RenderObject descendant;
|
||||
rect ??= target.paintBounds;
|
||||
|
||||
if (target is RenderBox) {
|
||||
final RenderBox targetBox = target;
|
||||
|
||||
// The pivot will be the topmost child before we hit a RenderSliver.
|
||||
RenderBox pivot = targetBox;
|
||||
while (pivot.parent is RenderBox)
|
||||
pivot = pivot.parent;
|
||||
// Starting at `target` and walking towards the root:
|
||||
// - `child` will be the last object before we reach this viewport, and
|
||||
// - `pivot` will be the last RenderBox before we reach this viewport.
|
||||
RenderObject child = target;
|
||||
RenderBox pivot;
|
||||
bool onlySlivers = target is RenderSliver; // ... between viewport and `target` (`target` included).
|
||||
while (child.parent != this) {
|
||||
assert(child.parent != null, '$target must be a descendant of $this');
|
||||
if (child is RenderBox) {
|
||||
pivot = child;
|
||||
}
|
||||
if (child.parent is RenderSliver) {
|
||||
final RenderSliver parent = child.parent;
|
||||
leadingScrollOffset += parent.childScrollOffset(child);
|
||||
} else {
|
||||
onlySlivers = false;
|
||||
leadingScrollOffset = 0.0;
|
||||
}
|
||||
child = child.parent;
|
||||
}
|
||||
|
||||
if (pivot != null) {
|
||||
assert(pivot.parent != null);
|
||||
assert(pivot.parent != this);
|
||||
assert(pivot != this);
|
||||
assert(pivot.parent is RenderSliver); // TODO(abarth): Support other kinds of render objects besides slivers.
|
||||
final RenderSliver pivotParent = pivot.parent;
|
||||
|
||||
final Matrix4 transform = targetBox.getTransformTo(pivot);
|
||||
final Matrix4 transform = target.getTransformTo(pivot);
|
||||
final Rect bounds = MatrixUtils.transformRect(transform, rect);
|
||||
|
||||
final GrowthDirection growthDirection = pivotParent.constraints.growthDirection;
|
||||
@ -619,15 +632,15 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
|
||||
offset = bounds.top;
|
||||
break;
|
||||
}
|
||||
leadingScrollOffset = pivot.size.height - offset;
|
||||
leadingScrollOffset += pivot.size.height - offset;
|
||||
targetMainAxisExtent = bounds.height;
|
||||
break;
|
||||
case AxisDirection.right:
|
||||
leadingScrollOffset = bounds.left;
|
||||
leadingScrollOffset += bounds.left;
|
||||
targetMainAxisExtent = bounds.width;
|
||||
break;
|
||||
case AxisDirection.down:
|
||||
leadingScrollOffset = bounds.top;
|
||||
leadingScrollOffset += bounds.top;
|
||||
targetMainAxisExtent = bounds.height;
|
||||
break;
|
||||
case AxisDirection.left:
|
||||
@ -640,28 +653,17 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
|
||||
offset = bounds.left;
|
||||
break;
|
||||
}
|
||||
leadingScrollOffset = pivot.size.width - offset;
|
||||
leadingScrollOffset += pivot.size.width - offset;
|
||||
targetMainAxisExtent = bounds.width;
|
||||
break;
|
||||
}
|
||||
descendant = pivot;
|
||||
} else if (target is RenderSliver) {
|
||||
} else if (onlySlivers) {
|
||||
final RenderSliver targetSliver = target;
|
||||
leadingScrollOffset = 0.0;
|
||||
targetMainAxisExtent = targetSliver.geometry.scrollExtent;
|
||||
descendant = targetSliver;
|
||||
} else {
|
||||
return RevealedOffset(offset: offset.pixels, rect: rect);
|
||||
}
|
||||
|
||||
// The child will be the topmost object before we get to the viewport.
|
||||
RenderObject child = descendant;
|
||||
while (child.parent is RenderSliver) {
|
||||
final RenderSliver parent = child.parent;
|
||||
leadingScrollOffset += parent.childScrollOffset(child);
|
||||
child = parent;
|
||||
}
|
||||
|
||||
assert(child.parent == this);
|
||||
assert(child is RenderSliver);
|
||||
final RenderSliver sliver = child;
|
||||
|
||||
@ -642,6 +642,118 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('Nested Viewports showOnScreen with allowImplicitScrolling=false for inner viewport', (WidgetTester tester) async {
|
||||
// Regression test for https://github.com/flutter/flutter/issues/20893.
|
||||
|
||||
List<Widget> slivers;
|
||||
final ScrollController controllerX = ScrollController(initialScrollOffset: 0.0);
|
||||
final ScrollController controllerY = ScrollController(initialScrollOffset: 0.0);
|
||||
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Center(
|
||||
child: Container(
|
||||
height: 200.0,
|
||||
width: 200.0,
|
||||
child: ListView(
|
||||
controller: controllerY,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
height: 150.0,
|
||||
),
|
||||
Container(
|
||||
height: 100.0,
|
||||
child: ListView(
|
||||
physics: const PageScrollPhysics(), // Turns off `allowImplicitScrolling`
|
||||
scrollDirection: Axis.horizontal,
|
||||
controller: controllerX,
|
||||
children: slivers = <Widget>[
|
||||
Container(
|
||||
width: 150.0,
|
||||
),
|
||||
Container(
|
||||
width: 150.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: 150.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
tester.renderObject(find.byWidget(slivers[1])).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(controllerX.offset, 0.0);
|
||||
expect(controllerY.offset, 50.0);
|
||||
});
|
||||
|
||||
testWidgets('Nested Viewports showOnScreen on Sliver with allowImplicitScrolling=false for inner viewport', (WidgetTester tester) async {
|
||||
Widget sliver;
|
||||
final ScrollController controllerX = ScrollController(initialScrollOffset: 0.0);
|
||||
final ScrollController controllerY = ScrollController(initialScrollOffset: 0.0);
|
||||
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Center(
|
||||
child: Container(
|
||||
height: 200.0,
|
||||
width: 200.0,
|
||||
child: ListView(
|
||||
controller: controllerY,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
height: 150.0,
|
||||
),
|
||||
Container(
|
||||
height: 100.0,
|
||||
child: CustomScrollView(
|
||||
physics: const PageScrollPhysics(), // Turns off `allowImplicitScrolling`
|
||||
scrollDirection: Axis.horizontal,
|
||||
controller: controllerX,
|
||||
slivers: <Widget>[
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.all(25.0),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Container(
|
||||
width: 100.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.all(25.0),
|
||||
sliver: sliver = SliverToBoxAdapter(
|
||||
child: Container(
|
||||
width: 100.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: 150.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
tester.renderObject(find.byWidget(sliver)).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(controllerX.offset, 0.0);
|
||||
expect(controllerY.offset, 25.0);
|
||||
});
|
||||
|
||||
testWidgets('Viewport showOnScreen with objects larger than viewport', (WidgetTester tester) async {
|
||||
List<Widget> children;
|
||||
ScrollController controller;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user