前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Flutter 组件 | 熟悉而陌生的 Container

Flutter 组件 | 熟悉而陌生的 Container

作者头像
张风捷特烈
发布2021-01-12 10:36:46
6930
发布2021-01-12 10:36:46
举报
0. Container 的简介

如果你看过 Container 的源码,会发现它是一个很有意思的组件,它基本上没干啥正事,就是将已有的组件拼一拼而已。它是一个 StatelessWidget,其中 build 方法使用了如下八个组件,本文将从源码的角度看一下,Container 到底是如何运作的,为其设置的各种属性都被用在了哪里。

image-20210104221354698
image-20210104221354698

1. 颜色属性

LayoutBuilder 篇我们知道,Scaffold 组件的 body 对应上层的区域约束为 BoxConstraints(0.0<=w<=屏幕宽, 0.0<=h<=屏幕高)。从表现上来看,当只有 color 属性时,Container 的尺寸会铺满最大约束区域。

image-20210104200522153
image-20210104200522153
@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 的尺寸会铺满最大约束区域的原因。

image-20210104203919102
image-20210104203919102

2. child 属性

如下,可见当设置 child 属性后,Container 的布局尺寸会与 child 一致。来看下源码这是为什么。

image-20210104205523272
image-20210104205523272

通过上面的源码也可以看出, 当 child 属性非空时,就不会包裹 LimitedBox + ConstrainedBox。从下面的调试结构看,只有 ColoredBox + Text 。 所以就没有了区域的延展,从而和 child 尺寸一致。

image-20210104205047135
image-20210104205047135

3. 宽高属性

添加宽高属性之后,Container 的布局区域会变为指定区域。那源码中是如何实现的呢?

image-20210104223623456
image-20210104223623456
  Container({
    //...
    double width,
    double height,
    //...
  }) : //...
       constraints =
        (width != null || height != null)
          ? constraints?.tighten(width: width, height: height)
            ?? BoxConstraints.tightFor(width: width, height: height)

当宽高被设置时,constraints 属性会被设置为对应宽高的紧约束,也就是把尺寸定死。

image-20210104223551282
image-20210104223551282

4.alignment 属性

通过 alignment 可以将子组件在容器区域内对齐摆放。那源码中是如何实现的呢?

image-20210104224302600
image-20210104224302600
if (alignment != null)
  current = Align(alignment: alignment, child: current);

其实处理非常简单,就是在 alignment 非空时,套上一个 Align 组件。

image-20210104224151550
image-20210104224151550

5.padding 和 margin 属性

通过布局查看器可以看出,外边距是 margin,内边距是 padding

image-20210104225059004
image-20210104225059004
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;
}

从源码中可以看出 paddingmargin 属性都是使用 Padding 属性完成的,只不过 margin 在外侧包裹而已。可以看到实际的 padding 值是通过 _paddingIncludingDecoration 获得的,其中会包含装饰的边距,默认为 0。

image-20210104224914989
image-20210104224914989

6.decoration 和 foregroundDecoration 属性

decoration 属性和 foregroundDecoration 非空时,都会包裹一个 DecoratedBox 组件。 foregroundDecoration 是前景装饰,所以较背景装饰而言在上层。关于 DecoratedBox 组件的使用在之前介绍过,这里就不再详细介绍了,可详见之前的 DecoratedBox 组件文章。

image-20210104230442993
image-20210104230442993
if (decoration != null)
  current = DecoratedBox(decoration: decoration, child: current);

if (foregroundDecoration != null) {
  current = DecoratedBox(
    decoration: foregroundDecoration,
    position: DecorationPosition.foreground,
    child: current,
  );
}

7. constraints 属性

constraints 属性非空,会包裹上 ConstrainedBox ,此时容器的区域会被约束,如下测试中,约束为最小宽高 80、32,最大宽高 100,140。即说明当前容器的所占区域不能在约束之外,这里宽高为 8,比最小区域宽高小,则会使用最小宽高。

image-20210105121035698
image-20210105121035698

当设定大小比约束区域大时,会使用最大的约束区域,也就是说如果当前容器的布局区域发生变化, constraints 会保证容器尺寸在一个范围内变化。比如盛放文字时,文字的长短不同导致布局尺寸不同,通过约束可以让文字在一定的尺寸范围内变动。

