Flutter Stack小工具介绍

448 阅读7分钟

如果说Flutter以什么而闻名,那就是它所附带的令人难以置信的大量小工具。所有这些部件都能帮助开发者以尽可能少的努力获得他们想要的确切外观。

在这篇文章中,我们将看看每个Flutter开发者都应该知道的一个小工具:Stack 小工具。

通过在应用程序中有效地使用Stack widget,我们可以向用户传达深度,并在没有大量工作的情况下创建一些相当复杂的布局。

一个Stack widget是什么样子的?

在这里,我们可以看到一个例子,说明我们可以在Flutter应用程序中使用Stack widget实现什么样的布局。

An Example Of A Layout Achieved With A Stack, Showing Three Photos With The Most Visible Positioned On Top, A Title and Description Above And Below Images

一个用堆栈实现的布局的例子(照片来自Unsplash的Sarah Dorweller)。

对于这个应用程序,我们看到中间有一张图片,然后两侧有另外两张图片。左边和右边的图片尺寸稍小,放在中间的图片后面。

从本质上讲,这些小组件相互堆叠,让用户清楚地了解我们希望他们关注的内容。

Stack 小组件是如何工作的?

为了证明Stack 小组件的作用,让我们首先看看Column 是如何布置它的孩子的。在这个简单的例子中,我们有五个容器,它们的宽度和高度都是逐渐增加的。

Widget build(BuildContext context) {
  return Scaffold(
    body: Column(
      children: [
        ...List.generate(
          5,
          (index) => Container(
            width: index * 50 + 50,
            height: index * 50 + 50,
            color: Color.fromRGBO(index * 20, index * 20, index * 40, 1.0),
          ),
        ).reversed
      ],
    ),
  );
}

这段代码的结果如下。

Column Layout With Child Elements Stacked From Top To Bottom

现在,如果我们用一个Stack 小组件取代Column 小组件,它就会变成这样。

Replacing The Column Widget With The Stack Widget, Makes Children Layer On Top Of Each Other

这些小部件不是在垂直轴上布置的,而是相互堆叠的。当我们想让我们的部件在彼此的顶部,而不是从上到下或从左到右时,这是有好处的。

我们还可以看到,小组件从下往上渲染。在我们的例子中,最大的小组件在堆栈的底部渲染,较小的小组件在顶部渲染,以此类推。

默认情况下,子小部件是向左上方对齐的,Stack 调整大小以适合所有的子小部件,这意味着它将和我们最大的子小部件一样大。

对齐和适合

有时,如果我们把一个较小的小组件放在一个较大的小组件里面,把所有的子小组件对准中心,会更有美感。

如果我们想把我们的小组件对准中心以获得视觉吸引力,我们可以把堆栈内的子小组件对准中心。为了达到这个目的,我们可以简单地将Stack 中的alignment 属性设置为Alignment.center ,就像这样。

  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        alignment: Alignment.center, // Center children in the Stack
        children: [
          ...List.generate(
            5,
            (index) => Container(
              width: index * 50 + 50,
              height: index * 50 + 50,
              color: Color.fromRGBO(index * 20, index * 20, index * 40, 1.0),
            ),
          ).reversed,
        ],
      ),
    );
  }

这就把Stack 中的所有子部件都集中到了相对中心,就像这样。

Centering Children In Stack Widget, Shows Children Stacked On Top Of Each Other And Centered

因为我们还没有将Stack 居中,它仍然在左上角。相反,我们只是把在Stack 里面的小部件居中。

我们还可以使用fit 参数来定义我们的堆栈是否应该扩展以填充父小部件,或者是否应该通过fit 的子对象直接到Stack 中的子对象。

广义上讲,这些只适用于更高级的布局方案,所以我们应该把fit ,即默认的StackFit.loose ,就可以了。

我们还可以通过使用Positioned ,在堆栈本身中定位小部件。如果我们添加一个蓝色背景的Container ,在其中放置一些文本,并将其定位在底部中心,小部件就会在Stack 的范围内进行相应的排版。

我们的代码就会变成这样。

  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        alignment: Alignment.center, // Center children in the Stack
        children: [
          ...List.generate(
            5,
            (index) => Container(
              width: index * 50 + 50,
              height: index * 50 + 50,
              color: Color.fromRGBO(index * 20, index * 20, index * 40, 1.0),
            ),
          ).reversed,
          // The second child positions the container at the very bottom
          // of the parent Stack.
          Positioned(
            left: 0,
            right: 0,
            bottom: 0,
            child: Container(
              color: Colors.blue.withOpacity(0.8),
              child: Text(
                "Yay for LogRocket!",
                textAlign: TextAlign.center,
                style: Theme.of(context).textTheme.headline5!.copyWith(
                      color: Colors.white,
                    ),
              ),
            ),
          )
        ],
      ),
    );
  }

这给我们带来了以下结果,Stack 中的孩子们被置于中心位置,而我们的Container 则根据我们在left,top, 和right 参数中指定的padding,被排列在最底部。

Aligning Container To The Stack, Shows A Blue Container With The Words "Yay For LogRocket!"

用于布局上述Stack 的代码的完整演示可以在这里找到

剪切行为

