LayoutBuilder

其实上一篇文章中也设计到了LayoutBuilder,然后其作用就是布局过程中拿到上层控件传递下来的约束信息,这样开发者可以动态的改变自身的布局。

  1. LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
  2. if (constraints.maxWidth < 200) {
  3. return Text();
  4. } else {
  5. return Container();
  6. }
  7. },
  8. )
  1. `LayoutBuilder` 另外一个作用就是当开发者需要排查布局问题时,需要知道当前传递下来的约束,这样就可以迅速的定位问题。

CustomMultiChildLayout

首先看看这个控件在Flutter中怎么定义的吧

  1. class CustomMultiChildLayout extends MultiChildRenderObjectWidget {
  2. CustomMultiChildLayout({
  3. Key? key,
  4. required this.delegate,
  5. List<Widget> children = const <Widget>[],
  6. }) : assert(delegate != null),
  7. super(key: key, children: children);
  8. /// The delegate that controls the layout of the children.
  9. final MultiChildLayoutDelegate delegate;
  10. @override
  11. RenderCustomMultiChildLayoutBox createRenderObject(BuildContext context) {
  12. return RenderCustomMultiChildLayoutBox(delegate: delegate);
  13. }
  14. @override
  15. void updateRenderObject(BuildContext context, RenderCustomMultiChildLayoutBox renderObject) {
  16. renderObject.delegate = delegate;
  17. }
  18. }
  1. `CustomMultiChildLayout`继承了`MultiChildRenderObjectWidget`,说明此控件可以绘制多个控件,并且控件实现了一个代理协议,在代理协议中开发者可以很灵活的拿到`CustomMultiChildLayout`中的控件并且进行布局计算。
  1. Container(
  2. child: CustomMultiChildLayout(
  3. delegate: null,
  4. children: [
  5. LayoutId(id: 1, child:
  6. Text('Hello')
  7. ),
  8. LayoutId(id: 2, child:
  9. Text('Flutter')
  10. )
  11. ],
  12. ),
  13. )

可以看到子控件用LayoutId进行包裹,并且用id进行识别。接着看看delegate是怎么定义的吧!

  1. class LayoutDelegate extends MultiChildLayoutDelegate {
  2. @override
  3. void performLayout(Size size) {
  4. final size1 = layoutChild(1, BoxConstraints.loose(size));
  5. positionChild(1, Offset(0, 0));
  6. final size2 = layoutChild(2, BoxConstraints.loose(size));
  7. positionChild(2, Offset(0, 0));
  8. }
  9. @override
  10. bool shouldRelayout(_) => true;
  11. @override
  12. Size getSize(BoxConstraints constraints) {
  13. return super.getSize(constraints);
  14. }
  15. }

这样就可以在performLayout中灵活的布局了。getSize这个方法就是根据父级给自己的约束来确定自己的size,这样我们也可以手动的设置size的大小,但是不能根据子控件的大小去设置该控件的size,当然这个算是CustomMultiChildLayout的局限性了。但是如果需要根据子控件的大小去设置父级的size,这个时候就需要自定义一个RenderObject

自定义RenderBox

SingleChildRenderObjectWidget可以把控件画在屏幕上,先看看自定义部分:

  1. class MyRenderBox extends SingleChildRenderObjectWidget {
  2. MyRenderBox({Widget child}) : super(child: child);
  3. @override
  4. RenderObject createRenderObject(BuildContext context) {
  5. return RenderMyRenderBox();
  6. }
  7. }
  8. class RenderMyRenderBox extends RenderBox with RenderObjectWithChildMixin {
  9. @override
  10. void performLayout() {
  11. child.layout(constraints);
  12. size = Size(300, 300);
  13. }
  14. @override
  15. void paint(PaintingContext context, Offset offset) {
  16. context.paintChild(child, offset);
  17. }

然后看看怎么应用:

  1. Widget renderBox() {
  2. return Container(
  3. child: MyRenderBox(
  4. child: Text('Hello')
  5. ),
  6. );
  7. }
  1. 这儿我们可以看到`size = Size(300, 300);`控件的尺寸是300*300,但是如果是根据自己子控件的大小去设置自己的大小呢?
  1. @override
  2. void performLayout() {
  3. child.layout(constraints, parentUsesSize: true);
  4. size = (child as RenderBox).size;
  5. }
  1. 需要把`parentUsesSize`设置成`true`,这样父类重新布局的时候就会和子控件的size有关系了。这里的就是和`Text('Hello')`子控件的大小了。<br />`performLayout``paint`是在不同遍历去执行的,一个是确定size的大小,一个是在控件中绘制视图。所以以上就可以用自定义的`RenderBox`解决之前说的问题了。

小结

Flutter的布局原理两篇文章介绍的差不多了,不过常用的就是第一篇中的那些,主要介绍的是不同控件中布局原理,通过这些布局原理可以更好的去写开发需求中的界面需求,这样就熟悉的掌握Flutter这门框架了。