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;@overrideRenderCustomMultiChildLayoutBox createRenderObject(BuildContext context) {return RenderCustomMultiChildLayoutBox(delegate: delegate);}@overridevoid 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 {@overridevoid 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));}@overridebool shouldRelayout(_) => true;@overrideSize 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);@overrideRenderObject createRenderObject(BuildContext context) {return RenderMyRenderBox();}}class RenderMyRenderBox extends RenderBox with RenderObjectWithChildMixin {@overridevoid performLayout() {child.layout(constraints);size = Size(300, 300);}@overridevoid paint(PaintingContext context, Offset offset) {context.paintChild(child, offset);}
然后看看怎么应用:
Widget renderBox() {return Container(child: MyRenderBox(child: Text('Hello')),);}
这儿我们可以看到`size = Size(300, 300);`控件的尺寸是300*300,但是如果是根据自己子控件的大小去设置自己的大小呢?
@overridevoid performLayout() {child.layout(constraints, parentUsesSize: true);size = (child as RenderBox).size;}
需要把`parentUsesSize`设置成`true`,这样父类重新布局的时候就会和子控件的size有关系了。这里的就是和`Text('Hello')`子控件的大小了。<br />`performLayout`和`paint`是在不同遍历去执行的,一个是确定size的大小,一个是在控件中绘制视图。所以以上就可以用自定义的`RenderBox`解决之前说的问题了。
小结
Flutter的布局原理两篇文章介绍的差不多了,不过常用的就是第一篇中的那些,主要介绍的是不同控件中布局原理,通过这些布局原理可以更好的去写开发需求中的界面需求,这样就熟悉的掌握Flutter这门框架了。
