有趣的Flutter千人千面, 合适的Widget万里挑一

675 阅读6分钟

Flutter Widget Guide

众所周知Flutter中一切皆为Widget,并且Widget之间还存在着嵌套组合的艺术,每个Widget可以功能单一,又可以将不同的Widget相互组合形成另一个Widget。然而这些Widget组合的结果就是,Weiget个数会成几何倍数增长,现在Widget个数怎么说也有500了吧。(这个是官方对Widget做的分类Widget Catalog)。

那么怎么合理的组合Widget形成另一个Widget,官方的Container就是一个很好的例子,他很好的将LimitedBox、ConstrainedBox、Align、Padding、ClipPath、DecoratedBox、Transform等Widget组合到了一起,这里可以看下源码感受一下。

无独有偶,实际上官方确实也组合了很多像Container一样好用的Widget。但是,也是因为Widget的可组合性太强了,同一个实现常常会有很多种的组合的方式。这就形成大家很喜欢自己造一些轮子的趋势,虽然也可以完成,但会出现很多相似的Widget。这样后期难以维护,所以建议大家优先使用官方的一些封装好的Widget。当然也有可能是刚入门,渐进式的使用Flutter,对Widget没有一个大概的了解,就不知不觉的走了弯路。

我呢开始也走了不少弯路,一边享受着Widget可以无限嵌套封装的快感,一边感到不知道到底应该选用哪个Widget的困惑。随着慢慢的了解,也渐渐的总结出了不同场景下,应该使用哪些Widget。

在这里把它分享出来,希望大家一起学习,一起探讨。以下都是我自己的主观认知,如有不对,还望指正。

概览

详细可点击链接在官网查看对应Widget的文档以及演示(部分Widget已经有详细演示)

功能优选不推荐描述
容器ContainerLimitedBox、ConstrainedBox、Align、Padding、ClipPath、DecoratedBox、Transform功能这么多的Container用起来它不香吗,但是如果你只使用了一个属性,例如外边距,还是建议直接使用Padding。
容器动画AnimatedContainerAnimatedXxxx每个属性都有单独的以Animated为前缀的隐式动画实现,但如果想让child使用动画请考虑使用AnimatedSwitcher
容器内容居中设置Alignment.centerCenterContainer中不要再套用Center了,直接设置Alignment为center即可。
比例容器AspectRatio手动计算宽或高比列尽可能写成 2 / 3,而不是0.6667,不仅精度高而且易读。扩展:GridView中item的Size就是通过,副轴长度固定,然后按照比例算出主轴长度,来实现固定布局的。
阴影PhysicalModelCardBoxShadow有多种实现方式,但是PhysicalModel术业有专工。
卡片效果CardMaterialCard的效果其实是使用Material进行了上层封装,想要实现卡片效果,直接使用Card即可。skr~
圆形头像CircleAvatarClipOvalClipRRectClipRRect更适用于圆角,它的圆形只是一种特殊情况。ClipRRect虽是椭圆,但更常用它的圆形表达形式。CircleAvatar看到名字就不用太多解释了。
局部刷新ValueNotifier & ValueListenableBuilder自封装StatefulWidget多留意后缀是Builder的组件,有些功能其实官方已经有了封装,这等小事就不要再去造轮子了。
布局刷新LayoutBuilder👆 可用于Web页面做自适应布局。
方向更改OrientationBuilder👆移动设备方向改变触发布局更新。
单次异步FutureBuilder👆 异步只能执行一次。
多次异步流StreamBuilder👆 可以向数据流中添加多次值,每次接收到数据后便刷新布局。
自定义动画AnimatedBuilderAnimatedWidget👆 用于构建自定义动画,可以将动画Widget中不需要变化的部分放到child节点,优化动画性能。
自定义插间动画TweenAnimationBuilderAnimatedWidget👆 使用与AnimatedBuilder类似,但其只是double类型的插间,而TweenAnimationBuilder可以指定插间类型,例如:Color、Offset、Rect等。
旋转RotatedBoxTransform.rotateTransform旋转前后占用的空间不会变化:[一日] => [丨日];RotatedBox是先旋转然后应用布局,所以不存在旋转框占用布局:[一日] => [旧]
画布中旋转RotationTransitionCanvas.rotate在Canvas中旋转可以在CustomPaint的画布上尽情作画,然后将旋转应用到这个Widget上,而不是驱动画布旋转,每次都计算角度偏移再重画一遍。
分割线DividerContainer、Border快收起你的奇技淫巧,分割线不用这么麻烦,Divider就够了。如果你想用网格线可以使用GridPaper, 但它限定主线宽1pixel。想自定义线宽的话用这个GridPaperExp
透明度Color().withOpacity()Opacity这里主要想说应用透明度,Opacity不是最优选的方式,它有一定的性能成本,可以在这里查看官方解释。
按钮ElevatedButtonTextButtonOutlinedButtonIconButtonCupertinoButtonGestureDetector、InkWell、Listener大多数情况下你如果只需要一个点击事件,那以上Widget都可以满足需求,但相对来说体验上会差一些。这时候尽可以使用官方提供好的XxxxButton,这样按下动画、禁用显示灰色等特性就无须再去实现了。
手势GestureDetectorRawGestureDetector、ListenerListener + GestureRecognizer = RawGestureDetector;RawGestureDetector + Common GestureRecognizer = GestureDetector 一般使用GestureDetector,如果预定义的手势不能满足需求,可以直接操作屏幕上监听的点使用Listener,或把这些点抽象成手势,实现对应的手势识别器,赋于RawGestureDetector这就形成了自定义手势。
Item固定长度列表ListView(itemExtent: 22.22,)SliverFixedExtentListSliverList既然知道了每一项的长度,就不要让容器动态去计算了,这在无限长度的布局中,可以提高不少性能。ListView如果没有设置itemExtent,最终实现就是SliverList,反之就是SliverFixedExtentList。
固定扩展无限布局SingleChildScrollViewListView如果布局比较固定,只是出现了一些不稳定因子,例如折叠伸缩文字,布局必须可变才能抵消其影响,这时候推荐使用此Widget。而只有在明确布局为无限长度时才推荐使用ListView。[补充]
主题Theme没有含义且多次重复的数值、颜色这些数据比较分散,集中取值便于管理,更便于修改。界面的风格统一比内容更重要。
待续

