Handle GlobalKey reparenting inside LayoutBuilder (#5673)
This commit is contained in:
parent
3732d754ca
commit
663596bc54
@ -91,6 +91,7 @@ class _FlexibleSpaceBarState extends State<FlexibleSpaceBar> {
|
|||||||
if (config.background != null) {
|
if (config.background != null) {
|
||||||
final double fadeStart = math.max(0.0, 1.0 - kToolBarHeight / deltaHeight);
|
final double fadeStart = math.max(0.0, 1.0 - kToolBarHeight / deltaHeight);
|
||||||
final double fadeEnd = 1.0;
|
final double fadeEnd = 1.0;
|
||||||
|
assert(fadeStart <= fadeEnd);
|
||||||
final double opacity = 1.0 - new Interval(fadeStart, fadeEnd).transform(t);
|
final double opacity = 1.0 - new Interval(fadeStart, fadeEnd).transform(t);
|
||||||
final double parallax = new Tween<double>(begin: 0.0, end: deltaHeight / 4.0).lerp(t);
|
final double parallax = new Tween<double>(begin: 0.0, end: deltaHeight / 4.0).lerp(t);
|
||||||
if (opacity > 0.0) {
|
if (opacity > 0.0) {
|
||||||
|
@ -880,6 +880,25 @@ class PipelineOwner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This flag is used to allow the kinds of mutations performed by GlobalKey
|
||||||
|
// reparenting while a LayoutBuilder is being rebuilt and in so doing tries to
|
||||||
|
// move a node from another LayoutBuilder subtree that hasn't been updated
|
||||||
|
// yet. To set this, call [_enableMutationsToDirtySubtrees], which is called
|
||||||
|
// by [RenderObject.invokeLayoutCallback].
|
||||||
|
bool _debugAllowMutationsToDirtySubtrees = false;
|
||||||
|
|
||||||
|
// See [RenderObject.invokeLayoutCallback].
|
||||||
|
void _enableMutationsToDirtySubtrees(VoidCallback callback) {
|
||||||
|
assert(_debugDoingLayout);
|
||||||
|
bool oldState = _debugAllowMutationsToDirtySubtrees;
|
||||||
|
_debugAllowMutationsToDirtySubtrees = true;
|
||||||
|
try {
|
||||||
|
callback();
|
||||||
|
} finally {
|
||||||
|
_debugAllowMutationsToDirtySubtrees = oldState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List<RenderObject> _nodesNeedingCompositingBitsUpdate = <RenderObject>[];
|
List<RenderObject> _nodesNeedingCompositingBitsUpdate = <RenderObject>[];
|
||||||
/// Updates the [needsCompositing] bits.
|
/// Updates the [needsCompositing] bits.
|
||||||
///
|
///
|
||||||
@ -1174,6 +1193,10 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
|||||||
result = true;
|
result = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (owner != null && owner._debugAllowMutationsToDirtySubtrees && node._needsLayout) {
|
||||||
|
result = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (node._debugMutationsLocked) {
|
if (node._debugMutationsLocked) {
|
||||||
result = false;
|
result = false;
|
||||||
break;
|
break;
|
||||||
@ -1584,8 +1607,21 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
|||||||
// this field always holds a closure.
|
// this field always holds a closure.
|
||||||
VoidCallback _performLayout = _doNothing;
|
VoidCallback _performLayout = _doNothing;
|
||||||
|
|
||||||
/// Allows this render object to mutate its child list during layout and
|
/// Allows mutations to be made to this object's child list (and any
|
||||||
/// calls callback.
|
/// descendants) as well as to any other dirty nodes in the render tree owned
|
||||||
|
/// by the same [PipelineOwner] as this object. The `callback` argument is
|
||||||
|
/// invoked synchronously, and the mutations are allowed only during that
|
||||||
|
/// callback's execution.
|
||||||
|
///
|
||||||
|
/// This exists to allow child lists to be built on-demand during layout (e.g.
|
||||||
|
/// based on the object's size), and to enable nodes to be moved around the
|
||||||
|
/// tree as this happens (e.g. to handle [GlobalKey] reparenting), while still
|
||||||
|
/// ensuring that any particular node is only laid out once per frame.
|
||||||
|
///
|
||||||
|
/// Calling this function disables a number of assertions that are intended to
|
||||||
|
/// catch likely bugs. As such, using this function is generally discouraged.
|
||||||
|
///
|
||||||
|
/// This function can only be called during layout.
|
||||||
@protected
|
@protected
|
||||||
void invokeLayoutCallback(LayoutCallback callback) {
|
void invokeLayoutCallback(LayoutCallback callback) {
|
||||||
assert(_debugMutationsLocked);
|
assert(_debugMutationsLocked);
|
||||||
@ -1593,7 +1629,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
|||||||
assert(!_doingThisLayoutWithCallback);
|
assert(!_doingThisLayoutWithCallback);
|
||||||
_doingThisLayoutWithCallback = true;
|
_doingThisLayoutWithCallback = true;
|
||||||
try {
|
try {
|
||||||
callback(constraints);
|
owner._enableMutationsToDirtySubtrees(() { callback(constraints); });
|
||||||
} finally {
|
} finally {
|
||||||
_doingThisLayoutWithCallback = false;
|
_doingThisLayoutWithCallback = false;
|
||||||
}
|
}
|
||||||
|
@ -48,10 +48,14 @@ class LayoutBuilder extends RenderObjectWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
_RenderLayoutBuilder createRenderObject(BuildContext context) => new _RenderLayoutBuilder();
|
_RenderLayoutBuilder createRenderObject(BuildContext context) => new _RenderLayoutBuilder();
|
||||||
|
|
||||||
|
// updateRenderObject is redundant with the logic in the LayoutBuilderElement below.
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RenderLayoutBuilder extends RenderBox with RenderObjectWithChildMixin<RenderBox> {
|
class _RenderLayoutBuilder extends RenderBox with RenderObjectWithChildMixin<RenderBox> {
|
||||||
_RenderLayoutBuilder({ LayoutCallback callback }) : _callback = callback;
|
_RenderLayoutBuilder({
|
||||||
|
LayoutCallback callback,
|
||||||
|
}) : _callback = callback;
|
||||||
|
|
||||||
LayoutCallback get callback => _callback;
|
LayoutCallback get callback => _callback;
|
||||||
LayoutCallback _callback;
|
LayoutCallback _callback;
|
||||||
@ -102,7 +106,7 @@ class _RenderLayoutBuilder extends RenderBox with RenderObjectWithChildMixin<Ren
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void performLayout() {
|
void performLayout() {
|
||||||
if (callback != null)
|
assert(callback != null);
|
||||||
invokeLayoutCallback(callback);
|
invokeLayoutCallback(callback);
|
||||||
if (child != null) {
|
if (child != null) {
|
||||||
child.layout(constraints, parentUsesSize: true);
|
child.layout(constraints, parentUsesSize: true);
|
||||||
@ -124,6 +128,7 @@ class _RenderLayoutBuilder extends RenderBox with RenderObjectWithChildMixin<Ren
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(ianh): move this class up to just below its widget.
|
||||||
class _LayoutBuilderElement extends RenderObjectElement {
|
class _LayoutBuilderElement extends RenderObjectElement {
|
||||||
_LayoutBuilderElement(LayoutBuilder widget) : super(widget);
|
_LayoutBuilderElement(LayoutBuilder widget) : super(widget);
|
||||||
|
|
||||||
@ -144,7 +149,7 @@ class _LayoutBuilderElement extends RenderObjectElement {
|
|||||||
@override
|
@override
|
||||||
void mount(Element parent, dynamic newSlot) {
|
void mount(Element parent, dynamic newSlot) {
|
||||||
super.mount(parent, newSlot); // Creates the renderObject.
|
super.mount(parent, newSlot); // Creates the renderObject.
|
||||||
renderObject.callback = _layout; // The _child will be built during layout.
|
renderObject.callback = _layout;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -161,7 +166,7 @@ class _LayoutBuilderElement extends RenderObjectElement {
|
|||||||
// This gets called if markNeedsBuild() is called on us.
|
// This gets called if markNeedsBuild() is called on us.
|
||||||
// That might happen if, e.g., our builder uses Inherited widgets.
|
// That might happen if, e.g., our builder uses Inherited widgets.
|
||||||
renderObject.markNeedsLayout();
|
renderObject.markNeedsLayout();
|
||||||
super.performRebuild(); // calls widget.updateRenderObject
|
super.performRebuild(); // Calls widget.updateRenderObject (a no-op in this case).
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -171,10 +176,9 @@ class _LayoutBuilderElement extends RenderObjectElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _layout(BoxConstraints constraints) {
|
void _layout(BoxConstraints constraints) {
|
||||||
if (widget.builder == null)
|
|
||||||
return;
|
|
||||||
owner.lockState(() {
|
|
||||||
Widget built;
|
Widget built;
|
||||||
|
if (widget.builder != null) {
|
||||||
|
owner.lockState(() {
|
||||||
try {
|
try {
|
||||||
built = widget.builder(this, constraints);
|
built = widget.builder(this, constraints);
|
||||||
debugWidgetBuilderValue(widget, built);
|
debugWidgetBuilderValue(widget, built);
|
||||||
@ -182,7 +186,14 @@ class _LayoutBuilderElement extends RenderObjectElement {
|
|||||||
_debugReportException('building $widget', e, stack);
|
_debugReportException('building $widget', e, stack);
|
||||||
built = new ErrorWidget(e);
|
built = new ErrorWidget(e);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
owner.lockState(() {
|
||||||
|
if (widget.builder == null) {
|
||||||
|
if (_child != null)
|
||||||
|
_child = updateChild(_child, null, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
_child = updateChild(_child, built, null);
|
_child = updateChild(_child, built, null);
|
||||||
assert(_child != null);
|
assert(_child != null);
|
||||||
|
@ -66,7 +66,7 @@ abstract class ScrollBehavior<T, U> {
|
|||||||
/// from the given offset by the given delta.
|
/// from the given offset by the given delta.
|
||||||
T applyCurve(T scrollOffset, T scrollDelta) => scrollOffset;
|
T applyCurve(T scrollOffset, T scrollDelta) => scrollOffset;
|
||||||
|
|
||||||
/// Whether this scroll behavior currently permits scrolling
|
/// Whether this scroll behavior currently permits scrolling.
|
||||||
bool get isScrollable => true;
|
bool get isScrollable => true;
|
||||||
|
|
||||||
/// The scroll drag constant to use for physics simulations created by this
|
/// The scroll drag constant to use for physics simulations created by this
|
||||||
|
@ -0,0 +1,73 @@
|
|||||||
|
// Copyright 2016 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter_test/flutter_test.dart' hide TypeMatcher;
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
class SizeChanger extends StatefulWidget {
|
||||||
|
SizeChanger({
|
||||||
|
Key key,
|
||||||
|
this.child,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
SizeChangerState createState() => new SizeChangerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class SizeChangerState extends State<SizeChanger> {
|
||||||
|
bool _flag = false;
|
||||||
|
|
||||||
|
void trigger() {
|
||||||
|
setState(() {
|
||||||
|
_flag = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return new Row(
|
||||||
|
children: <Widget>[
|
||||||
|
new SizedBox(
|
||||||
|
height: _flag ? 50.0 : 100.0,
|
||||||
|
width: 100.0,
|
||||||
|
child: config.child
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Wrapper extends StatelessWidget {
|
||||||
|
Wrapper({
|
||||||
|
Key key,
|
||||||
|
this.child,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('Applying parent data inside a LayoutBuilder', (WidgetTester tester) async {
|
||||||
|
int frame = 0;
|
||||||
|
await tester.pumpWidget(new SizeChanger( // when this is triggered, the child LayoutBuilder will build again
|
||||||
|
child: new LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
|
||||||
|
return new Column(children: <Widget>[new Flexible(
|
||||||
|
flex: frame, // this is different after the next pump, so that the parentData has to be applied again
|
||||||
|
child: new Container(height: 100.0),
|
||||||
|
)]);
|
||||||
|
})
|
||||||
|
));
|
||||||
|
frame += 1;
|
||||||
|
SizeChangerState state = tester.state(find.byType(SizeChanger));
|
||||||
|
state.trigger();
|
||||||
|
await tester.pump();
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
// Copyright 2016 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/src/widgets/basic.dart';
|
||||||
|
import 'package:flutter/src/widgets/framework.dart';
|
||||||
|
import 'package:flutter/src/widgets/layout_builder.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart' hide TypeMatcher;
|
||||||
|
|
||||||
|
class Wrapper extends StatelessWidget {
|
||||||
|
Wrapper({
|
||||||
|
Key key,
|
||||||
|
this.child
|
||||||
|
}) : super(key: key) {
|
||||||
|
assert(child != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => child;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('Moving a global key from another LayoutBuilder at layout time', (WidgetTester tester) async {
|
||||||
|
final GlobalKey victimKey = new GlobalKey();
|
||||||
|
|
||||||
|
await tester.pumpWidget(new Row(children: <Widget>[
|
||||||
|
new Wrapper(
|
||||||
|
child: new LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
|
||||||
|
return new SizedBox();
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
new Wrapper(
|
||||||
|
child: new Wrapper(
|
||||||
|
child: new LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
|
||||||
|
return new Wrapper(
|
||||||
|
child: new SizedBox(key: victimKey)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]));
|
||||||
|
|
||||||
|
await tester.pumpWidget(new Row(children: <Widget>[
|
||||||
|
new Wrapper(
|
||||||
|
child: new LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
|
||||||
|
return new Wrapper(
|
||||||
|
child: new SizedBox(key: victimKey)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
),
|
||||||
|
new Wrapper(
|
||||||
|
child: new Wrapper(
|
||||||
|
child: new LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
|
||||||
|
return new SizedBox();
|
||||||
|
})
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user