父组件向下传递约束,子组件向上传送大小。
1、收紧约束
ConstrainedBox 可以改变约束,但必须符合父组件约束前提,也就是说只能收紧约束。
SizedBox 是 ConstrainedBox 的一个定制,设置为紧约束,等价于:
ConstrainedBox(
constraints: BoxConstraints.tightFor(width: 80.0,height: 80.0),
child: _child,
)
2、放松约束
UnconstrainedBox 可以在其子组件布局时取消当前约束(子组件可以为无限大, 但子组件的占用空间还是受原约束的限制。),但 UnconstrainedBox 自身是受其父组件约束的,所以当 UnconstrainedBox 随着其子组件变大后,如果UnconstrainedBox 的大小超过它父组件约束时,也会导致溢出报错。
Align/Center 可以将紧约束变为松约束。
3、常见问题分析
在使用 SizedBox/ConstrainedBox 给子元素指定固定宽高时,如果没有效果,说明与当前父组件指定的约束有冲突。比如 AppBar 中的 actions 里使用 SizedBox 就没有效果,这时要么使用 UnconstrainedBox ,要么使用 Align/Center。
还有一些其他的尺寸限制类容器,比如AspectRatio,它可以指定子组件的长宽比、LimitedBox 用于指定最大宽高、FractionallySizedBox 可以根据父容器宽高的百分比来设置子组件宽高等。
mainAxisSize:表示Row在主轴(水平)方向占用的空间,默认是MainAxisSize.max,表示尽可能多的占用水平方向的空间,此时无论子 widgets 实际占用多少水平空间,Row的宽度始终等于水平方向的最大宽度;而MainAxisSize.min表示尽可能少的占用水平空间,当子组件没有占满水平剩余空间,则Row的实际宽度等于所有子组件占用的水平空间。
mainAxisAlignment:表示子组件在Row所占用的水平空间内对齐方式,如果mainAxisSize值为MainAxisSize.min,则此属性无意义,因为子组件的宽度等于Row的宽度。只有当mainAxisSize的值为MainAxisSize.max时,此属性才有意义。
Expanded 只能作为 Flex 的孩子(否则会报错),它可以按比例“扩伸”Flex子组件所占用的空间。一个Flex中可以有多个Expaned,它们共同分配主轴的全部空闲空间,分配的比例由 Expaned 的 flex 参数决定,当然了也可以只有一个 Expaneded 组件,独自占用全部空闲空间。Spacer 是 Expaneded 的一个包装类,如果不需要子组件,就可以直接使用 Spacer 来替代。
可以认为Wrap和Flex(包括Row和Column)除了超出显示范围后Wrap会折行外,其他行为基本相同。
我们一般很少会使用Flow,因为其过于复杂,需要自己实现子 widget 的位置转换,在很多场景下首先要考虑的是Wrap是否满足需求。
如果我们需要自定义布局策略,一般首选的方式是通过直接继承RenderObject,然后通过重写 performLayout 的方式实现。
Stack 组件里使用 Positioned 可以进行定位,如果没有使用 Positioned 进行横坐标轴和纵坐标轴定位的子组件,会按照 aligment 属性进行定位(比如 Positioned 只定位了横轴,那么纵轴就按照 aligment 属性定位),fit 属性用于确定没有定位的子组件如何去适应Stack的大小。StackFit.loose表示使用子组件的大小,StackFit.expand表示扩伸到Stack的大小。
通过Stack和Positioned,我们可以指定一个或多个子元素相对于父元素各个边的精确偏移,并且可以重叠。但如果我们只想简单的调整一个子元素在父元素中的位置的话,使用Align组件会更简单一些。
Align组件的widthFactor和heightFactor是用于确定Align 组件本身宽高的属性;它们是两个缩放因子,会分别乘以子元素的宽、高,最终的结果就是Align 组件的宽高。如果值为null,则组件的宽高将会占用尽可能多的空间。
Align 组件是通过 alignment 属性进行定位,除了可以使用 Alignent枚举之外,还可以直接使用 Alignent 实例来精确设置,以矩形的中心点为坐标原点,x表示从左到右,y表示从上到下,取值范围是-1~1,比如 (1.0,1.0) 表示的就是右下的顶点,(-1.0,1.0)表示的是左下的顶点,这种坐标系不好记,一般使用Alignent的子类 FractionalOffset 来替代,它的坐标原点是左上角,同样x是从左到右,y是从上到下。
Align和Stack/Positioned都可以用于指定子元素相对于父元素的偏移,但它们还是有两个主要区别:
定位参考系统不同;Stack/Positioned定位的参考系可以是父容器矩形的四个顶点;而Align则需要先通过alignment 参数来确定坐标原点,不同的alignment会对应不同原点,最终的偏移是需要通过alignment的转换公式来计算出。
Stack可以有多个子元素,并且子元素可以堆叠,而Align只能有一个子元素,不存在堆叠。
LayoutBuilder 的使用很简单,但是不要小看它,因为它非常实用且重要,它主要有两个使用场景:
1、可以使用 LayoutBuilder 来根据设备的尺寸来实现响应式布局。
2、LayoutBuilder 可以帮我们高效排查问题。比如我们在遇到布局问题或者想调试组件树中某一个节点布局的约束时 LayoutBuilder 就很有用。在组件外层套一个 LayoutBuilder,然后打印出 constraints 信息,就知道该组件的约束信息了。
AfterLayout是Flutter实战作者封装的一个组件,可以在组件布局完成后,获取组件的大小和坐标。
DecoratedBox可以在组件上面或下面绘制一些装饰,主要有两个属性:
1、position 决定在哪里绘制,它接收 DecorationPosition 的枚举类型,分别为 background 和 foreground。
2、decoration 代表将要绘制的装饰,它的类型为Decoration,它是一个抽象类,我们一般使用它的实现类 BoxDecoration:
BoxDecoration({
Color color, //颜色
DecorationImage image,//图片
BoxBorder border, //边框
BorderRadiusGeometry borderRadius, //圆角
List boxShadow, //阴影,可以指定多个
Gradient gradient, //渐变
BlendMode backgroundBlendMode, //背景混合模式
BoxShape shape = BoxShape.rectangle, //形状
})
Transform的变换是应用在绘制阶段,而并不是应用在布局(layout)阶段,所以无论对子组件应用何种变化,其占用空间的大小和在屏幕上的位置都是固定不变的,因为这些是在布局阶段就确定的。
由于矩阵变化只会作用在绘制阶段,所以在某些场景下,在UI需要变化时,可以直接通过矩阵变化来达到视觉上的UI改变,而不需要去重新触发build流程,这样会节省layout的开销,所以性能会比较好。如之前介绍的Flow组件,它内部就是用矩阵变换来更新UI,除此之外,Flutter的动画组件中也大量使用了Transform以提高性能。
RotatedBox和Transform.rotate功能相似,它们都可以对子组件进行旋转变换,但是有一点不同:RotatedBox的变换是在layout阶段,会影响在子组件的位置和大小。
四种裁剪:
ClipOval 子组件为正方形时剪裁成内贴圆形;为矩形时,剪裁成内贴椭圆
ClipRRect 将子组件剪裁为圆角矩形
ClipRect 默认剪裁掉子组件布局空间之外的绘制内容(溢出部分剪裁)
ClipPath 按照自定义的路径剪裁
另外,还有自定义裁剪,通过继承 CustomClipper 来实现。
子组件大小超出了父组件大小时,如果不经过处理的话 Flutter 中就会显示一个溢出警告并在控制台打印错误日志。问题的本质就是:子组件如何适配父组件空间。而根据 Flutter 布局协议适配算法应该在容器或布局组件的 layout 中实现,为了方便开发者自定义适配规则,Flutter 提供了一个 FittedBox 组件。
FittedBox适配原理:
1、FittedBox 在布局子组件时会忽略其父组件传递的约束,可以允许子组件无限大,即FittedBox 传递给子组件的约束为(0<=width<=double.infinity, 0<= height <=double.infinity)。
2、FittedBox 对子组件布局结束后就可以获得子组件真实的大小。
3、FittedBox 知道子组件的真实大小也知道他父组件的约束,那么FittedBox 就可以通过指定的适配方式(BoxFit 枚举中指定),让起子组件在 FittedBox 父组件的约束范围内按照指定的方式显示。
FittedBox 的 fit 属性决定了当子组件的大小超过了父组件大小时,子组件如何渲染,默认是 BoxFit.contain,表示子组件按等比例缩放,尽可能多的占据父组件的空间; 如果是 BoxFit.none 则表示子组件会按照真实大小进行绘制,从而超出父组件的大小。
要注意一点,在未指定适配方式时,虽然 FittedBox 子组件的大小超过了 FittedBox 父 Container 的空间,但FittedBox 自身还是要遵守其父组件传递的约束。
在使用Row组件显示多段文本时,如果文本太长,会出现溢出错误,那可以封装一个 SingleLineFittedBox 包裹在外层,当文本太长时,会自动缩小文本。
class SingleLineFittedBox extends StatelessWidget {
const SingleLineFittedBox({Key? key,this.child}) : super(key: key);
final Widget? child;
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (_, constraints) {
return FittedBox(
child: ConstrainedBox(
constraints: constraints.copyWith(
minWidth: constraints.maxWidth,
maxWidth: double.infinity,
//maxWidth: constraints.maxWidth
),
child: child,
),
);
},
);
}
}
BottomNavigationBar 底部导航栏,BottomAppBar 打洞效果的底部导航栏