1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
// 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/foundation.dart';
import 'package:flutter/rendering.dart';
import 'debug.dart';
import 'framework.dart';
/// The signature of the [LayoutBuilder] builder function.
typedef Widget LayoutWidgetBuilder(BuildContext context, BoxConstraints constraints);
/// 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 constraints. This
/// is useful when the parent constrains the child's size and doesn't depend on
/// the child's intrinsic size. The [LayoutBuilder]'s final size will match its
/// child's size.
///
/// If the child should be smaller than the parent, consider wrapping the child
/// in an [Align] widget. If the child might want to be bigger, consider
/// wrapping it in a [SingleChildScrollView].
///
/// See also:
///
/// * [Builder], which calls a `builder` function at build time.
/// * [StatefulBuilder], which passes its `builder` function a `setState` callback.
/// * [CustomSingleChildLayout], which positions its child during layout.
class LayoutBuilder extends RenderObjectWidget {
/// Creates a widget that defers its building until layout.
///
/// The [builder] argument must not be null.
const LayoutBuilder({
Key key,
@required this.builder
}) : assert(builder != null),
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();
// updateRenderObject is redundant with the logic in the LayoutBuilderElement below.
}
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 forgetChild(Element child) {
assert(child == _child);
_child = null;
}
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot); // Creates the renderObject.
renderObject.callback = _layout;
}
@override
void update(LayoutBuilder newWidget) {
assert(widget != newWidget);
super.update(newWidget);
assert(widget == newWidget);
renderObject.callback = _layout;
renderObject.markNeedsLayout();
}
@override
void performRebuild() {
// This gets called if markNeedsBuild() is called on us.
// That might happen if, e.g., our builder uses Inherited widgets.
renderObject.markNeedsLayout();
super.performRebuild(); // Calls widget.updateRenderObject (a no-op in this case).
}
@override
void unmount() {
renderObject.callback = null;
super.unmount();
}
void _layout(BoxConstraints constraints) {
owner.buildScope(this, () {
Widget built;
if (widget.builder != null) {
try {
built = widget.builder(this, constraints);
debugWidgetBuilderValue(widget, built);
} catch (e, stack) {
built = ErrorWidget.builder(_debugReportException('building $widget', e, stack));
}
}
try {
_child = updateChild(_child, built, null);
assert(_child != null);
} catch (e, stack) {
built = ErrorWidget.builder(_debugReportException('building $widget', e, stack));
_child = updateChild(null, built, slot);
}
});
}
@override
void insertChildRenderObject(RenderObject child, dynamic slot) {
final RenderObjectWithChildMixin<RenderObject> renderObject = this.renderObject;
assert(slot == null);
assert(renderObject.debugValidateChild(child));
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);
}
}
class _RenderLayoutBuilder extends RenderBox with RenderObjectWithChildMixin<RenderBox> {
_RenderLayoutBuilder({
LayoutCallback<BoxConstraints> callback,
}) : _callback = callback;
LayoutCallback<BoxConstraints> get callback => _callback;
LayoutCallback<BoxConstraints> _callback;
set callback(LayoutCallback<BoxConstraints> value) {
if (value == _callback)
return;
_callback = value;
markNeedsLayout();
}
bool _debugThrowIfNotCheckingIntrinsics() {
assert(() {
if (!RenderObject.debugCheckingIntrinsics) {
throw new FlutterError(
'LayoutBuilder does not support returning intrinsic dimensions.\n'
'Calculating the intrinsic dimensions would require running the layout '
'callback speculatively, which might mutate the live render object tree.'
);
}
return true;
}());
return true;
}
@override
double computeMinIntrinsicWidth(double height) {
assert(_debugThrowIfNotCheckingIntrinsics());
return 0.0;
}
@override
double computeMaxIntrinsicWidth(double height) {
assert(_debugThrowIfNotCheckingIntrinsics());
return 0.0;
}
@override
double computeMinIntrinsicHeight(double width) {
assert(_debugThrowIfNotCheckingIntrinsics());
return 0.0;
}
@override
double computeMaxIntrinsicHeight(double width) {
assert(_debugThrowIfNotCheckingIntrinsics());
return 0.0;
}
@override
void performLayout() {
assert(callback != null);
invokeLayoutCallback(callback);
if (child != null) {
child.layout(constraints, parentUsesSize: true);
size = constraints.constrain(child.size);
} else {
size = constraints.biggest;
}
}
@override
bool hitTestChildren(HitTestResult result, { Offset position }) {
return child?.hitTest(result, position: position) ?? false;
}
@override
void paint(PaintingContext context, Offset offset) {
if (child != null)
context.paintChild(child, offset);
}
}
FlutterErrorDetails _debugReportException(
String context,
dynamic exception,
StackTrace stack,
) {
final FlutterErrorDetails details = new FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'widgets library',
context: context
);
FlutterError.reportError(details);
return details;
}