刚开端触摸flutter的时分,Container
组件是用得最多的。它就像HTML中的div一样遍及,专门用来布局页面的。
可是运用Container嵌套布局的时分,经常出现一些令人无法理解的问题。就如下面代码,在一个固定的容器中,子组件却铺满了全屏。
/// 例一
@override
Widget build(BuildContext context) {
return Container(
width: 300,
height: 300,
color: Colors.amber,
child: Container(width: 50, height: 50, color: Colors.red,),
);
}
然后要加上alignment
特点,子组件正常显现了,但容器仍是铺满全屏。
/// 例二
@override
Widget build(BuildContext context) {
return Container(
width: 300,
height: 300,
color: Colors.amber,
alignment: Alignment.center,
child: Container(width: 50, height: 50, color: Colors.red,),
);
}
而在容器外层添加一个Scaffold
组件,它就正常显现了。
/// 例三
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: 300,
height: 300,
color: Colors.amber,
alignment: Alignment.center,
child: Container(width: 50, height: 50, color: Colors.red,),
),
);
}
这一切的奇怪行为困扰了我很久,直到我深入了flutter布局的学习,才渐渐解开这些疑惑。
1、flutter的widget类型
flutter的widget能够分为三类,组合类ComponentWidget、署理类ProxyWidget和制作类RenderObjectWidget
组合类:如Container
、Scaffold
、MaterialApp
还有一系列经过承继StatelessWidget和StatefulWidget的类。组合类是咱们开发过程中用得最多的组件。
署理类:InheritedWidget
,功用型组件,它能够高效快捷的完成同享数据的跨组件传递。如常见的Theme
、MediaQuery
便是InheritedWidget的运用。
制作类:屏幕上看到的UI简直都会经过RenderObjectWidget
完成。经过承继它,能够进行界面的布局和制作。如Align
、Padding
、ConstrainedBox
等都是经过承继RenderObjectWidget,并经过重写createRenderObject办法来创建RenderObject目标,完成终究的布局(layout)和制作(paint)。
2、Container是个组合类
清楚明了Container承继StatelessWidget,它是一个组合类,同时也是一个由DecoratedBox
、ConstrainedBox
、Transform
、Padding
、Align
等组件组合的多功用容器。能够经过检查Container类,看出它实际便是经过不同的参数判别,再进行组件的层层嵌套来完成的。
@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()),
);
} else 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!;
}
组合类根本不参加ui的制作,都是经过制作类的组合来完成功用。
3、flutter布局束缚
flutter中有两种布局束缚BoxConstraints盒束缚和SliverConstraints线性束缚,如Align、Padding、ConstrainedBox运用的是盒束缚。
BoxConstraints盒束缚是指flutter结构在运行时遍历整个组件树,在这过程中 「向下传递束缚,向上传递尺度」,以此来确认每个组件的尺度和巨细。
BoxConstraints类由4个特点组成,最小宽度minWidth、最大宽度maxWidth、最小高度minHeight、最大高度maxHeight。
BoxConstraints({
this.minWidth,
this.maxWidth,
this.minHeight,
this.maxHeight,
});
依据这4个特点的变化,能够分为“紧束缚(tight)”、“松束缚(loose)”、“无界束缚”、“有界束缚”。
紧束缚:最小宽(高)度和最大宽(高)度值相等,此刻它是一个固定宽高的束缚。
BoxConstraints.tight(Size size)
: minWidth = size.width,
maxWidth = size.width,
minHeight = size.height,
maxHeight = size.height;
松束缚:最小宽(高)值为0,最大宽(高)大于0,此刻它是一个束缚规模。
BoxConstraints.loose(Size size)
: minWidth = 0.0,
maxWidth = size.width,
minHeight = 0.0,
maxHeight = size.height;
无界束缚:最小宽(高)和最大宽(高)值存在double.infinity(无限)。
BoxConstraints.expand({double? width, double? height})
: minWidth = width ?? double.infinity,
maxWidth = width ?? double.infinity,
minHeight = height ?? double.infinity,
maxHeight = height ?? double.infinity;
有界束缚:最小宽(高)和最大宽(高)值均为固定值。
BoxConstraints(100, 300, 100, 300)
4、Container布局行为解惑
了解了BoxConstraints布局束缚,回到本文最开端的问题。
/// 例一
@override
Widget build(BuildContext context) {
return Container(
width: 300,
height: 300,
color: Colors.amber,
child: Container(width: 50, height: 50, color: Colors.red,),
);
}
例一中,两个固定宽高的Container,为什么子容器铺满了全屏?
依据BoxConstraints布局束缚,遍历整个组件树,最开端的root是树的起点,它向下传递的是一个紧束缚。由于是移动设备,root即是屏幕的巨细,假设屏幕宽414、高896。于是整个布局束缚如下:
这儿有个问题,便是Container分明现已设置了固定宽高,为什么无效?
由于父级向下传递的束缚,子组件必须严格遵守。这儿Container容器设置的宽高超出了父级的束缚规模,就会主动被疏忽,采用契合束缚的值。
例一两上Container都被铺满屏幕,而最底下的赤色Container叠到了最上层,所以终究显现赤色。
/// 例二
@override
Widget build(BuildContext context) {
return Container(
width: 300,
height: 300,
color: Colors.amber,
alignment: Alignment.center,
child: Container(width: 50, height: 50, color: Colors.red,),
);
}
例二也同样能够依据布局束缚求证,如下图:
这儿Container为什么是ConstrainedBox
和Align
组件?前面说过Container是一个组合组件,它是由多个原子组件组成的。依据例二,它是由ConstrainedBox和Align嵌套而成。
Align供给给子组件的是一个松束缚,所以容器本身设置50宽高值是在合理规模的,因此生效,屏幕上显现的便是50像素的赤色方块。ConstrainedBox遭到的是紧束缚,所以本身的300宽高被疏忽,显现的是铺满屏幕的黄色块。
/// 例三
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: 300,
height: 300,
color: Colors.amber,
alignment: Alignment.center,
child: Container(width: 50, height: 50, color: Colors.red,),
),
);
}
例三中Scaffold
向下传递的是一个松束缚,所以黄色Container的宽高依据本身设置的300,在合理的规模内,有效。Container再向下传递的也是松束缚,终究赤色Container宽高为50。
这儿还有个问题,怎样确认组件向下传递的是紧束缚仍是松束缚?
这就涉及到组件的内部完成了,这儿经过Align举个例。
Align
是一个制作组件,它能够进行界面的布局和制作,这是由于Align的承继链为:
Align -> SingleChildRenderObjectWidget -> RenderObjectWidget
Align需求重写createRenderObject办法,回来RenderObject的完成,这儿Align回来的是RenderPositionedBox,所以核心内容就在这个类中
class Align extends SingleChildRenderObjectWidget {
/// ...
@override
RenderPositionedBox createRenderObject(BuildContext context) {
return RenderPositionedBox(
alignment: alignment,
widthFactor: widthFactor,
heightFactor: heightFactor,
textDirection: Directionality.maybeOf(context),
);
}
/// ...
}
而RenderPositionedBox类中,重写performLayout办法,该办法用于依据本身束缚条件,计算出子组件的布局,再依据子组件的尺度设置本身的尺度,构成一个至下而上,由上到下的闭环,终究完成界面的整个制作。
RenderPositionedBox -> RenderAligningShiftedBox -> RenderShiftedBox -> RenderBox
class RenderPositionedBox extends RenderAligningShiftedBox {
/// ...
@override
void performLayout() {
final BoxConstraints constraints = this.constraints; // 本身的束缚巨细
final bool shrinkWrapWidth = _widthFactor != null || constraints.maxWidth == double.infinity;
final bool shrinkWrapHeight = _heightFactor != null || constraints.maxHeight == double.infinity;
/// 存在子组件
if (child != null) {
/// 开端布局子组件
child!.layout(constraints.loosen(), parentUsesSize: true);
/// 依据子组件的尺度设置本身尺度
size = constraints.constrain(Size(
shrinkWrapWidth ? child!.size.width * (_widthFactor ?? 1.0) : double.infinity,
shrinkWrapHeight ? child!.size.height * (_heightFactor ?? 1.0) : double.infinity,
));
/// 计算子组件的位置
alignChild();
} else {
/// 不存在子组件
size = constraints.constrain(Size(
shrinkWrapWidth ? 0.0 : double.infinity,
shrinkWrapHeight ? 0.0 : double.infinity,
));
}
}
/// ...
}
依据Align中performLayout办法的完成,能够确认该组件终究会给子组件传递一个怎样样的束缚。
/// constraints.loosen供给的是一个松束缚
child!.layout(constraints.loosen(), parentUsesSize: true);
/// loosen办法
BoxConstraints loosen() {
assert(debugAssertIsValid());
/// BoxConstraints({double minWidth = 0.0, double maxWidth = double.infinity, double minHeight = 0.0, double maxHeight = double.infinity})
return BoxConstraints(
maxWidth: maxWidth,
maxHeight: maxHeight,
);
}
其它制作类的组件根本跟Align大同小异,只需重点看performLayout办法的完成,即可判别出组件供给的束缚条件。
总结
1、flutter的widget分为,组合类、署理类和制作类。
2、Container是一个组合类,由DecoratedBox、ConstrainedBox、Transform、Padding、Align等制作组件组合而成。
3、flutter中有两种布局束缚BoxConstraints盒束缚和SliverConstraints线性束缚。
4、BoxConstraints的束缚原理是:「向下传递束缚,向上传递尺度」。
5、BoxConstraints的束缚类型为:紧束缚、松束缚、无界束缚、有界束缚。
6、判别一个制作组件的束缚行为能够经过检查performLayout办法中layout传入的束缚值。
最后,既然都看到这儿了,就请您高抬贵手点个赞呗~。您的鼓舞,是我创造的最大动力!