若是你看过 Container
的源码,会发现它是一个颇有意思的组件,它基本上没干啥正事,就是将已有的组件拼一拼而已。它是一个 StatelessWidget
,其中 build
方法使用了以下八个组件,本文将从源码的角度看一下,Container 究竟是如何运做的,为其设置的各类属性都被用在了哪里。web
在 LayoutBuilder
篇咱们知道,Scaffold 组件的 body 对应上层的区域约束为 BoxConstraints(0.0<=w<=屏幕宽, 0.0<=h<=屏幕高)
。从表现上来看,当只有 color 属性时,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()),
);
}
...
if (color != null)
current = ColoredBox(color: color, child: current);
...
return current;
}
复制代码
从代码中能够看到,当 child 为 null ,而且 constraints 为 null,current
会被套上 LimitedBox + ConstrainedBox,其中 ConstrainedBox 的约束是延展的。 当颜色非 null,会在 current
上传套上 ColoredBox
,而 ColoredBox
组件的做用就是在尺寸区域中填充颜色。这就是 Container 的尺寸会铺满最大约束区域的缘由。微信
以下,可见当设置 child
属性后,Container
的布局尺寸会与 child
一致。来看下源码这是为何。markdown
经过上面的源码也能够看出, 当 child 属性非空时,就不会包裹 LimitedBox
+ ConstrainedBox
。从下面的调试结构看,只有 ColoredBox
+ Text
。 因此就没有了区域的延展,从而和 child
尺寸一致。less
添加宽高属性以后,Container
的布局区域会变为指定区域。那源码中是如何实现的呢?ide
Container({
//...
double width,
double height,
//...
}) : //...
constraints =
(width != null || height != null)
? constraints?.tighten(width: width, height: height)
?? BoxConstraints.tightFor(width: width, height: height)
复制代码
当宽高被设置时,constraints
属性会被设置为对应宽高的紧约束,也就是把尺寸定死。源码分析
经过 alignment 能够将子组件在容器区域内对齐摆放。那源码中是如何实现的呢?布局
if (alignment != null)
current = Align(alignment: alignment, child: current);
复制代码
其实处理很是简单,就是在 alignment
非空时,套上一个 Align
组件。测试
经过布局查看器能够看出,外边距是 margin
,内边距是 padding
。优化
EdgeInsetsGeometry get _paddingIncludingDecoration {
if (decoration == null || decoration.padding == null)
return padding;
final EdgeInsetsGeometry decorationPadding = decoration.padding;
if (padding == null)
return decorationPadding;
return padding.add(decorationPadding);
}
@override
Widget build(BuildContext context) {
Widget current = child;
//...
final EdgeInsetsGeometry effectivePadding = _paddingIncludingDecoration;
if (effectivePadding != null)
current = Padding(padding: effectivePadding, child: current);
//...
if (margin != null)
current = Padding(padding: margin, child: current);
return current;
}
复制代码
从源码中能够看出 padding
和 margin
属性都是使用 Padding
属性完成的,只不过 margin
在外侧包裹而已。能够看到实际的 padding
值是经过 _paddingIncludingDecoration
得到的,其中会包含装饰的边距,默认为 0。
decoration 属性和 foregroundDecoration 非空时,都会包裹一个 DecoratedBox
组件。 foregroundDecoration 是前景装饰,因此较背景装饰而言在上层。关于 DecoratedBox
组件的使用在以前介绍过,这里就再也不详细介绍了,可详见以前的 DecoratedBox
组件文章。
if (decoration != null)
current = DecoratedBox(decoration: decoration, child: current);
if (foregroundDecoration != null) {
current = DecoratedBox(
decoration: foregroundDecoration,
position: DecorationPosition.foreground,
child: current,
);
}
复制代码
constraints
属性非空,会包裹上 ConstrainedBox
,此时容器的区域会被约束,以下测试中,约束为最小宽高 80、32,最大宽高 100,140。即说明当前容器的所占区域不能在约束以外,这里宽高为 8,比最小区域宽高小,则会使用最小宽高。
当设定大小比约束区域大时,会使用最大的约束区域,也就是说若是当前容器的布局区域发生变化, constraints
会保证容器尺寸在一个范围内变化。好比盛放文字时,文字的长短不一样致使布局尺寸不一样,经过约束可让文字在必定的尺寸范围内变更。
if (constraints != null)
current = ConstrainedBox(constraints: constraints, child: current);
复制代码
Clip
是一个枚举类,包含四种形式,以下:
enum Clip {
none, // 无
hardEdge, // 硬边缘
antiAlias, // 抗锯齿
antiAliasWithSaveLayer, // 抗锯齿保存图层
}
复制代码
从源码中能够看出 clipBehavior
不为 Clip.none
时,必须有 decoration
属性。这里将 current
包裹一层 ClipPath
,clipBehavior
就是在该组件中使用的。这里的裁剪使用 _DecorationClipper
,经过 decoration
获取裁剪路径,也就是圆角装饰时的裁剪行为。
if (clipBehavior != Clip.none) {
assert(decoration != null);
current = ClipPath(
clipper: _DecorationClipper(
textDirection: Directionality.of(context),
decoration: decoration
),
clipBehavior: clipBehavior,
child: current,
);
}
/// A clipper that uses [Decoration.getClipPath] to clip.
class _DecorationClipper extends CustomClipper<Path> {
_DecorationClipper({
TextDirection textDirection,
@required this.decoration
}) : assert(decoration != null),
textDirection = textDirection ?? TextDirection.ltr;
final TextDirection textDirection;
final Decoration decoration;
@override
Path getClip(Size size) {
return decoration.getClipPath(Offset.zero & size, textDirection);
}
@override
bool shouldReclip(_DecorationClipper oldClipper) {
return oldClipper.decoration != decoration
|| oldClipper.textDirection != textDirection;
}
}
复制代码
transform 接收一个 Matrix4
的变化矩阵对象,能够据此完成一些移动、旋转、缩放的变换效果。不过经过源码能够看出 Container
组件只是对 Transform
的一个简单封装,实际上 Transform
还能够指定变化中心 origin
、对齐模式 alignment
等。这样能够看出来 Container
只是为了组件的简化使用,并不是全权将这些组件的功能进行集成。
if (transform != null)
current = Transform(transform: transform, child: current);
复制代码
对于这些 SingleChildRenderObjectWidget
,因为各自的属性比较少,有些功能很经常使用,当联合使用时,就会一层层嵌套,致使使用的体验不是很好。若是没有 Container
组件,那么要完成上面的效果,你就须要使用下面右侧的实现方式,将这些小组件一个个嵌套,这样用起来是很是麻烦和别扭的。
当有了 Container
,虽然它没有干什么很是伟大的事,却实实在在地将这八个组件整合到了一块儿。如右侧图片,使用起来就很是精简。但本质上仍是那些组件的功劳,这就是一种封装,将多个子系统内聚,对外界提供访问的接口,表面上操做的是外表的接口,其实是子系统的运做。
Container 是一个 StatelessWidget,它只须要完成 build
的任务,依赖其余组件来完成任务,这是一件比较轻松的事。经过设置 Container
组件的属性,再将这些属性移交给内部的各个组件,能够颇有效地表象的树状结构拉平
,这样的好处是提供代码的易读性
,经过Container
的组件名,也有必定的语义性
。更方便用户的理解和使用。 咱们再反过来思考一下,源码中能够这样,若是有相似的场景,不少短小的层级结构,咱们也能够适当地封装一个组件进行优化。
从源码能够看出对于 Align 、Transform 组件,Container 并无将它们所有属性都集成进来。 这样看来 Container
只是一个通才,什么都能干,但并不须要样样都精。若是暴露了过多的属性,会增长用户使用的复杂性。因此凡事适度,才能有最好的效果。
最后说一下,经过源码分析后,咱们应该能够明白,有些很简单的场景是不须要使用 Container
的,好比只是为了加个 Padding
、只是显示一下颜色、只是进行变换等,使用对应的组件便可。当须要同时使用几个功能时,使用 Container 时也没必要有什么负担,担忧使用 Container 低效什么的,其实就是在元素树里多了个元素而已,代码可读性的价值远远在其之上,本身一层层叠也多是多写多错
。了解 Container
的源码以后,在使用时便再也不陌生,一个黑盒被照亮后,在使用它的时候,你就会多一份自信。那么本文就到此结束,谢谢观看。
@张风捷特烈 2021.01.02 未允禁转
个人公众号:编程之王
联系我--邮箱:1981462002@qq.com -- 微信:zdl1994328
~ END ~