Flutter -- 布局约束Layout

653 阅读2分钟

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')子控件的大小了。

performLayoutpaint是在不同遍历去执行的,一个是确定size的大小,一个是在控件中绘制视图。所以以上就可以用自定义的RenderBox解决之前说的问题了。

小结

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