Unverified Commit 9e9a7286 authored by xubaolin's avatar xubaolin Committed by GitHub

Fix a [_ViewportElement] RenderObjectChild update bug (#96377)

parent 72df4801
...@@ -211,6 +211,9 @@ class _ViewportElement extends MultiChildRenderObjectElement { ...@@ -211,6 +211,9 @@ class _ViewportElement extends MultiChildRenderObjectElement {
/// Creates an element that uses the given widget as its configuration. /// Creates an element that uses the given widget as its configuration.
_ViewportElement(Viewport widget) : super(widget); _ViewportElement(Viewport widget) : super(widget);
bool _doingMountOrUpdate = false;
int? _centerSlotIndex;
@override @override
Viewport get widget => super.widget as Viewport; Viewport get widget => super.widget as Viewport;
...@@ -219,26 +222,67 @@ class _ViewportElement extends MultiChildRenderObjectElement { ...@@ -219,26 +222,67 @@ class _ViewportElement extends MultiChildRenderObjectElement {
@override @override
void mount(Element? parent, Object? newSlot) { void mount(Element? parent, Object? newSlot) {
assert(!_doingMountOrUpdate);
_doingMountOrUpdate = true;
super.mount(parent, newSlot); super.mount(parent, newSlot);
_updateCenter(); _updateCenter();
assert(_doingMountOrUpdate);
_doingMountOrUpdate = false;
} }
@override @override
void update(MultiChildRenderObjectWidget newWidget) { void update(MultiChildRenderObjectWidget newWidget) {
assert(!_doingMountOrUpdate);
_doingMountOrUpdate = true;
super.update(newWidget); super.update(newWidget);
_updateCenter(); _updateCenter();
assert(_doingMountOrUpdate);
_doingMountOrUpdate = false;
} }
void _updateCenter() { void _updateCenter() {
// TODO(ianh): cache the keys to make this faster // TODO(ianh): cache the keys to make this faster
if (widget.center != null) { if (widget.center != null) {
renderObject.center = children.singleWhere( int elementIndex = 0;
(Element element) => element.widget.key == widget.center, for (final Element e in children) {
).renderObject as RenderSliver?; if (e.widget.key == widget.center) {
renderObject.center = e.renderObject as RenderSliver?;
break;
}
elementIndex++;
}
assert(elementIndex < children.length);
_centerSlotIndex = elementIndex;
} else if (children.isNotEmpty) { } else if (children.isNotEmpty) {
renderObject.center = children.first.renderObject as RenderSliver?; renderObject.center = children.first.renderObject as RenderSliver?;
_centerSlotIndex = 0;
} else { } else {
renderObject.center = null; renderObject.center = null;
_centerSlotIndex = null;
}
}
@override
void insertRenderObjectChild(RenderObject child, IndexedSlot<Element?> slot) {
super.insertRenderObjectChild(child, slot);
// Once [mount]/[update] are done, the `renderObject.center` will be updated
// in [_updateCenter].
if (!_doingMountOrUpdate && slot.index == _centerSlotIndex) {
renderObject.center = child as RenderSliver?;
}
}
@override
void moveRenderObjectChild(RenderObject child, IndexedSlot<Element?> oldSlot, IndexedSlot<Element?> newSlot) {
super.moveRenderObjectChild(child, oldSlot, newSlot);
assert(_doingMountOrUpdate);
}
@override
void removeRenderObjectChild(RenderObject child, Object? slot) {
super.removeRenderObjectChild(child, slot);
if (!_doingMountOrUpdate && renderObject.center == child) {
renderObject.center = null;
} }
} }
......
...@@ -6,6 +6,81 @@ import 'package:flutter/widgets.dart'; ...@@ -6,6 +6,81 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
void main() { void main() {
// Regression test for https://github.com/flutter/flutter/issues/96024
testWidgets('CustomScrollView.center update test 1', (WidgetTester tester) async {
final Key centerKey = UniqueKey();
late StateSetter setState;
bool hasKey = false;
await tester.pumpWidget(Directionality(
textDirection: TextDirection.ltr,
child: CustomScrollView(
center: centerKey,
slivers: <Widget>[
const SliverToBoxAdapter(key: Key('a'), child: SizedBox(height: 100.0)),
StatefulBuilder(
key: centerKey,
builder: (BuildContext context, StateSetter setter) {
setState = setter;
if (hasKey) {
return const SliverToBoxAdapter(
key: Key('b'),
child: SizedBox(height: 100.0),
);
} else {
return const SliverToBoxAdapter(
child: SizedBox(height: 100.0),
);
}
},
),
],
),
));
await tester.pumpAndSettle();
// Change the center key will trigger the old RenderObject remove and a new
// RenderObject insert.
setState(() {
hasKey = true;
});
await tester.pumpAndSettle();
// Pass without throw.
});
testWidgets('CustomScrollView.center update test 2', (WidgetTester tester) async {
const List<Widget> slivers1 = <Widget>[
SliverToBoxAdapter(key: Key('a'), child: SizedBox(height: 100.0)),
SliverToBoxAdapter(key: Key('b'), child: SizedBox(height: 100.0)),
SliverToBoxAdapter(key: Key('c'), child: SizedBox(height: 100.0)),
];
const List<Widget> slivers2 = <Widget>[
SliverToBoxAdapter(key: Key('c'), child: SizedBox(height: 100.0)),
SliverToBoxAdapter(key: Key('d'), child: SizedBox(height: 100.0)),
SliverToBoxAdapter(key: Key('a'), child: SizedBox(height: 100.0)),
];
Widget buildFrame(List<Widget> slivers, Key center) {
return Directionality(
textDirection: TextDirection.ltr,
child: CustomScrollView(
center: center,
slivers: slivers,
),
);
}
await tester.pumpWidget(buildFrame(slivers1, const Key('b')));
await tester.pumpAndSettle();
await tester.pumpWidget(buildFrame(slivers2, const Key('d')));
await tester.pumpAndSettle();
// Pass without throw.
});
testWidgets('CustomScrollView.center', (WidgetTester tester) async { testWidgets('CustomScrollView.center', (WidgetTester tester) async {
await tester.pumpWidget(const Directionality( await tester.pumpWidget(const Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
......
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