我们还可以使用Stack ,在不使用底层绘图函数的情况下,完成一些漂亮的应用程序的布局。

我们可以通过使用Position 小组件将我们的小组件定位在我们的Stack 之外,然后为适当的方向(如底部或右侧)指定一个负数来做到这一点。

如果我们在Stack 外放置一个容器,我们可以看到,Stack 默认会夹住我们溢出的小部件。

Light Blue Stack Clips Bigger Blue Container

我们也可以通过指定clipBehaviour: Clip.none ,告诉我们的Stack ,不要夹住溢出的小部件,以防我们想让小部件继续在 的范围之外渲染。Stack.

Stack Does Not Clip Container, Allows Overlapping

实用的Stack 用途

看到彩色的盒子堆叠在一起是很好的,但是我们什么时候会在你的Flutter应用程序中实际使用Stack

互相堆叠的小部件有多种用途,但有两个主要的使用领域,即在指定一个小部件在容器中的位置或显示另一个必须处于前台的小部件时。

为了证明这一点,让我们做一个应用程序,向我们展示猫的图片,并让我们选择从我们的收藏夹中添加或删除它们。它还会一直向我们显示我们的收藏夹中的猫的总数。

下面是成品的模样。

Cat App With Stacks, Shows A Carousel Of Cat Pictures With A Forward Button And Favorites Container

我们上面的应用程序有一个Stack ,其中包含一个PageView 和一个ContainerPageView 包含五张猫的图片和一个有风格的封面,而Container 显示有多少只被收藏的猫,并让用户选择点击下一个而不是滑动。

Container 也被嵌套在一个Positioned 小组件中,使其出现在屏幕的右下方。它也有适当的填充,所以SnackBar 显示时,它不会与按钮重叠。

正如我们所看到的,即使我们与正下方的PageView 进行互动,这两个按钮和我们所青睐的猫咪总数仍然是可见的。

Stack(
        children: [
          PageView(
            onPageChanged: (page) {
              setState(() {
                showFavouriteButton = page > 0;
              });
            },
            controller: _controller,
            children: [
              Container(
                decoration: BoxDecoration(
                    gradient: LinearGradient(
                  begin: Alignment.topLeft,
                  end: Alignment.bottomCenter,
                  colors: [
                    Colors.purple,
                    Colors.deepPurpleAccent,
                  ],
                )),
                child: Center(
                    child: Text(
                  "Look at these great cats!",
                  style: Theme.of(context).textTheme.headline3,
                )),
              ),
              ...catImages.map(
                (e) => Image.network(
                  e,
                ),
              )
            ],
          ),
          Positioned(
            bottom: 50,
            right: 0,
            child: Column(
              children: [
                Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Container(
                    padding: EdgeInsets.all(16),
                    decoration: BoxDecoration(borderRadius: BorderRadius.circular(12), color: Colors.blue),
                    child: Column(
                      children: [
                        Text("Total Favourite Cats"),
                        Text(
                          favourites.length.toString(),
                        ),
                      ],
                    ),
                  ),
                ),
                Row(
                  children: [
                    Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: AnimatedOpacity(
                        duration: Duration(milliseconds: 500),
                        opacity: showFavouriteButton ? 1 : 0,
                        child: FloatingActionButton(
                          onPressed: () {
                            setState(() {
                              if (favourites.contains(catImages[_controller.page!.floor() - 1])) {
                                favourites.remove(catImages[_controller.page!.floor() - 1]);
                                ScaffoldMessenger.of(context).showSnackBar(
                                  SnackBar(
                                    content: Text("You removed this cat from your favorites."),
                                  ),
                                );
                              } else {
                                favourites.add(catImages[_controller.page!.floor() - 1]);
                                ScaffoldMessenger.of(context).showSnackBar(
                                  SnackBar(
                                    content: Text("You added this cat to your favorites."),
                                  ),
                                );
                              }
                            });
                          },
                          child: Icon(Icons.favorite),
                        ),
                      ),
                    ),
                    Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: FloatingActionButton(
                        onPressed: () {
                          _controller.nextPage(duration: Duration(milliseconds: 500), curve: Curves.fastOutSlowIn);
                        },
                        child: Icon(Icons.navigate_next),
                      ),
                    )
                  ],
                ),
              ],
            ),
          )
        ],
      ),

我们还看到,前台的小部件,如按钮和收藏夹计数器,对点击事件做出了反应,并没有把它们传递给下面的小部件。

在前台没有widget的地方,我们的触摸事件会传递给后面的PageView

你可以在这里查看这个项目的完整代码

使用IndexedStack

Stack 小组件密切相关的是IndexedStack 小组件。这个小组件与Stack 小组件相同,但它允许我们指定我们真正想要显示的Stack 中的项目。

这使得它非常适用于我们想一次显示一个小组件的应用程序,因为它可以维护每个子项的状态

如果我们有一个应用程序有一个主屏幕,一个设置屏幕和一个收藏夹屏幕,我们可以在我们的setState 方法中设置当前的小工具来显示,并根据我们的需要轻松地在小工具之间切换。

总结

Stack widget是任何Flutter开发者工具包中必不可少的widget,我希望这篇文章能帮助你开始使用它🙌

The postIntro to the Flutter Stack widgetappeared first onLogRocket Blog.