LayoutBuilder
其实上一篇文章中也设计到了LayoutBuilder
,然后其作用就是布局过程中拿到上层控件传递下来的约束信息,这样开发者可以动态的改变自身的布局。
LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
if (constraints.maxWidth < 200) {
return Text();
} else {
return Container();
}
},
)
`LayoutBuilder` 另外一个作用就是当开发者需要排查布局问题时,需要知道当前传递下来的约束,这样就可以迅速的定位问题。
CustomMultiChildLayout
首先看看这个控件在Flutter中怎么定义的吧
class CustomMultiChildLayout extends MultiChildRenderObjectWidget {
CustomMultiChildLayout({
Key? key,
required this.delegate,
List<Widget> children = const <Widget>[],
}) : assert(delegate != null),
super(key: key, children: children);
/// The delegate that controls the layout of the children.
final MultiChildLayoutDelegate delegate;
@override
RenderCustomMultiChildLayoutBox createRenderObject(BuildContext context) {
return RenderCustomMultiChildLayoutBox(delegate: delegate);
}
@override
void updateRenderObject(BuildContext context, RenderCustomMultiChildLayoutBox renderObject) {
renderObject.delegate = delegate;
}
}
`CustomMultiChildLayout`继承了`MultiChildRenderObjectWidget`,说明此控件可以绘制多个控件,并且控件实现了一个代理协议,在代理协议中开发者可以很灵活的拿到`CustomMultiChildLayout`中的控件并且进行布局计算。
Container(
child: CustomMultiChildLayout(
delegate: null,
children: [
LayoutId(id: 1, child:
Text('Hello')
),
LayoutId(id: 2, child:
Text('Flutter')
)
],
),
)
可以看到子控件用LayoutId
进行包裹,并且用id
进行识别。接着看看delegate
是怎么定义的吧!
class LayoutDelegate extends MultiChildLayoutDelegate {
@override
void performLayout(Size size) {
final size1 = layoutChild(1, BoxConstraints.loose(size));
positionChild(1, Offset(0, 0));
final size2 = layoutChild(2, BoxConstraints.loose(size));
positionChild(2, Offset(0, 0));
}
@override
bool shouldRelayout(_) => true;
@override
Size getSize(BoxConstraints constraints) {
return super.getSize(constraints);
}
}
这样就可以在performLayout
中灵活的布局了。getSize
这个方法就是根据父级给自己的约束来确定自己的size,这样我们也可以手动的设置size的大小,但是不能根据子控件的大小去设置该控件的size,当然这个算是CustomMultiChildLayout
的局限性了。但是如果需要根据子控件的大小去设置父级的size,这个时候就需要自定义一个RenderObject
。
自定义RenderBox
SingleChildRenderObjectWidget
可以把控件画在屏幕上,先看看自定义部分:
class MyRenderBox extends SingleChildRenderObjectWidget {
MyRenderBox({Widget child}) : super(child: child);
@override
RenderObject createRenderObject(BuildContext context) {
return RenderMyRenderBox();
}
}
class RenderMyRenderBox extends RenderBox with RenderObjectWithChildMixin {
@override
void performLayout() {
child.layout(constraints);
size = Size(300, 300);
}
@override
void paint(PaintingContext context, Offset offset) {
context.paintChild(child, offset);
}
然后看看怎么应用:
Widget renderBox() {
return Container(
child: MyRenderBox(
child: Text('Hello')
),
);
}
这儿我们可以看到`size = Size(300, 300);`控件的尺寸是300*300,但是如果是根据自己子控件的大小去设置自己的大小呢?
@override
void performLayout() {
child.layout(constraints, parentUsesSize: true);
size = (child as RenderBox).size;
}
需要把`parentUsesSize`设置成`true`,这样父类重新布局的时候就会和子控件的size有关系了。这里的就是和`Text('Hello')`子控件的大小了。<br />`performLayout`和`paint`是在不同遍历去执行的,一个是确定size的大小,一个是在控件中绘制视图。所以以上就可以用自定义的`RenderBox`解决之前说的问题了。
小结
Flutter的布局原理两篇文章介绍的差不多了,不过常用的就是第一篇中的那些,主要介绍的是不同控件中布局原理,通过这些布局原理可以更好的去写开发需求中的界面需求,这样就熟悉的掌握Flutter这门框架了。