先看一下,下面这段代码:
body: Container(
width: 400,
height: 400,
color: Colors.blue,
child: Container(
width: 100,
height: 100,
color: Colors.orange,
child: const FlutterLogo(
size: 50,
),
),
),
我们:想要的是 400*400 蓝色底,100*100橘色的底, 50*50 FlutterLoge, 而实际情况确是这样:
究其原因是因为:上面的组件没有满足 Flutter的约束条件:会被修正,可以理解为 child width 和height 只是个建议尺寸,实际的尺寸 是有 父级对象 决定的!
- Flutter 向下传递约束
- Flutter 向上传递建议尺寸
- 最后父级通过 位置计算得到 每个 child尺寸,在决定把 child 放在那里
经过简单的处理之后 我们得到了应该有的布局
body: Container(
width: 400,
height: 400,
color: Colors.blue,
child: Align(
//alignment: Alignment(0.5,0.5)等价于 alignment: Alignment.center,
// alignment: Alignment.bottomCenter,
child: Container(
width: 250,
height: 250,
color: Colors.orange,
//没有规定child 放在那里,所有只能 放满全屏,如果 添加一个Center/Align 让父组件怎么 如何摆放child,这时就能确定 child位置和 尺寸
//Center 继承自 Align
child: Center(
child: const FlutterLogo(
size: 50,
),
),
),
),
),
body: Container(
width: 400,
height: 400,
color: Colors.blue,
child: Align(
//alignment: Alignment(0.5,0.5)等价于 alignment: Alignment.center,
// alignment: Alignment.bottomCenter,
child: Container(
width: 250,
height: 250,
color: Colors.orange,
//没有规定child 放在那里,所有只能 放满全屏,如果 添加一个Center/Align 让父组件怎么 如何摆放child,这时就能确定 child位置和 尺寸
//Center 继承自 Align
child: Center(
child: const FlutterLogo(
size: 50,
),
),
),
),
),
layout 获取和设置 布局约束
- 紧约束
constraints:BoxConstraints(w=350.0, h=350.0)
- 松约束 (最小值为0的约束 )
BoxConstraints(0.0<=w<=350.0, 0.0<=h<=350.0)
下面的代码中 FlutterLoge() 设置的 size: 9999,是没有效果的,会被父层级Container 修正
body: Center(
child: Container(
width: 350,
height: 350,
color: Colors.orangeAccent,
child: FlutterLogo(
size: 9999,
),
),
)
使用 LayoutBuilder 查看父层级的约束:
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
print("constraints:$constraints");
return const FlutterLogo(
size: 9999,
);
},
)
打印结果为一个: 紧约束,所有这里不管我们怎么 设置子组件尺寸都是 没有用的,父组件会强制设置子组件的尺寸为 350*350
I/flutter (13229): constraints:BoxConstraints(w=350.0, h=350.0)
我们给 LayoutBuilder 包裹一个 Center 组件, 可以改变父组件的 约束,成为一个 松约束,让后告诉父组件我们要摆放的位置;就能实现控制子组件的大小了
constraints:BoxConstraints(0.0<=w<=350.0, 0.0<=h<=350.0)
body: Center(
child: Container(
width: 350,
height: 350,
color: Colors.orangeAccent,
child: Center(
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
print("constraints:$constraints");
return const FlutterLogo(
size: 50,
);
},
),
),
),
),
ConstrainedBox,修改我们的约束, 前提是 我们当前组件满足父组件的约束
- 先将我们的约束设置为 松约束,如果不设置,我么的定义约束还是会被父组件 强制修改
- 定定义我们的约束
body: Center(
child: Container(
width: 350,
height: 350,
color: Colors.orangeAccent,
child: Center( //1 先将我们的约束设置为 松约束
child: ConstrainedBox(
constraints: const BoxConstraints( //2 定定义我们的约束
minWidth: 50, maxWidth: 250, minHeight: 50, maxHeight: 250),
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
print("constraints:$constraints");
return const FlutterLogo(
size: 50,
);
},
),
),
),
),
),
上面代码中如果我们不使用,Center组件(不修改 强制约束 为 松约束),那么 ConstrainedBox 是不会起作用的!
//错误示范
body: Center(
child: Container(
width: 350,
height: 350,
color: Colors.orangeAccent,
//1 先将我们的约束设置为 松约束;负责不起作用
child: ConstrainedBox(
constraints: const BoxConstraints( //2 定定义我们的约束
minWidth: 50, maxWidth: 250, minHeight: 50, maxHeight: 250),
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
print("constraints:$constraints");
return const FlutterLogo(
size: 50,
);
},
),
),
),
),
layout flex弹性布局:
Column 或者 Row 中嵌入 ListView: 报错这个现象
body: Center(
child: Container(
color: Colors.orange,
width: 250,
child: Column(
// mainAxisSize: MainAxisSize.max,
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ElevatedButton(onPressed: () {}, child: Text("")),
ListView(
children: [for (int i = 0; i < 10; i++) Text("List view $i")],
),
ElevatedButton(onPressed: () {}, child: Text("")),
],
),
),
),
RenderBox was not laid out: RenderRepaintBoundary#341bb relayoutBoundary=up5 NEEDS-PAINT
'package:flutter/src/rendering/box.dart':
Failed assertion: line 1929 pos 12: 'hasSize'
上面代码当中:
Column 元素会首先布局 固定大小(有约束限制)的组件:其中 ElevatedButton组件高度固定,
Listview 组件是一个弹性组件高度无限制,而 Column 也是一个高度无限制的组件;所以就会出错!在这里给ListView 包裹一层就能解决这个问题!这里归根结底的原因就是:Column 本身是一个无限制的元素,而子元素 中除过 ListView 也是一个无限制元素;两个无限制元素同时出现时 Flutter就无法确定 元素大小就会报错
I/flutter (11566): constraints:BoxConstraints(0.0<=w<=250.0, 0.0<=h<=Infinity)
所以 当无约束的空间 嵌套无约束的空间,就是出异常,在这里给 ListView 包裹 Expand组件就能决绝 这个问题:Expand占剩余空间 大小确定
body: Center(
child: Container(
color: Colors.orange,
width: 250,
child: Column(
// mainAxisSize: MainAxisSize.max,
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ElevatedButton(onPressed: () {}, child: Text("")),
Expanded(
child: ListView(
children: [
for (int i = 0; i < 100; i++) Text("List view $i")
],
),
),
ElevatedButton(onPressed: () {}, child: Text("")),
],
),
),
),
layout Stack 层叠:影响 Stack 组件大小的因素
在 Stack 布局中 有一个有意思的现象
1 子组件中 有Positioned 组件 和 其他组件的情况下 Stack 组件大小由 最大 非Positioned 组件 决定;如下代码中 则是由 最长 Text 组件决定的
body: Center( child: Container( color: Colors.blue, child: Stack( // alignment: Alignment.center, alignment: Alignment(-1, 0.75), children: [ Text( 'You have You have You', style: TextStyle(fontSize: 20, color: Colors.orange), ), Text( 'GoGoGO', style: TextStyle(fontSize: 15, color: Colors.red), ), Positioned( left: 0, top: 0, child: FlutterLogo( size: 250, ), ), Positioned( left: 0, top: 0, child: Container( width: 24, height: 24, color: Colors.black, ), ), ], ), ), ),
2
子组件中 都是 Positioned 组件,那么 Stack 的尺寸则会占满父组件 最大约束
之所以会有这样的现象 主要是应为:本身Stack 组件为 用户实现层叠样式实现的,如果有其他组件(非Positioned)那么State 就有参照!所以可以按照参照进行布局,如果全部都是 Positoned 则没有参照;所以会 占满父组件;
当然也可通过 Satck fit 属性进行设置:修改默认规则
child: Stack( //fit: StackFit.expand,//最大适配子元素 fit:StackFit.loose,//最小适配元素 //fit:StackFit.passthrough,//直接把 Stack 父组件的约束传递给子组件们
body: Center( child: Container( color: Colors.blue, child: Stack( fit: _isSelect ? StackFit.expand : StackFit.loose, // fit: StackFit.loose, //最小适配 // fit: StackFit.expand, //最大适配 // alignment: Alignment.center, alignment: Alignment(-1, 0.75), children: [ Text("Stack 影响组件大小的 因素"), Positioned( left: 0, top: 0, child: FlutterLogo( size: 250, ), ), Positioned( left: 0, top: 0, child: Container( width: 24, height: 24, color: Colors.black, ), ), Switch( value: _isSelect, onChanged: (value) { setState(() { _isSelect = value; }); print('Switch--------value:$value'); print('Switch--------_isSelect:$_isSelect'); }, ), ], ), ), ),
Stack 超出显示和裁切:超出部分 虽然可以显示出来,但是不能响应 用户点击事件
- overflow: Overflow.visible
- clipBehavior: Clip.none,//超出部分 虽然可以显示出来,但是不能响应 用户点击事件
body: Center( child: Container( color: Colors.blue, constraints: const BoxConstraints( maxWidth: 350, minWidth: 10, maxHeight: 350, minHeight: 10, ), child: Stack( //alignment: Alignment.bottomCenter, overflow: Overflow.visible, clipBehavior: Clip.none,//超出部分 虽然可以显示出来,但是不能响应 用户点击事件 children: [ FlutterLogo(size: 250), Positioned( left: 20, top: -20, width: 250, height: 50, child: Container( width: 24, height: 24, color: Colors.orange, ), ), ], ), ), ),
layout 决定 Container 组件大小的因素
当 Container 没有 子组件 的时候,自身尺寸 占满父组件最大约束;
当 Contaiber 有 子组件 的时候,自身尺寸 就是子组件的尺寸
*当 Contaiber 有对齐属性的时候:Contaiber 自身尺寸 占满父组件最大约束;(对齐属性)类似包裹了一层 layout
*当 Contaiber 父组件是类似 Column/Row/ListView 这种无边界约束的时候 unborder,因为不能确定约束的最大的值! 这时候 Contaiber 会取约束的最小值没有 子组件 的时候:
body: Center( child: Container( color: Colors.orange, // child: FlutterLogo(size: 100), ), ),
有子组件 的时候
body: Center( child: Container( color: Colors.orange, child: FlutterLogo(size: 100), ), ),
右对齐属性:
body: Container( alignment: Alignment.center, color: Colors.orange, child: const FlutterLogo( size: 50, ), ),
Column/Row/ListView 这种无边界约束的时候:
body: Column( children: [ Container( // alignment: Alignment.center, color: Colors.orange, child: const FlutterLogo( size: 50, ), ) ], ),
当然也通过可以修改 Container 的 constraints 改变父组件约束,来修 占满父组件最大约束
body: Center( child: Container( constraints: const BoxConstraints( maxHeight: 250, minHeight: 10, maxWidth: 250, minWidth: 10, ), color: Colors.orange, // child: FlutterLogo(size: 30), ), ),
如果有 约束传递/多组件约束叠加 的情况发生,那么取 约束的交集
Center 组件是一个 松约束 范围 width: 0 到 屏幕宽; height: 0 到 屏幕高
SizedBox 强制宽度到 200;高度没有 影响
Container :受父组件约束 高度200,受子组件影响 宽度 100
FlutterLog : 宽100 高100;
所以在这里 Container 组件的大小 实际上 受到多个组建的叠加影响!body: Center( //松约束 0-屏幕宽高 child: SizedBox( width: 200, // 宽度紧约束 child: Container( //页数叠加: 宽度由SizeBox决定, 由于Container 又子组件高度为 子组件的高度 200*100 color: Colors.orange, child: FlutterLogo(size: 100), //子组件 紧约束 100*100 ), ), ),
本质上:Container组件 类以一个语法糖的写法;里面的很多属性 都是一个一个的 类型去实现的:等等
如果有 alignment 属性则会包裹一层 Align(alignment: alignment!, child: current);
如果有 color 属性,则会包裹一层 ColoredBox(color: color!, child: current);
他的部分源码:
@override Widget build(BuildContext context) { Widget? current = child; if (child == null && (constraints == null || !constraints!.isTight)) { current = LimitedBox( maxWidth: 0.0, maxHeight: 0.0, child: ConstrainedBox(constraints: const BoxConstraints.expand()), ); } if (alignment != null) current = Align(alignment: alignment!, child: current); final EdgeInsetsGeometry? effectivePadding = _paddingIncludingDecoration; if (effectivePadding != null) current = Padding(padding: effectivePadding, child: current); if (color != null) current = ColoredBox(color: color!, child: current); if (clipBehavior != Clip.none) { assert(decoration != null); current = ClipPath( clipper: _DecorationClipper( textDirection: Directionality.maybeOf(context), decoration: decoration!, ), clipBehavior: clipBehavior, child: current, ); } if (decoration != null) current = DecoratedBox(decoration: decoration!, child: current); if (foregroundDecoration != null) { current = DecoratedBox( decoration: foregroundDecoration!, position: DecorationPosition.foreground, child: current, ); } if (constraints != null) current = ConstrainedBox(constraints: constraints!, child: current); if (margin != null) current = Padding(padding: margin!, child: current); if (transform != null) current = Transform(transform: transform!, alignment: transformAlignment, child: current); return current!; }