Flutter布局约束

Flutter进行布局时,上层 widget 会向下层 widget 传递一个尺寸约束,下层 widget 不能违反父级尺寸约束。当子级约束和父级约束有冲突时,子级约束无效,按照父级约束。

什么是布局约束?

Flutter进行布局时,上层 widget 会向下层 widget 传递一个尺寸约束,下层 widget 不能违反父级尺寸约束。当子级约束和父级约束有冲突时,子级约束无效,按照父级约束。(爸爸说的才算数。。。😂)

怎么获取布局约束?

对布局约束不明确可以用LayoutBuilder去获取上层传递的约束,也可以在看一下Flutter布局(二)常见布局组件的布局约束。

LayoutBuilder(
  builder: (context, constraints) {
  print('$constraints');
  return Text(
    'HomeView is working',
    );
  }
)

严格约束(Tight) vs 宽松约束(loose)

严格约束:宽高的最小值和最大值相等。

BoxConstraints.tight(Size size)
   : minWidth = size.width,
     maxWidth = size.width,
     minHeight = size.height,
     maxHeight = size.height;

宽松约束: 宽高最小值都是0。

BoxConstraints.loose(Size size)
   : minWidth = 0.0,
     maxWidth = size.width,
     minHeight = 0.0,
     maxHeight = size.height;

在宽松约束下,子级约束可以在约束区间内自由定义;在严格约束下,子级必须遵守此约束。

布局约束流程

首先,上层 widget 向下层 widget 传递约束条件。 然后,下层 widget 向上层 widget 传递大小信息。 最后,上层 widget 决定下层 widget 的位置。 一句话就是向下传递约束,向上传递尺寸。

为了加深我们对Flutter布局约束的理解,下面用CustomMultiChildLayout来实现一个自定义布局的例子。

CustomMultiChildLayout(
  delegate: ColumnDelegate(),
  children: [
    //Z-Order类似Stack,越往后的子级显示在最上面
    LayoutId(
      id: 1,
      child: Container(width: 10, height: 10, color: Colors.amber),
    ),
    LayoutId(
      id: 2,
      child: Container(width: 10, height: 10, color: Colors.amber),
    ),
    LayoutId(
      id: 3,
      child: Container(width: 10, height: 10, color: Colors.amber),
    ),
  ],
)
​
class ColumnDelegate extends MultiChildLayoutDelegate {
  //自定义布局实现
  @override
  void performLayout(Size size) {
    //向下传递约束BoxConstraints.loose(size),返回子级的尺寸
    var firstSize = layoutChild(1, BoxConstraints.loose(size));
    //设置子级的位置
    positionChild(1, const Offset(0, 0));
    var secondSize = layoutChild(2, BoxConstraints.loose(size));
    positionChild(2, const Offset(0, firstSize.height));
    layoutChild(3, BoxConstraints.loose(size));
    positionChild(3, const Offset(0, secondSize.height));
  }
​
  //是否需要重新调用performLayout
  @override
  bool shouldRelayout(covariant MultiChildLayoutDelegate oldDelegate) {
    return true;
  }
​
  //组件尺寸大小
  @override
  Size getSize(BoxConstraints constraints) {
    return constraints.smallest;
  }
}

下面举一个实际的布局约束流程:

