forked from flutter/flutter
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* LayoutBuilder Widget
- Loading branch information
Hans Muller
committed
May 3, 2016
1 parent
c9010c9
commit b38927e
Showing
6 changed files
with
336 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
// 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 'debug.dart'; | ||
import 'framework.dart'; | ||
|
||
import 'package:flutter/rendering.dart'; | ||
|
||
/// The signature of the [LayoutBuilder] builder function. | ||
typedef Widget LayoutWidgetBuilder(BuildContext context, Size size); | ||
|
||
/// Builds a widget tree that can depend on the parent widget's size. | ||
/// | ||
/// Similar to the [Builder] widget except that the framework calls the [builder] | ||
/// function at layout time and provides the parent widget's size. This is useful | ||
/// when the parent constrains the child's size and doesn't depend on the child's | ||
/// intrinsic size. | ||
class LayoutBuilder extends RenderObjectWidget { | ||
LayoutBuilder({ Key key, this.builder }) : super(key: key); | ||
|
||
/// Called at layout time to construct the widget tree. The builder must not | ||
/// return null. | ||
final LayoutWidgetBuilder builder; | ||
|
||
@override | ||
_LayoutBuilderElement createElement() => new _LayoutBuilderElement(this); | ||
|
||
@override | ||
_RenderLayoutBuilder createRenderObject(BuildContext context) => new _RenderLayoutBuilder(); | ||
} | ||
|
||
class _RenderLayoutBuilder extends RenderBox with RenderObjectWithChildMixin<RenderBox> { | ||
_RenderLayoutBuilder({ LayoutCallback callback }) : _callback = callback; | ||
|
||
LayoutCallback get callback => _callback; | ||
LayoutCallback _callback; | ||
void set callback(LayoutCallback value) { | ||
if (value == _callback) | ||
return; | ||
_callback = value; | ||
markNeedsLayout(); | ||
} | ||
|
||
double getIntrinsicWidth(BoxConstraints constraints) => constraints.constrainWidth(); | ||
|
||
double getIntrinsicHeight(BoxConstraints constraints) => constraints.constrainHeight(); | ||
|
||
@override | ||
double getMinIntrinsicWidth(BoxConstraints constraints) { | ||
assert(constraints.debugAssertIsValid()); | ||
return getIntrinsicWidth(constraints); | ||
} | ||
|
||
@override | ||
double getMaxIntrinsicWidth(BoxConstraints constraints) { | ||
assert(constraints.debugAssertIsValid()); | ||
return getIntrinsicWidth(constraints); | ||
} | ||
|
||
@override | ||
double getMinIntrinsicHeight(BoxConstraints constraints) { | ||
assert(constraints.debugAssertIsValid()); | ||
return getIntrinsicHeight(constraints); | ||
} | ||
|
||
@override | ||
double getMaxIntrinsicHeight(BoxConstraints constraints) { | ||
assert(constraints.debugAssertIsValid()); | ||
return getIntrinsicHeight(constraints); | ||
} | ||
|
||
@override | ||
bool get sizedByParent => true; | ||
|
||
@override | ||
void performResize() { | ||
size = constraints.biggest; | ||
} | ||
|
||
@override | ||
void performLayout() { | ||
if (callback != null) | ||
invokeLayoutCallback(callback); | ||
if (child != null) | ||
child.layout(constraints.loosen(), parentUsesSize: false); | ||
} | ||
|
||
@override | ||
bool hitTestChildren(HitTestResult result, { Point position }) { | ||
return child?.hitTest(result, position: position) ?? false; | ||
} | ||
|
||
@override | ||
void paint(PaintingContext context, Offset offset) { | ||
if (child != null) | ||
context.paintChild(child, offset); | ||
} | ||
} | ||
|
||
class _LayoutBuilderElement extends RenderObjectElement { | ||
_LayoutBuilderElement(LayoutBuilder widget) : super(widget); | ||
|
||
@override | ||
LayoutBuilder get widget => super.widget; | ||
|
||
@override | ||
_RenderLayoutBuilder get renderObject => super.renderObject; | ||
|
||
Element _child; | ||
|
||
@override | ||
void visitChildren(ElementVisitor visitor) { | ||
if (_child != null) | ||
visitor(_child); | ||
} | ||
|
||
@override | ||
void mount(Element parent, dynamic newSlot) { | ||
super.mount(parent, newSlot); // Creates the renderObject. | ||
renderObject.callback = _layout; // The _child will be built during layout. | ||
} | ||
|
||
@override | ||
void update(LayoutBuilder newWidget) { | ||
assert(widget != newWidget); | ||
super.update(newWidget); | ||
assert(widget == newWidget); | ||
renderObject.callback = _layout; | ||
renderObject.markNeedsLayout(); | ||
} | ||
|
||
@override | ||
void unmount() { | ||
renderObject.callback = null; | ||
super.unmount(); | ||
} | ||
|
||
void _layout(BoxConstraints constraints) { | ||
if (widget.builder == null) | ||
return; | ||
owner.lockState(() { | ||
Widget built; | ||
try { | ||
built = widget.builder(this, constraints.biggest); | ||
debugWidgetBuilderValue(widget, built); | ||
} catch (e, stack) { | ||
_debugReportException('building $widget', e, stack); | ||
built = new ErrorWidget(e); | ||
} | ||
|
||
try { | ||
_child = updateChild(_child, built, null); | ||
assert(_child != null); | ||
} catch (e, stack) { | ||
_debugReportException('building $widget', e, stack); | ||
built = new ErrorWidget(e); | ||
_child = updateChild(null, built, slot); | ||
} | ||
}, building: true); | ||
} | ||
|
||
@override | ||
void insertChildRenderObject(RenderObject child, dynamic slot) { | ||
final RenderObjectWithChildMixin<RenderObject> renderObject = this.renderObject; | ||
assert(slot == null); | ||
renderObject.child = child; | ||
assert(renderObject == this.renderObject); | ||
} | ||
|
||
@override | ||
void moveChildRenderObject(RenderObject child, dynamic slot) { | ||
assert(false); | ||
} | ||
|
||
@override | ||
void removeChildRenderObject(RenderObject child) { | ||
final _RenderLayoutBuilder renderObject = this.renderObject; | ||
assert(renderObject.child == child); | ||
renderObject.child = null; | ||
assert(renderObject == this.renderObject); | ||
} | ||
} | ||
|
||
void _debugReportException(String context, dynamic exception, StackTrace stack) { | ||
FlutterError.reportError(new FlutterErrorDetails( | ||
exception: exception, | ||
stack: stack, | ||
library: 'widgets library', | ||
context: context | ||
)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
// Copyright 2015 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'; | ||
import 'package:flutter/rendering.dart'; | ||
import 'package:flutter/widgets.dart'; | ||
import 'package:test/test.dart'; | ||
|
||
void main() { | ||
testWidgets('LayoutBuilder parent size', (WidgetTester tester) { | ||
Size layoutBuilderSize; | ||
Key childKey = new UniqueKey(); | ||
|
||
tester.pumpWidget( | ||
new Center( | ||
child: new SizedBox( | ||
width: 100.0, | ||
height: 200.0, | ||
child: new LayoutBuilder( | ||
builder: (BuildContext context, Size size) { | ||
layoutBuilderSize = size; | ||
return new SizedBox( | ||
key: childKey, | ||
width: size.width / 2.0, | ||
height: size.height / 2.0 | ||
); | ||
} | ||
) | ||
) | ||
) | ||
); | ||
|
||
expect(layoutBuilderSize, const Size(100.0, 200.0)); | ||
RenderBox box = tester.renderObject(find.byKey(childKey)); | ||
expect(box.size, equals(const Size(50.0, 100.0))); | ||
}); | ||
|
||
testWidgets('LayoutBuilder stateful child', (WidgetTester tester) { | ||
Size layoutBuilderSize; | ||
StateSetter setState; | ||
Key childKey = new UniqueKey(); | ||
double childWidth = 10.0; | ||
double childHeight = 20.0; | ||
|
||
tester.pumpWidget( | ||
new LayoutBuilder( | ||
builder: (BuildContext context, Size size) { | ||
layoutBuilderSize = size; | ||
return new StatefulBuilder( | ||
builder: (BuildContext context, StateSetter setter) { | ||
setState = setter; | ||
return new SizedBox( | ||
key: childKey, | ||
width: childWidth, | ||
height: childHeight | ||
); | ||
} | ||
); | ||
} | ||
) | ||
); | ||
|
||
expect(layoutBuilderSize, equals(const Size(800.0, 600.0))); | ||
RenderBox box = tester.renderObject(find.byKey(childKey)); | ||
expect(box.size, equals(const Size(10.0, 20.0))); | ||
|
||
setState(() { | ||
childWidth = 100.0; | ||
childHeight = 200.0; | ||
}); | ||
tester.pump(); | ||
box = tester.renderObject(find.byKey(childKey)); | ||
expect(box.size, equals(const Size(100.0, 200.0))); | ||
}); | ||
|
||
testWidgets('LayoutBuilder stateful parent', (WidgetTester tester) { | ||
Size layoutBuilderSize; | ||
StateSetter setState; | ||
Key childKey = new UniqueKey(); | ||
double childWidth = 10.0; | ||
double childHeight = 20.0; | ||
|
||
tester.pumpWidget( | ||
new Center( | ||
child: new StatefulBuilder( | ||
builder: (BuildContext context, StateSetter setter) { | ||
setState = setter; | ||
return new SizedBox( | ||
width: childWidth, | ||
height: childHeight, | ||
child: new LayoutBuilder( | ||
builder: (BuildContext context, Size size) { | ||
layoutBuilderSize = size; | ||
return new SizedBox( | ||
key: childKey, | ||
width: size.width, | ||
height: size.height | ||
); | ||
} | ||
) | ||
); | ||
} | ||
) | ||
) | ||
); | ||
|
||
expect(layoutBuilderSize, equals(const Size(10.0, 20.0))); | ||
RenderBox box = tester.renderObject(find.byKey(childKey)); | ||
expect(box.size, equals(const Size(10.0, 20.0))); | ||
|
||
setState(() { | ||
childWidth = 100.0; | ||
childHeight = 200.0; | ||
}); | ||
tester.pump(); | ||
box = tester.renderObject(find.byKey(childKey)); | ||
expect(box.size, equals(const Size(100.0, 200.0))); | ||
}); | ||
} |