Skip to content

Commit

Permalink
Reland fix 25807 implement move for sliver multibox widget (flutter#3…
Browse files Browse the repository at this point in the history
  • Loading branch information
chunhtai authored May 3, 2019
1 parent 39d660b commit 38808d9
Show file tree
Hide file tree
Showing 14 changed files with 1,125 additions and 67 deletions.
38 changes: 23 additions & 15 deletions packages/flutter/lib/src/material/tabs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1125,6 +1125,7 @@ class _TabBarViewState extends State<TabBarView> {
TabController _controller;
PageController _pageController;
List<Widget> _children;
List<Widget> _childrenWithKey;
int _currentIndex;
int _warpUnderwayCount = 0;

Expand Down Expand Up @@ -1156,7 +1157,7 @@ class _TabBarViewState extends State<TabBarView> {
@override
void initState() {
super.initState();
_children = widget.children;
_updateChildren();
}

@override
Expand All @@ -1173,7 +1174,7 @@ class _TabBarViewState extends State<TabBarView> {
if (widget.controller != oldWidget.controller)
_updateTabController();
if (widget.children != oldWidget.children && _warpUnderwayCount == 0)
_children = widget.children;
_updateChildren();
}

@override
Expand All @@ -1184,6 +1185,11 @@ class _TabBarViewState extends State<TabBarView> {
super.dispose();
}

void _updateChildren() {
_children = widget.children;
_childrenWithKey = KeyedSubtree.ensureUniqueKeysForList(widget.children);
}

void _handleTabControllerAnimationTick() {
if (_warpUnderwayCount > 0 || !_controller.indexIsChanging)
return; // This widget is driving the controller's animation.
Expand All @@ -1206,28 +1212,30 @@ class _TabBarViewState extends State<TabBarView> {
return _pageController.animateToPage(_currentIndex, duration: kTabScrollDuration, curve: Curves.ease);

assert((_currentIndex - previousIndex).abs() > 1);
int initialPage;
final int initialPage = _currentIndex > previousIndex
? _currentIndex - 1
: _currentIndex + 1;
final List<Widget> originalChildren = _childrenWithKey;
setState(() {
_warpUnderwayCount += 1;
_children = List<Widget>.from(widget.children, growable: false);
if (_currentIndex > previousIndex) {
_children[_currentIndex - 1] = _children[previousIndex];
initialPage = _currentIndex - 1;
} else {
_children[_currentIndex + 1] = _children[previousIndex];
initialPage = _currentIndex + 1;
}
});

_childrenWithKey = List<Widget>.from(_childrenWithKey, growable: false);
final Widget temp = _childrenWithKey[initialPage];
_childrenWithKey[initialPage] = _childrenWithKey[previousIndex];
_childrenWithKey[previousIndex] = temp;
});
_pageController.jumpToPage(initialPage);

await _pageController.animateToPage(_currentIndex, duration: kTabScrollDuration, curve: Curves.ease);
if (!mounted)
return Future<void>.value();

setState(() {
_warpUnderwayCount -= 1;
_children = widget.children;
if (widget.children != _children) {
_updateChildren();
} else {
_childrenWithKey = originalChildren;
}
});
}

Expand Down Expand Up @@ -1272,7 +1280,7 @@ class _TabBarViewState extends State<TabBarView> {
dragStartBehavior: widget.dragStartBehavior,
controller: _pageController,
physics: widget.physics == null ? _kTabBarViewPhysics : _kTabBarViewPhysics.applyTo(widget.physics),
children: _children,
children: _childrenWithKey,
),
);
}
Expand Down
102 changes: 91 additions & 11 deletions packages/flutter/lib/src/rendering/sliver_multi_box_adaptor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ abstract class RenderSliverBoxChildManager {
/// list).
int get childCount;

/// Called during [RenderSliverMultiBoxAdaptor.adoptChild].
/// Called during [RenderSliverMultiBoxAdaptor.adoptChild] or
/// [RenderSliverMultiBoxAdaptor.move].
///
/// Subclasses must ensure that the [SliverMultiBoxAdaptorParentData.index]
/// field of the child's [RenderObject.parentData] accurately reflects the
Expand Down Expand Up @@ -193,7 +194,12 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
RenderSliverMultiBoxAdaptor({
@required RenderSliverBoxChildManager childManager,
}) : assert(childManager != null),
_childManager = childManager;
_childManager = childManager {
assert(() {
_debugDanglingKeepAlives = <RenderBox>[];
return true;
}());
}

@override
void setupParentData(RenderObject child) {
Expand All @@ -214,6 +220,27 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
/// The nodes being kept alive despite not being visible.
final Map<int, RenderBox> _keepAliveBucket = <int, RenderBox>{};

List<RenderBox> _debugDanglingKeepAlives;

/// Indicates whether integrity check is enabled.
///
/// Setting this property to true will immediately perform an integrity check.
///
/// The integrity check consists of:
///
/// 1. Verify that the children index in childList is in ascending order.
/// 2. Verify that there is no dangling keepalive child as the result of [move].
bool get debugChildIntegrityEnabled => _debugChildIntegrityEnabled;
bool _debugChildIntegrityEnabled = true;
set debugChildIntegrityEnabled(bool enabled) {
assert(enabled != null);
assert(() {
_debugChildIntegrityEnabled = enabled;
return _debugVerifyChildOrder() &&
(!_debugChildIntegrityEnabled || _debugDanglingKeepAlives.isEmpty);
}());
}

@override
void adoptChild(RenderObject child) {
super.adoptChild(child);
Expand All @@ -224,21 +251,70 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver

bool _debugAssertChildListLocked() => childManager.debugAssertChildListLocked();

/// Verify that the child list index is in strictly increasing order.
///
/// This has no effect in release builds.
bool _debugVerifyChildOrder(){
if (_debugChildIntegrityEnabled) {
RenderBox child = firstChild;
int index;
while (child != null) {
index = indexOf(child);
child = childAfter(child);
assert(child == null || indexOf(child) > index);
}
}
return true;
}

@override
void insert(RenderBox child, { RenderBox after }) {
assert(!_keepAliveBucket.containsValue(child));
super.insert(child, after: after);
assert(firstChild != null);
assert(() {
int index = indexOf(firstChild);
RenderBox child = childAfter(firstChild);
while (child != null) {
assert(indexOf(child) > index);
index = indexOf(child);
child = childAfter(child);
assert(_debugVerifyChildOrder());
}

@override
void move(RenderBox child, { RenderBox after }) {
// There are two scenarios:
//
// 1. The child is not keptAlive.
// The child is in the childList maintained by ContainerRenderObjectMixin.
// We can call super.move and update parentData with the new slot.
//
// 2. The child is keptAlive.
// In this case, the child is no longer in the childList but might be stored in
// [_keepAliveBucket]. We need to update the location of the child in the bucket.
final SliverMultiBoxAdaptorParentData childParentData = child.parentData;
if (!childParentData.keptAlive) {
super.move(child, after: after);
childManager.didAdoptChild(child); // updates the slot in the parentData
// Its slot may change even if super.move does not change the position.
// In this case, we still want to mark as needs layout.
markNeedsLayout();
} else {
// If the child in the bucket is not current child, that means someone has
// already moved and replaced current child, and we cannot remove this child.
if (_keepAliveBucket[childParentData.index] == child) {
_keepAliveBucket.remove(childParentData.index);
}
return true;
}());
assert(() {
_debugDanglingKeepAlives.remove(child);
return true;
}());
// Update the slot and reinsert back to _keepAliveBucket in the new slot.
childManager.didAdoptChild(child);
// If there is an existing child in the new slot, that mean that child will
// be moved to other index. In other cases, the existing child should have been
// removed by updateChild. Thus, it is ok to overwrite it.
assert(() {
if (_keepAliveBucket.containsKey(childParentData.index))
_debugDanglingKeepAlives.add(_keepAliveBucket[childParentData.index]);
return true;
}());
_keepAliveBucket[childParentData.index] = child;
}
}

@override
Expand All @@ -249,6 +325,10 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
return;
}
assert(_keepAliveBucket[childParentData.index] == child);
assert(() {
_debugDanglingKeepAlives.remove(child);
return true;
}());
_keepAliveBucket.remove(childParentData.index);
dropChild(child);
}
Expand Down
7 changes: 7 additions & 0 deletions packages/flutter/lib/src/widgets/framework.dart
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,13 @@ abstract class GlobalKey<T extends State<StatefulWidget>> extends Key {
assert(() {
assert(parent != null);
if (_debugReservations.containsKey(this) && _debugReservations[this] != parent) {
// Reserving a new parent while the old parent is not attached is ok.
// This can happen when a renderObject detaches and re-attaches to rendering
// tree multiple times.
if (_debugReservations[this].renderObject?.attached == false) {
_debugReservations[this] = parent;
return true;
}
// It's possible for an element to get built multiple times in one
// frame, in which case it'll reserve the same child's key multiple
// times. We catch multiple children of one widget having the same key
Expand Down
Loading

0 comments on commit 38808d9

Please sign in to comment.