Scaffold(
  body: Container(
    width: 300,
    height: 300,
    child: ConstrainedBox(
      constraints: BoxConstraints.loose(const Size.square(200)),
      child: SizeBox(
        width: 200,
        height: 200,
        child: Column(
          children: const [
            SizedBox.expand(),//SizedBox1
            SizedBox(width: 100,height: 100),//SizedBox2
            SizedBox(width: 100,height: 100),//SizedBox3
          ],
        )
      ),
    ),
  ),
)
  • Scaffold : 向下级传递一个宽松约束BoxConstraints(0.0<=w<=屏幕宽度, 0.0<=h<=屏幕高度)

  • Container :向下级传递一个严格约束BoxConstraints(w=300, h=300)

  • ConstrainedBox :上级传递的是一个严格约束,所以自己定义的布局约束无效,向下级传递一个严格约束BoxConstraints(w=300, h=300)

  • SizeBox : 上级传递的是一个严格约束,所以自己定义的布局约束无效,向下级传递一个严格约束BoxConstraints(w=300, h=300)

  • Column :SizedBox1是弹性控件,暂时无法指定约束,先跳过;向SizedBox2传递一个宽松约束BoxConstraints(0.0<=w<=300, 0.0<=h<=Infinity);向SizedBox3传递一个宽松约束BoxConstraints(0.0<=w<=300, 0.0<=h<=Infinity)。

  • SizedBox3 :向上传递其尺寸是BoxConstraints(w=100, h=100)。

  • SizedBox2 :向上传递其尺寸是BoxConstraints(w=100, h=100)。

  • Column : 所有的非弹性控件的尺寸知道了,向SizedBox1传递约束BoxConstraints(0.0<=w<=300, 0.0<=h<=100)。

  • SizedBox1 :向上传递其尺寸是BoxConstraints(w=300, h=100)。

  • Column : 向上传递其尺寸是BoxConstraints(w=300, h=300)。

  • SizeBox : 向上传递其尺寸是BoxConstraints(w=300, h=300)。

  • ConstrainedBox : 向上传递其尺寸是BoxConstraints(w=300, h=300)。

  • Container : 向上传递其尺寸是BoxConstraints(w=300, h=300)。

  • Scaffold :向上传递其尺寸是BoxConstraints(w=屏幕宽度, h=屏幕高度)。

官网上的举例分析都是错的,太难了。。。😂

对Flutter布局约束的理解

子级约束由父级约束指定这种责任链的设计方式,主要是为了让子级能够在父级范围内有效的显示,这个和Android中的布局也是比较相像的。

常见的容器类组件

直接或间接继承SingleChildRenderObjectWidget和MultiChildRenderObjectWidget的组件。

Widget说明用途
LeafRenderObjectWidget非容器类组件基类Widget树的叶子节点,用于没有子节点的widget,通常基础组件都属于这一类,如Image。
SingleChildRenderObjectWidget单子组件基类包含一个子Widget,如:ConstrainedBox、DecoratedBox等
MultiChildRenderObjectWidget多子组件基类包含多个子Widget,一般都有一个children参数,接受一个Widget数组。如Row、Column、Stack等

下面介绍几种常见的容器类组件它们是如何传递约束的,只研究影响组件布局的属性,其他属性不做过多研究。

Scaffold

其宽高是由系统指定,严格约束其宽高为屏幕大小,向下传递宽松约束,向上传递屏幕尺寸。

Scaffold(
  //body约束为BoxConstraints(0.0<=w<=屏幕宽度, 0.0<=h<=屏幕高度)
  body: Container(
    color: blue,
  ),
)

ConstrainedBox

向下传递严格布局,向上传递指定的尺寸。

Scaffold(
  body: ConstrainedBox(
    constraints: BoxConstraints.tight(const Size.square(100)),
    //child约束为BoxConstraints(w=100, h=100)    
    child: Container(
      color: Colors.blue,
    ),
  )
)

向下传递宽松布局,向上传递子级尺寸。

Scaffold(
  body: ConstrainedBox(
    constraints: BoxConstraints.loose(const Size.square(100)),
    //child约束为BoxConstraints(0.0<=w<=100, 0.0<=h<=100)    
    child: Container(
      color: Colors.blue,
    ),
  )
)

Sizebox

向下传递严格约束,向上传递指定的尺寸。

Scaffold(
  body: SizedBox(
  	width: 100,
  	height: 100,
    //child约束为BoxConstraints(w=100, h=100)  
  	child: Container(
      color: blue,
  	)
  )
)

实际上SizedBox只是ConstrainedBox的一个定制,两者都是通过RenderConstrainedBox来渲染的,上面SizeBox的代码等价于:

ConstrainedBox(
  constraints: BoxConstraints.tightFor(width: 100,height: 100),
  child: container, 
)

UnconstrainedBox

向下传递无限约束BoxConstraints(unconstrained),向上传递子级的尺寸,如果没有子级相同的子级的尺寸是0。