image-20210105121322760
image-20210105121322760
if (constraints != null)
  current = ConstrainedBox(constraints: constraints, child: current);

8. clipBehavior 裁剪行为

Clip 是一个枚举类,包含四种形式,如下:

enum Clip {
  none, // 无
  hardEdge, // 硬边缘
  antiAlias, // 抗锯齿
  antiAliasWithSaveLayer, // 抗锯齿保存图层
}

从源码中可以看出 clipBehavior 不为 Clip.none 时,必须有 decoration 属性。这里将 current 包裹一层 ClipPathclipBehavior 就是在该组件中使用的。这里的裁剪使用 _DecorationClipper ,通过 decoration 获取裁剪路径,也就是圆角装饰时的裁剪行为。

image-20210105122402575
image-20210105122402575
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;
  }
}
复制代码

9. transform 属性

transform 接收一个 Matrix4 的变化矩阵对象,可以据此完成一些移动、旋转、缩放的变换效果。不过通过源码可以看出 Container 组件只是对 Transform 的一个简单封装,实际上 Transform 还可以指定变化中心 origin对齐模式 alignment 等。这样可以看出来 Container 只是为了组件的简化使用,并非全权将这些组件的功能进行集成。

image-20210105123424763
image-20210105123424763
if (transform != null)
  current = Transform(transform: transform, child: current);

10. Container 组件存在的意义

对于这些 SingleChildRenderObjectWidget ,由于各自的属性比较少,有些功能很常用,当联合使用时,就会一层层嵌套,导致使用的体验不是很好。如果没有 Container 组件,那么要完成上面的效果,你就需要使用下面右侧的实现方式,将这些小组件一个个嵌套,这样用起来是非常麻烦和别扭的。

当有了 Container ,虽然它没有干什么非常伟大的事,却实实在在地将这八个组件整合到了一起。如右侧图片,使用起来就非常精简。但本质上还是那些组件的功劳,这就是一种封装,将多个子系统内聚,对外界提供访问的接口,表面上操作的是外表的接口,实际上是子系统的运作。

image-20210105124801630
image-20210105124801630

Container 是一个 StatelessWidget,它只需要完成 build 的任务,依赖其他组件来完成任务,这是一件比较轻松的事。通过设置 Container 组件的属性,再将这些属性移交给内部的各个组件,可以很有效地表象的树状结构拉平,这样的好处是提供代码的易读性,通过Container 的组件名,也有一定的语义性。更方便用户的理解和使用。 我们再反过来思考一下,源码中可以这样,如果有类似的场景,很多短小的层级结构,我们也可以适当地封装一个组件进行优化。

从源码可以看出对于 Align 、Transform 组件,Container 并没有将它们全部属性都集成进来。 这样看来 Container 只是一个通才,什么都能干,但并不需要样样都精。如果暴露了过多的属性,会增加用户使用的复杂性。所以凡事适度,才能有最好的效果。

最后说一下,通过源码分析后,我们应该可以明白,有些很简单的场景是不需要使用 Container 的,比如只是为了加个 Padding 、只是显示一下颜色、只是进行变换等,使用对应的组件即可。当需要同时使用几个功能时,使用 Container 时也不必有什么负担,担心使用 Container 低效什么的,其实就是在元素树里多了个元素而已,代码可读性的价值远远在其之上,自己一层层叠也可能是多写多错。了解 Container 的源码之后,在使用时便不再陌生,一个黑盒被照亮后,在使用它的时候,你就会多一份自信。那么本文就到此结束,谢谢观看。


@张风捷特烈 2021.01.02 未允禁转 我的公众号:编程之王 联系我--邮箱:1981462002@qq.com -- 微信: ~ END ~

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0. Container 的简介
  • 1. 颜色属性
  • 2. child 属性
  • 3. 宽高属性
  • 4.alignment 属性
  • 5.padding 和 margin 属性
  • 6.decoration 和 foregroundDecoration 属性
  • 7. constraints 属性
  • 8. clipBehavior 裁剪行为
  • 9. transform 属性
  • 10. Container 组件存在的意义
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档