Flutter进阶篇-布局(Layout)原理

1、约束、尺寸、位置  

  @override
  Widget build(BuildContext context) {
    return Scaffold(body: LayoutBuilder(builder: (context, constraints) {
      print("body约束:" + constraints.toString());
      return Container(
          color: Colors.black,
          width: 300,
          height: 300,
          child: LayoutBuilder(builder: (context, constraints) {
            print("Container约束:" + constraints.toString());
            return Container(width: 20, height: 20, color: Colors.orange);
          }));
    }));
  }

运行效果图: 

控制台输出:

flutter: body约束:BoxConstraints(0.0<=w<=390.0, 0.0<=h<=844.0)
flutter: Container约束:BoxConstraints(w=300.0, h=300.0)

由此得出结论:

  • body对子控件是loose松约束(子控件可以设置指定宽高);
  • Container对子控件是tight强约束(如果子控件不指定位置怎么摆放的话,因为不指定位置的话系统也不知道子控件放大或者缩小后该怎么摆放,索性填充满父控件,设置宽高自然会失效),本质上是子控件违背了父控件的约束,所以一律按照父控件约束执行
  • 总结:当设置子控件大小的时候如果不听使唤可以用LayoutBuilder(builder: (context, constraints){})打印下看看是tight还是loose

布局原理:

每个组件在渲染之前的布局过程具体可分为两个线性过程。首先从组件顶部向下传递布局约束,然后从底部向上传递布局信息。

简单点说就是flutter在布局的时候会遍历组件树 ,从根部开始(deep first 深度优先原则)向下传递约束,当子控件约束违背父控件约束的时候会执行父控件约束。flutter的布局是onePass只需要遍历WidgetTree一遍,向上传递尺寸,最后由父级得到尺寸再决定把children放在哪里

这两个线性过程会在元素树所引用的RenderObject树中完成,并且最终的布局信息将保存在RenderObject中。因此,当重新构建组件时,如果元素和RenderObject能够复用,那么同样可以使用和上次一样的布局信息。这种单向传递和保存信息的方式是Flutter布局性能优于其他框架的重要原因之一。

RenderObject树由一个个RenderObject组合而成。当Element实例挂载到元素树上后,就会调用组件的createRenderObject()方法生成对应的RenderObject。由于RenderObject树被元素树引用,并且主要任务就是帮助Element实例做具体的渲染工作,因此RenderObject树也常称为元素树的子树。

 每个RenderObject会被元素持有,并且在组件重建后会尽量复用,每当元素中的状态发生改变时,就会调用组件的updateRenderObject()方法更新渲染对象,屏幕上的值最终得以更新。

2、自定义盒子布局约束

 @override
  Widget build(BuildContext context) {
    return Scaffold(body: LayoutBuilder(builder: (context, constraints) {
      print("body约束:" + constraints.toString());
      return Container(
        constraints: BoxConstraints(
            minWidth: 60, minHeight: 60, maxWidth: 100, maxHeight: 100),
        child: LayoutBuilder(builder: (context, constraints) {
          print("Container约束:" + constraints.toString());
          return FlutterLogo(size: 500);
        }),
      );
    }));
  }

flutter: body约束:BoxConstraints(0.0<=w<=390.0, 0.0<=h<=844.0)
flutter: Container约束:BoxConstraints(60.0<=w<=100.0, 60.0<=h<=100.0)

3.Stack层叠组件

  1. (children里既有普通Widget又有Position包裹的Widget),其大小是由children中没有被Position包裹的Widget中的最大个子组件决定,(被Position包裹的子组件有点类似于前端布局中的absolute绝对布局会脱离文档流)可以超出父组件显示,不想显示也可以设置clipBehavior: Clip.hardEdge,强制裁剪超出部分。
  2. (children里全部是普通的Widget),其大小是包裹着子组件
  3. children里全部是Position包裹的Widget,就没有参照物不知道该怎么布局)Stack的大小会充满父组件尽可能的大,(因为只有Stack自身足够大对应的children设置左上对齐或者其他对齐才会有意义,不然会挤在一起失去了原有设置对齐的意义)

4、CustomMultiChildLayout

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: CustomMultiChildLayout(
      delegate: MyDelegate(4),
      children: [
        LayoutId(
            id: "text",
            child: Text(
                "测试文字下划线测试文字下划线测试文字下划线测试文字下划线测试文字下划线测试文字下划线测试文字下划线测试文字下划线")),
        LayoutId(
            id: "underline",
            child: Container(
              color: Colors.green,
            ))
      ],
    ));
  }

class MyDelegate extends MultiChildLayoutDelegate {

final double thickness;

MyDelegate(this.thickness);



@override

Size getSize(BoxConstraints constraints) {

return super.getSize(constraints);

}

@override

void performLayout(Size size) {

final sizeText = layoutChild("text", BoxConstraints.loose(size));



final sizeUnderline =

layoutChild("underline", BoxConstraints.tight(Size(sizeText.width, thickness)));



final left = (size.width - sizeText.width) / 2;

final top = (size.height - sizeText.height) / 2;

positionChild("text", Offset(left, top));

positionChild("underline", Offset(left, top + sizeText.height));

}



@override

bool shouldRelayout(covariant MultiChildLayoutDelegate oldDelegate) => true;

}

 5、RenderObject

 @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Container(
            color: Colors.red,
            child: ShadowBox(child: FlutterLogo(size: 200), distance: 200)));
  }


//SingleChildRenderObjectWidget一种能真正画到屏幕上的widget
class ShadowBox extends SingleChildRenderObjectWidget {
  double distance;
  ShadowBox({Widget child, this.distance}) : super(child: child);

  @override
  RenderObject createRenderObject(BuildContext context) {
    return RenderShadowBox(distance);
  }

  @override
  void updateRenderObject(
      BuildContext context, covariant RenderShadowBox renderObject) {
    renderObject.distance = distance;
  }
}

//RenderObjectWithChildMixin比较方便的设置一个child
class RenderShadowBox extends RenderProxyBox with DebugOverflowIndicatorMixin {
  double distance;

  RenderShadowBox(this.distance);

  //处理布局
  // @override
  // void performLayout() {
  //   child.layout(constraints, parentUsesSize: true); //parentUsesSize: false
  //   // 如果parentUsesSize: false则parent的size跟这个child没关系以后再relayout凡是涉及到这个child的不会再更新,如果是true则代表会使用这个child
  //   size = (child as RenderBox).size; // relayout boundary
  //   // size = Size(300, 100);
  // }

  //处理绘制
  //其实layout和paint各遍历一次
  @override
  void paint(PaintingContext context, Offset offset) {
    context.paintChild(child, offset);
    context.canvas.clipRRect(RRect.fromLTRBAndCorners(105, 5, 5, 5));
    //建立新图层,可以在新图层上做各种操作
    context.pushOpacity(offset, 100, (context, offset) {
      context.paintChild(child, offset + Offset(distance, distance));
    });
    //containerRect: 能画的范围
    //childRect: 想让画的范围
    //警戒线
    paintOverflowIndicator(
        context,
        offset,
        Offset.zero & size, //Rect.fromLTWH(0, 0, size.width, size.height)
        Offset.zero & child.size);
  }
}

猜你喜欢

转载自blog.csdn.net/RreamigOfGirls/article/details/130989686
今日推荐