Scaffold(
  body: Container(
  	width: 100,
  	height: 100,
  	child: UnconstrainedBox(
      //child约束为BoxConstraints(unconstrained)
      child: Container(
        color: blue,
        width: 200,//宽度超过父控件,会显示越界异常
        height: 100,
  	  )
  	)
  )
)

Align

向下传递宽松约束,向上传递父控件的最大尺寸(Align默认是一种 expand 效果)。和UnconstrainedBox不同,其布局约束在其父级约束范围内,而UnconstrainedBox是没有任何约束,所以去掉严格约束限制建议使用Align。

Scaffold(
  body: Container(
  	width: 100,
  	height: 100,
  	child: Align(
      //左上角
      alignment: Alignment(-1,1)
      //child约束为BoxConstraints(0.0<=w<=100, 0.0<=h<=100)
      child: Container(
    	color: blue,
  	  )
  	)
  )
)

Padding

将其布局约束减去padding向下传递,向上传递子级加padding的尺寸。

Scaffold(
  body: SizedBox(
  	width: 100,
  	height: 100,
  	child: Padding(
      padding: const EdgeInsets.all(10),
      //child约束为BoxConstraints(w=80, h=80)  
      child: Container(
        color: Colors.blue,
      ),
    )
  )
)

Container

指定宽高,向下传递严格约束,向上传递指定的尺寸。

Scaffold(
  body: Container(
  	width: 100,
  	height: 100,
  	//child约束为BoxConstraints(w=100, h=100)
  	child: Container(
      color: blue,
  	)
  )
)

没指定任何约束条件且没有子级,如果是无界约束向上传递尺寸是0 ,如果是有界约束向上传递约束的最大尺寸。(Container里面封装了LimitedBox,没有子级是默认子级是BoxConstraints.expand())

Scaffold(
  //向上传递屏幕大小的尺寸
  body: Container(
  	 color: blue,
  )
)

没指定任何约束条件,有子级,向上传递子级的尺寸。(所以可以用Container查看组件的尺寸)

Scaffold(
  //向上传递200*200尺寸
  body: Container(
  	 color: blue,
     child: Container(
       width: 200,
       height: 200,
     ),
  )
)

指定 alignment 属性同 Align 控件约束。 指定 padding 属性同 Padding 控件约束。 指定 constraints 属性同 ConstraintsBox 控件约束。

LimitedBox

当父级是无界约束时向下传递备用约束。

Scaffold(
  body: Column(
    children: [
      //child纵轴是无界约束,LimitedBox尺寸是屏幕宽*0
      LimitedBox(
        maxWidth: 0,//w是无界约束时备用最大宽是0
        maxHeight: 0,//h是无界约束时备用最大高是0
        child: ConstrainedBox(constraints: const BoxConstraints.expand()),
      )
    ]
  )
)

Column

设置 crossAxisAlignment 为 CrossAxisAlignment.stretch 时,纵轴向下传递严格约束。 mainAxisSize为 MainAxisSize.min时,向上传递的尺寸是BoxConstraints(w=屏幕宽度, h=子级高度之和);mainAxisSize为 MainAxisSize.max时,向上传递的尺寸是BoxConstraints(w=屏幕宽度, h=父控件高最大值)。(注意:上面的Column是在宽松约束下,如果是在严格约束下mainAxisSize也不起作用,向上传递的尺寸也将是严格约束的尺寸)

Scaffold(
  body: Column(
    //纵轴设置拉伸
    crossAxisAlignment: CrossAxisAlignment.stretch,
    children: [
      //child约束为BoxConstraints(w=屏幕宽度, 0.0<=h<=Infinity)
      Container(
        color: Colors.blue,
      ),
    ],
  )
)

其他值向下传递宽松约束。 mainAxisSize为 MainAxisSize.min时,向上传递的尺寸是BoxConstraints(w=子级宽度最大值, h=子级高度之和);mainAxisSize为 MainAxisSize.max时,向上传递的尺寸是BoxConstraints(w=子级宽度最大值, h=父控件高最大值)。

