【Flutter 组件集录】Stack | 8 月更文挑战

1,614 阅读4分钟
前言:

这是我参与8月更文挑战的第 23 天,活动详情查看:8月更文挑战。为应掘金的八月更文挑战,我准备在本月挑选 31 个以前没有介绍过的组件,进行全面分析和属性介绍。这些文章将来会作为 Flutter 组件集录 的重要素材。希望可以坚持下去,你的支持将是我最大的动力~

本系列组件文章列表
1.NotificationListener2.Dismissible3.Switch
4.Scrollbar5.ClipPath6.CupertinoActivityIndicator
7.Opacity8.FadeTransition9. AnimatedOpacity
10. FadeInImage11. Offstage12. TickerMode
13. Visibility14. Padding15. AnimatedContainer
16.CircleAvatar17.PhysicalShape18.Divider
19.Flexible、Expanded 和 Spacer 20.Card21.SizedBox
22.ConstrainedBox23.Stack

1. 认识 Stack 组件

Stack 是一个经常被用到的组件,我看可以通过它来叠合若干个组件。源码中对它的介绍是:

一个将其子部件,相对于盒边缘进行定位的组件。

也就是说 Stack 组件擅长的是 定位 组件。下面是 Stack 组件类的定义构造方法,可以看出它继承自 MultiChildRenderObjectWidget。所以可接受一个组件列表。另外还有一些配置参数,在下面将一一介绍 。


2. Stack 组件的简单使用

如下,通过 Stack 将蓝色盒子、红色盒子和一个图标叠放在一起。可以看出,默认情况下组件会以 Stack 区域的左上角进行对齐叠放,且会根据列表元素的顺序依次叠放。

Stack(
  children: <Widget>[
    Container( width: 100, height: 100, color: Colors.red ),
    Container( width: 60, height: 60, color: Colors.green ),
    Icon(Icons.ac_unit,color: Colors.white )
  ],
);

textDirection属性

根据不同国家的阅读习惯,给出 textDirection 来确定排布方向。其类型为 TextDirection 枚举,有两个元素,rtl 代表从右向左,ltr 代表从左向右,我们的阅读习惯自然是 ltr

enum TextDirection {
  /// The text flows from right to left (e.g. Arabic, Hebrew).
  rtl,
  /// The text flows from left to right (e.g., English, French).
  ltr,
}

当把 textDirection 设为 rtl 时,效果如下,会以 Stack 区域的右上角进行对齐叠放。

Stack(
  textDirection: TextDirection.rtl,
  children: <Widget>[
    Container( width: 100, height: 100, color: Colors.red ),
    Container( width: 60, height: 60, color: Colors.green ),
    Icon(Icons.ac_unit,color: Colors.white )
  ],
);

alignment属性

alignment 属性我们应该非常熟悉,它的类型是 AlignmentGeometry ,用于控制对齐方式。比如将 alignment 设置为 Alignment.center 常量,就会在 Stack 中居中排布。

Stack(
  alignment: Alignment.center,
  children: <Widget>[
    Container( width: 100, height: 100, color: Colors.red ),
    Container( width: 60, height: 60, color: Colors.green ),
    Icon(Icons.ac_unit,color: Colors.white )
  ],
);

另外 Alignment 除了八个方位外,可以自己指定对齐方式。之前再 《出神入化的Align》 一文中详细介绍了 alignment 属性的用法,这里不再赘述。比如下面是 Alignment(0.2,0.2) 的效果。


3. Stack 的 fit 属性

fit 属性类型为 StackFit 枚举,有如下三个元素。默认情况下是 loose

enum StackFit {
  loose,
  expand,
  passthrough,
}

先看个例子:如下,在 Stack 外层通过 ConstrainedBox 施加一个宽高固定的约束,毫无疑问 Stack 在被强制约束后,其占位区域被固定。

ConstrainedBox(
  constraints: BoxConstraints.tightFor(width:200,height:150),
  child: Stack(
  fit: StackFit.loose,
  children: <Widget>[
    Container(width: 100, height: 100, color: Colors.red),
    Container(width: 60, height: 60, color: Colors.green),
    Icon(Icons.ac_unit,color: Colors.white,)
  ],
));

可以看出,在 StackFit.loose 的情况下,子组件并未受到该固定尺寸的约束,而是以该固定区域为上限的松散约束,如下红色所示。


此时,在 StackFit.expand 的情况下,子组件也会受到一个固定尺寸的强约束。

Container 在该约束下,会变成固定的宽高,即使蓝色自身要求是 60*60

其实稍微瞄一下 RenderStack 的源码我们就能知道 fit 具体的处理逻辑。在 loose 时,会通过 constraints.loosen() 将创建一个松散约束,该方法就是将最小宽高设为 0 ,最大宽高设为对应最大值。而 expand 会将自身约束的最大区域最为固定宽高约束。最后 passthrough 则什么都不处理,直接将自身约束施加给子节点。

BoxConstraints loosen() {
  assert(debugAssertIsValid());
  return BoxConstraints(
    minWidth: 0.0,
    maxWidth: maxWidth,
    minHeight: 0.0,
    maxHeight: maxHeight,
  );
}

4. Stack 的 overflow 与 clipBehavior 属性

我们可以清楚地看到 overflow 属性是一个过时的属性,而 clipBehavior 就是替代它的。clipBehavior 是什么我就不多说了,详见 ClipPath 一文。

那为什么需要 clipBehavior 进行裁剪呢?通过 Positioned 组件可以对子组件进行精确定位,甚至是负数。如下左边偏移 -20 ,右边偏移 -10 。默认裁剪行为是 hardEdge ,效果如下:

class StackDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return
      Container(
        color: Colors.grey.withAlpha(33),
        constraints: BoxConstraints.tightFor(width:200,height:150),
        child: Stack(
        children: <Widget>[
          Container(width: 100, height: 100, color: Colors.red),
          Positioned(
              left: -20,
              top: -10,
              child: Container(width: 60, height: 60, color: Colors.green)),
          Icon(Icons.ac_unit,color: Colors.white,)
        ],
      ));
  }
}

clipBehavior: Clip.none, 时,不会进行裁剪,出界的就会被显示出来。注意一点,出界的部分是无法响应点击事件的。如果需要点击,有其他组件能够解决。


Stack 的使用方式到这里就介绍完毕,那本文到这里就结束了,谢谢观看,明天见~