演示

容器内容居中

动手尝试一波

// 👍
Container(
  color: Colors.purple,
  alignment: Alignment.center,
  child: Text(text),
)
// 🙅‍♂️     
Container(
  color: Colors.blue,
  child: Center(
    child: Text(text),
  ),
)

阴影

动手尝试一波

// 👍
PhysicalModel(
  color: Colors.grey,
  elevation: 10.0,
  shadowColor: Colors.grey[900],
  clipBehavior: Clip.hardEdge,
  borderRadius: borderRadius,
  child: child,
)
// 👍
Card(
  color: Colors.grey,
  elevation: 10.0,
  shadowColor: Colors.grey[900],
  clipBehavior: Clip.hardEdge,
  shape: RoundedRectangleBorder(
    borderRadius: borderRadius,
  ),
  child: child,
)
// 🙅‍♂️ 
Container(
  clipBehavior: Clip.hardEdge,
  decoration: BoxDecoration(
    color: Colors.grey,
    borderRadius: borderRadius,
    boxShadow: <BoxShadow>[
      BoxShadow(
        color: Colors.grey,
        offset: Offset(2.0, 8.0),
        blurRadius: 10.0,
      )
    ],
  ),
  child: child,
),

固定扩展无限布局

动手尝试一波

可以看到SingleChildScrollView在再次折叠文字后变为固定布局,页面便不可以滚动。而在成为滚动布局后,行为与ListView基本一致,比如Android溢出有水波纹,iOS溢出弹簧性显示。

_child = Column(children: _children);

// 👍
SingleChildScrollView(
  child: _child,
),

// 🙅‍♂️ 
ListView(
  children: _children,
)

// 🙅‍♂️ 
_child

结语

后面还会继续添加,想要持续关注可以出门左转到 GitHub,顺便给我来个Start 哈哈哈😄