flutter系列之:flutter中常用的Stack layout詳解

[toc]

簡介

對於現代APP的應用來說,為了更加美觀,通常會需要用到不同影像的堆疊效果,比如在一個APP用戶背景頭像上面添加一個按鈕,表示可以修改用戶資訊等。

要實現這樣的效果,我們需要在一個Image上面堆疊其他的widget對象,flutter為我們提供了這樣的一個非常方便的layout組件叫做Stack,今天和大家一起來聊一聊Stack的使用。

Stack詳解

我們先來看下Stack的定義:

class Stack extends MultiChildRenderObjectWidget 

Stack繼承自MultiChildRenderObjectWidget,表示在stack中可以render多個child widget對象。

因為Stack中的child是重疊關係,所以需要對child進行定位,根據定位的不同Stack中的child可以分為兩種類型,分別是positioned和non-positioned。

所謂positioned,是指child widget被包裝在Positioned中。什麼是Positioned呢?

Positioned是專門用來定位Stack中的child位置的一個widget。所以Positioned必須用在Stack中,並且Positioned和Stack的路徑之間只能存在StatelessWidget或者StatefulWidget這兩種widget。

如果一個對象被包含在Positioned中,那麼這個對象就是一個Stack中的positioned對象。

Positioned中除了封裝的child之外,還有6個屬性,如下所示:

  const Positioned({
    Key? key,
    this.left,
    this.top,
    this.right,
    this.bottom,
    this.width,
    this.height,
    required Widget child,
  })

這六個屬性分別是left,top,right,bottom,width和height。其中left,top,right,bottom分別表示到左,頂,右,底的距離,這個距離是相對stack來說的。而width和height則表示的是Positioned的寬度和高度。

事實上,使用left和right可以定義出width,使用top和bottom可以定義出height。

如果在一個軸方向的三個值都不存在,那麼會使用Stack.alignment來定位子元素。

如果六個值都不存在,那麼這個child就是一個non-positioned的child。

對於non-positioned的child,是通過Stack的alignment來進行布局的,默認情況下是按top left corners進行布局的。

Stack的屬性

我們接下來看一下Stack中有哪些屬性,下面是Stack的構造函數:

  Stack({
    Key? key,
    this.alignment = AlignmentDirectional.topStart,
    this.textDirection,
    this.fit = StackFit.loose,
    @Deprecated(
      'Use clipBehavior instead. See the migration guide in flutter.dev/go/clip-behavior. '
      'This feature was deprecated after v1.22.0-12.0.pre.',
    )
    this.overflow = Overflow.clip,
    this.clipBehavior = Clip.hardEdge,
    List<Widget> children = const <Widget>[],
  })

可以看到Stack中有alignment,textDirection,fit,overflow和clipBehavior這幾個屬性。

首先來看alignment,這裡的alignment是一個AlignmentGeometry對象,主要用來布局non-positioned children。

AlignmentGeometry中有兩個需要設置的屬性,分別是start和y。

start表示的是橫線定位範圍,它的取值比較奇怪,-1表示的是start side的邊緣位置,而1表示的是end side的邊緣位置。如果取值超過了這個範圍,則表示對應的位置超過了邊緣位置。

start的位置跟TextDirection是相關聯的,如果TextDirection的值是ltr,也就是說從左到右排列,那麼start就在最左邊,如果TextDirection的值是rtl,也就是說從右到左排列,那麼start就是在最右邊。

有橫向位置就有縱向位置,這個縱向位置用y來表示,它的正常取值範圍也是-1到1,當然你也可以超出這個範圍。

為了用戶更加方便的使用AlignmentGeometry,AlignmentGeometry提供了一些便捷的方法,如topStart,topCenter,topEnd等,大家可以自行選取。

接下來的屬性是textDirection,textDirection是一個TextDirection對象,它有兩個值,分別是rtl和ltr,在講解alignment的時候,我們已經提到過textDirection,它會影響alignment中橫向的布局。

接下來是StackFit類型的fit屬性,StackFit有三個值,分別是loose,expand和passthrough。

loose表示的是一個鬆散結構,比如Stack規定的size是300×500,那麼它的child的寬度可以從0-300,child的高度可以從0-500.

expand表示是一個擴充的效果,比如Stack規定的size是300×500,那麼它的child的寬度就是300,child的高度就是500.

passthrough表示傳遞給stack的限制會原封不動的傳遞給他的child,不會進行任何修改。

overflow表示children超出展示部分是否會被剪切。不過這個屬性已經是Deprecated,flutter推薦我們使用clipBehavior這個屬性來代替。

clipBehavior是一個Clip對象,它的默認值是Clip.hardEdge。其他的幾個值還有none,hardEdge,antiAlias和antiAliasWithSaveLayer。

none表示不進行任何裁剪,hardEdge的裁剪速度最快,但是精確度不高。antiAlias速度比hardEdge慢一點,但是有光滑的邊緣。antiAliasWithSaveLayer是最慢的,應該很少被使用。

Stack的使用

有了上面的講解,接下來我們看一下Stack的具體使用。

在我們這個例子中,我們在Stack中設置一個背景圖片,然後在圖片上疊加一個文本。

那麼應該怎麼實現呢?

首先我們需要設置Stack的alignment方式,我們希望文本和圖片的中心重合,也就是說把文字放在圖片中間,我們將Stack的alignment設置為Alignment.center。

接下來是一個背景圖片,因為原始圖片是一個正方形的圖片,我們需要對圖片進行裁剪成圓形,這裡使用一個非常方便的類CircleAvatar來創建圓形的圖標:

 const CircleAvatar(
          backgroundImage: AssetImage('images/head.jpg'),
          radius: 100,
        ),

上面的程式碼能夠創建一個半徑是100的圓。

然後是文本的創建,可以給Text設置文本內容和對應的style:

Text(
            '編輯',
            style: TextStyle(
              fontSize: 20,
              fontWeight: FontWeight.bold,
              color: Colors.white,
            ),
          )

然後把Text封裝在Container中,並使用BoxDecoration給他指定一個背景:

Container(
          decoration: const BoxDecoration(
            color: Colors.green,
          ),
          child: const Text(
              ...

最後將上面的程式碼組合起來就是我們最後的Stack:

 Widget build(BuildContext context) {
    return Stack(
      alignment: Alignment.center,
      children: [
        const CircleAvatar(
          backgroundImage: AssetImage('images/head.jpg'),
          radius: 100,
        ),
        Container(
          decoration: const BoxDecoration(
            color: Colors.green,
          ),
          child: const Text(
            '編輯',
            style: TextStyle(
              fontSize: 20,
              fontWeight: FontWeight.bold,
              color: Colors.white,
            ),
          ),
        ),
      ],
    );

運行生成的介面如下:

總結

以上就是Stack的使用,通過堆疊組件,我們可以實現很多炫酷的功能。

本文的例子://github.com/ddean2009/learn-flutter.git

更多內容請參考 //www.flydean.com/11-flutter-ui-layout-stack/

最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!