Scaffold(
  body: Column(
    //纵轴设置居中
    crossAxisAlignment: CrossAxisAlignment.center,
    children: [
      //child约束为BoxConstraints(0.0<=w<=屏幕宽度, 0.0<=h<=Infinity)
      Container(
        color: Colors.blue,
      ),
    ],
  )
)

弹性子级 主轴方向,Column会先向非弹性子级传递一个无界约束,非弹性子级返回尺寸后,再向弹性子级(Flexiable)传递一个严格约束。

Scaffold(
  body: Column(
    crossAxisAlignment: CrossAxisAlignment.center,
    children: [
      Flexible(
        flex: 1,
        //child约束为BoxConstraints(0.0<=w<=屏幕宽度, h=屏幕高度)
        child: Container(
          color: Colors.blue,
        ),
      ),
    ],
  )
)

为什么经常会出现超出边界异常? 因为Column无法事先知道有几个子级,也无法知道每个控件的尺寸,为了尽可能展示子级的显示效果,向固定子级传递的约束是 0.0<=h<=Infinit,子级在主轴方向可以随意定义尺寸大小,导致超出父控件Column的约束。

ListView放到Column为什么会报异常? ListView在主轴方向需要一个有界约束,但是直接把ListView放到Column中是一个无界约束,所以需要把ListView放到一个弹性(Flexiable)布局中。

Stack

先布局非位置的组件,当非位置之间布局完成确定Stack尺寸后,再去布局位置组件(Positioned)。

全是位置子级的情况 向下传递宽松约束,向上传递约束的最大尺寸。

Scaffold(
  //向上传递尺寸是屏幕的尺寸
  body: Stack(
    alignment: Alignment.center,
    //children约束为BoxConstraints(0.0<=w<=屏幕宽度, 0.0<=h<=屏幕高度)
    children: [
      Position(
        top: 0,
        left: 0,
        //child约束为BoxConstraints(unconstrained)
        child: Container(
          color: Colors.blue,
          width: 200,
          height: 200,
        ),
      ),
      Position(
        top: 0,
        left: 0,
        child: Container(
          color: Colors.red,
          width: 100,
          height: 100,
        ),
      ),
    ],
  )
)

不全是位置子级的情况 fit为StackFit.expand时,向下传递严格约束,向上传递非位置子级尺寸的最大值。

var children = [
      Position(
        top: 50,
        left: 50,
        child: Container(
          color: Colors.blue,
          width: 100,
          height: 100,
        ),
      ),
      Container(
        color: Colors.red,
        width: 100,
        height: 100,
      ),
      Container(
        color: Colors.amber,
        width: 200,
        height: 200,
      ),
];
Scaffold(
  //向上传递尺寸是屏幕尺寸
  body: Stack(
    fit: StackFit.expand,
    alignment: Alignment.center,
    //children约束为BoxConstraints(w=屏幕宽度, h=屏幕高度)
    children: children,
  )
)

fit为StackFit.loose时,向下传递宽松约束,向上传递非位置子级尺寸的最大值。

Scaffold(
  //向上传递尺寸是200*200
  body: ConstrainedBox(
    constraints: BoxConstraints(minWidth: 100,minHeight: 100)
    child: Stack(
      fit: StackFit.loose,
      alignment: Alignment.center,
      //children约束为BoxConstraints(0.0<=w<=屏幕宽度, 0.0<=h<=屏幕高度)
      children: children,
    )
  )
)

fit为StackFit.passthrough时,向下传递父级约束,向上传递非位置子级尺寸的最大值。

Scaffold(
  //向上传递尺寸是200*200
  body: ConstrainedBox(
    constraints: BoxConstraints(minWidth: 100,minHeight: 100)
    child: Stack(
      fit: StackFit.loose,
      alignment: Alignment.center,
      //children约束为BoxConstraints(100<=w<=屏幕宽度, 100<=h<=屏幕高度)
      children: children,
    )
  )
)

(注意:上述情况当Stack父级是固定尺寸时除外,此时Stack尺寸是固定的,不受子级尺寸的影响)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值