Fix update order of SliverAppBar (#158159)
Fixes #158158 Similar to 154484, but addressing more fundamental causes. We should call the child's `didUpdateWidget` before the child's `build` when states change. 1. Remove redundant code but keep the old test because we don't need it after this fix. 2. Add getters of `lastShrinkOffset` and `lastOverlapsContent` that are needed when `SliverAppBar.update`. 3. Add a test with a custom TabBar to avoid breaking when some changes to the default TabBar.
This commit is contained in:
parent
1a31e396db
commit
5f0f18d739
@ -1772,11 +1772,9 @@ class _TabBarState extends State<TabBar> {
|
||||
wrappedTabs[previousIndex] = _buildStyledTab(wrappedTabs[previousIndex], false, animation, _defaults);
|
||||
} else {
|
||||
// The user is dragging the TabBarView's PageView left or right.
|
||||
if (_currentIndex! < widget.tabs.length) {
|
||||
final int tabIndex = _currentIndex!;
|
||||
final Animation<double> centerAnimation = _DragAnimation(_controller!, tabIndex);
|
||||
wrappedTabs[tabIndex] = _buildStyledTab(wrappedTabs[tabIndex], true, centerAnimation, _defaults);
|
||||
}
|
||||
final int tabIndex = _currentIndex!;
|
||||
final Animation<double> centerAnimation = _DragAnimation(_controller!, tabIndex);
|
||||
wrappedTabs[tabIndex] = _buildStyledTab(wrappedTabs[tabIndex], true, centerAnimation, _defaults);
|
||||
if (_currentIndex! > 0) {
|
||||
final int tabIndex = _currentIndex! - 1;
|
||||
final Animation<double> previousAnimation = ReverseAnimation(_DragAnimation(_controller!, tabIndex));
|
||||
|
@ -162,7 +162,13 @@ abstract class RenderSliverPersistentHeader extends RenderSliver with RenderObje
|
||||
}
|
||||
|
||||
bool _needsUpdateChild = true;
|
||||
|
||||
/// The most recent `shrinkOffset` passed to [updateChild].
|
||||
double get lastShrinkOffset => _lastShrinkOffset;
|
||||
double _lastShrinkOffset = 0.0;
|
||||
|
||||
/// The most recent `overlapsContent` passed to [updateChild].
|
||||
bool get lastOverlapsContent => _lastOverlapsContent;
|
||||
bool _lastOverlapsContent = false;
|
||||
|
||||
/// Defines the parameters used to execute an [AsyncCallback] when a
|
||||
|
@ -286,6 +286,8 @@ class _SliverPersistentHeaderElement extends RenderObjectElement {
|
||||
final SliverPersistentHeaderDelegate oldDelegate = oldWidget.delegate;
|
||||
if (newDelegate != oldDelegate &&
|
||||
(newDelegate.runtimeType != oldDelegate.runtimeType || newDelegate.shouldRebuild(oldDelegate))) {
|
||||
final _RenderSliverPersistentHeaderForWidgetsMixin renderObject = this.renderObject;
|
||||
_updateChild(newDelegate, renderObject.lastShrinkOffset, renderObject.lastOverlapsContent);
|
||||
renderObject.triggerRebuild();
|
||||
}
|
||||
}
|
||||
@ -298,20 +300,19 @@ class _SliverPersistentHeaderElement extends RenderObjectElement {
|
||||
|
||||
Element? child;
|
||||
|
||||
void _updateChild(SliverPersistentHeaderDelegate delegate, double shrinkOffset, bool overlapsContent) {
|
||||
final Widget newWidget = delegate.build(this, shrinkOffset, overlapsContent);
|
||||
child = updateChild(
|
||||
child,
|
||||
floating ? _FloatingHeader(child: newWidget) : newWidget,
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
void _build(double shrinkOffset, bool overlapsContent) {
|
||||
owner!.buildScope(this, () {
|
||||
final _SliverPersistentHeaderRenderObjectWidget sliverPersistentHeaderRenderObjectWidget = widget as _SliverPersistentHeaderRenderObjectWidget;
|
||||
child = updateChild(
|
||||
child,
|
||||
floating
|
||||
? _FloatingHeader(child: sliverPersistentHeaderRenderObjectWidget.delegate.build(
|
||||
this,
|
||||
shrinkOffset,
|
||||
overlapsContent
|
||||
))
|
||||
: sliverPersistentHeaderRenderObjectWidget.delegate.build(this, shrinkOffset, overlapsContent),
|
||||
null,
|
||||
);
|
||||
_updateChild(sliverPersistentHeaderRenderObjectWidget.delegate, shrinkOffset, overlapsContent);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -2274,6 +2274,53 @@ void main() {
|
||||
expect(navToolBar.middleSpacing, NavigationToolbar.kMiddleSpacing);
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/158158.
|
||||
testWidgets('SliverAppBar should update TabBar before TabBar build', (WidgetTester tester) async {
|
||||
final List<Tab> tabs = <Tab>[];
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: StatefulBuilder(
|
||||
builder: (BuildContext context, StateSetter setState) {
|
||||
return DefaultTabController(
|
||||
length: tabs.length,
|
||||
child: Scaffold(
|
||||
body: CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
SliverAppBar(
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: const Text('Add Tab'),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
tabs.add(Tab(text: 'Tab ${tabs.length + 1}'));
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
bottom: TabBar(tabs: tabs),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Initializes with zero tabs.
|
||||
expect(find.text('Tab 1'), findsNothing);
|
||||
expect(find.text('Tab 2'), findsNothing);
|
||||
|
||||
// No crash after tabs added.
|
||||
await tester.tap(find.text('Add Tab'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Tab 1'), findsOneWidget);
|
||||
expect(find.text('Tab 2'), findsNothing);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
group('Material 2', () {
|
||||
// These tests are only relevant for Material 2. Once Material 2
|
||||
// support is deprecated and the APIs are removed, these tests
|
||||
|
Loading…
x
Reference in New Issue
Block a user