Flutter 容器类组件

2021/4/18 posted in  Flutter

先说一个意外发现的小技巧,在写flutter布局的时候使用 const 可以避免一些不必要的频繁刷新。

  1. 当我们调用 setState() 后,Flutter 会调用 build 方法,并且 rebuild 其中的每一个组件,避免全部重新构建的方法就是用 const。
  2. 如果一个组件更新频繁(比如动画),用 const 后可以减少垃圾回收。

e.g.


class _MyWidgetState extends State<MyWidget> {
 
  String title = "Title";
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Column(
        children: <Widget>[
          const Text("Text 1"),
          const Padding(
            padding: const EdgeInsets.all(8.0),
            child: const Text("Another Text widget"),
          ),
          const Text("Text 3"),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: () {
          setState(() => title = 'New Title');
        },
      ),
    );
  }

容器类的组件是布局中进行调整比较多的部分了,罗列下可能会用到比较多的容器类组件。

Padding

主要用于填充,容器的空白部分。

定义如下:

Padding({
  ...
  EdgeInsetsGeometry padding,
  Widget child,
})

日常使用比较多的是 EdgeInsetsEdgeInsetsGeometry 的子类。

EdgeInsets

  • fromLTRB(double left, double top, double right, double bottom):分别指定四个方向的填充。
  • all(double value) : 所有方向均使用相同数值的填充。
  • only({left, top, right ,bottom }):可以设置具体某个方向的填充(可以同时指定多个方向)。
  • symmetric({ vertical, horizontal }):用于设置对称方向的填充,vertical指top和bottom,horizontal指left和right。

e.g.

class PaddingTestRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Padding(
      //上下左右各添加16像素补白
      padding: EdgeInsets.all(16.0),
      child: Text("I am Jake"),
    );
  }
}

ConstrainedBox

用于对子组件添加额外的约束。

e.g.

ConstrainedBox(
  constraints: BoxConstraints(
    minWidth: double.infinity, //宽度尽可能大
    minHeight: 50.0 //最小高度为50像素
  ),
  child: Container(
      height: 5.0, 
      child: redBox 
  ),
)

BoxConstraints 的其他构造函数:

BoxConstraints.tight(Size size),它可以生成给定大小的限制;const BoxConstraints.expand()可以生成一个尽可能大的用以填充另一个容器的BoxConstraints.

SizedBox

使用 e.g.

SizedBox(
  width: 80.0,
  height: 80.0,
  child: redBox
)

SizedBox 是 ConstrainedBox 的一个定制,等价于:

ConstrainedBox(
  constraints: BoxConstraints.tightFor(width: 80.0,height: 80.0),
  child: redBox, 
)

其实就是取width 和 height 去创建了约束嘛...

关于多重限制

这也是在写UI中常常会遇到并且需要调试最多的问题之一

场景 e.g.

ConstrainedBox(
    constraints: BoxConstraints(minWidth: 60.0, minHeight: 60.0), //父
    child: ConstrainedBox(
      constraints: BoxConstraints(minWidth: 90.0, minHeight: 20.0),//子
      child: redBox,
    )
)

对于场景中的 minWidth 和 minHeight 来说,是取父子中数值较大的生效 以避免父子约束冲突。(也就是满足要求比较高的约束先嘛)

同理对于max系列的约束来说,就是取父子组件中数值比较小的来生效了。

UnconstrainedBox

UnconstrainedBox 比较少会使用到,最多的用处是用于 “去除” 多重限制的约束。

e.g.

ConstrainedBox(
    constraints: BoxConstraints(minWidth: 60.0, minHeight: 100.0),  //父
    child: UnconstrainedBox( //“去除”父级限制
      child: ConstrainedBox(
        constraints: BoxConstraints(minWidth: 90.0, minHeight: 20.0),//子
        child: redBox,
      ),
    )
)

例子中子组件的 ConstrainedBox 将会按照 90 * 20进行绘制redBox。

但是并不是真正地去除了父组件的限制,只是让子组件摆脱了父组件的约束而已。

实际开发过程中,当我们发现对子元素指定宽高都没有效果的时候,基本上是因为父元素设置了限制。

在定义通用组件的时候要注意:如果要对子组件指定限制,一旦指定限制条件,子组件如果要进行自定义大小将会非常困难。

DecoratedBox

这个组件就是用于给组件设置一些样式,如背景,边框,渐变等,定义如下:

const DecoratedBox({
  Decoration decoration,
  DecorationPosition position = DecorationPosition.background,
  Widget child
})

通常直接使用其子类 BoxDecoration

BoxDecoration({
  Color color, //颜色
  DecorationImage image,//图片
  BoxBorder border, //边框
  BorderRadiusGeometry borderRadius, //圆角
  List<BoxShadow> boxShadow, //阴影,可以指定多个
  Gradient gradient, //渐变
  BlendMode backgroundBlendMode, //背景混合模式
  BoxShape shape = BoxShape.rectangle, //形状
})

Container

这...我感觉应该是最常用的组件了吧!之前的学习过程中就出现过好多次了。定义如下:

Container是一个组合类容器,它本身不对应具体的RenderObject,它是DecoratedBox、ConstrainedBox、Transform、Padding、Align等组件组合的一个多功能容器,所以我们只需通过一个Container组件可以实现同时需要装饰、变换、限制的场景

Container({
  this.alignment,
  this.padding, //容器内补白,属于decoration的装饰范围
  Color color, // 背景色
  Decoration decoration, // 背景装饰
  Decoration foregroundDecoration, //前景装饰
  double width,//容器的宽度
  double height, //容器的高度
  BoxConstraints constraints, //容器大小的限制条件
  this.margin,//容器外补白,不属于decoration的装饰范围
  this.transform, //变换
  this.child,
})

需要注意的是:

  • 容器的大小可以通过width、height属性来指定,也可以通过constraints来指定;如果它们同时存在时,width、height优先。实际上Container内部会根据width、height来生成一个constraints。
  • color和decoration是互斥的,如果同时设置它们则会报错!实际上,当指定color时,Container内会自动创建一个decoration。

Padding和Margin

这个两个 Container 的属性通过一段代码和一张图片就可以了解清楚了:

...
Container(
  margin: EdgeInsets.all(20.0), //容器外补白
  color: Colors.orange,
  child: Text("Hello world!"),
),
Container(
  padding: EdgeInsets.all(20.0), //容器内补白
  color: Colors.orange,
  child: Text("Hello world!"),
),
...

事实上,Container内margin和padding都是通过Padding 组件来实现的,所以上面的代码等价于:

...
Padding(
  padding: EdgeInsets.all(20.0),
  child: DecoratedBox(
    decoration: BoxDecoration(color: Colors.orange),
    child: Text("Hello world!"),
  ),
),
DecoratedBox(
  decoration: BoxDecoration(color: Colors.orange),
  child: Padding(
    padding: const EdgeInsets.all(20.0),
    child: Text("Hello world!"),
  ),
),
...