Flutter 之Stack 組件

Flutter 之Stack 組件

Stack

Stack 這個(gè)是Flutter中布局用到的組件翩瓜,跟Android中FrameLayout很像,都是可以疊加的現(xiàn)實(shí)View物独,具體的使用細(xì)節(jié)還是有些不同的挤土,我們一一說(shuō)來(lái)

Stack({
  Key key,
  this.alignment = AlignmentDirectional.topStart,
  this.textDirection,
  this.fit = StackFit.loose,
  this.overflow = Overflow.clip,
  List<Widget> children = const <Widget>[],
})
  • alignment : 指的是子Widget的對(duì)其方式容劳,默認(rèn)情況是以左上角為開(kāi)始點(diǎn) ,這個(gè)屬性是最難理解的颜价,它區(qū)分為使用了Positioned和未使用Positioned定義兩種情況斯碌,沒(méi)有使用Positioned情況還是比較好理解的,下面會(huì)詳細(xì)講解的

  • fit :用來(lái)決定沒(méi)有Positioned方式時(shí)候子Widget的大小句惯,StackFit.loose 指的是子Widget 多大就多大土辩,StackFit.expand使子Widget的大小和父組件一樣大

  • overflow :指子Widget 超出Stack時(shí)候如何顯示抢野,默認(rèn)值是Overflow.clip拷淘,子Widget超出Stack會(huì)被截?cái)啵?/p>

    Overflow.visible超出部分還會(huì)顯示的

初探Stack組件的使用

import 'package:flutter/material.dart';

class StackScreen extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("stack title"),
      ),
      body: Stack(
        children: <Widget>[
          Container(
            width: 100,
            height: 100,
            color: Colors.red,
          ),
          Container(
            width: 90,
            height: 90,
            color: Colors.blue,
          ),
          Container(
            width: 80,
            height: 80,
            color: Colors.green,
          ),
        ],
      ),
    );
  }

}

上面的代碼Stack做為根布局,疊加的方式展示3個(gè)組件指孤,第一個(gè)組件比較大100100启涯,第二個(gè)組件稍微小點(diǎn)90**90

贬堵,第三個(gè)組件最小80*80,顯示的方式是能看見(jiàn)第一個(gè)和第二個(gè)組件的部分區(qū)域结洼,第三個(gè)組件是能全部顯示出來(lái)

stack_demo.jpg

fit 屬性使用

如果指定是StackFit.expand黎做,所以的子組件會(huì)和Stack一樣大的

class StackScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("stack title"),
        actions: <Widget>[
          RaisedButton(
            onPressed: () {
              Navigator.push(context,
                  MaterialPageRoute(builder: (context) => PositionScreen()));
            },
            color: Colors.blue,
            child: Icon(Icons.add),
          ),
        ],
      ),
      body: Stack(
        fit: StackFit.expand,
        children: <Widget>[
          Container(
            width: 100,
            height: 100,
            color: Colors.red,
          ),
          Container(
            width: 90,
            height: 90,
            color: Colors.blue,
          ),
          Container(
            width: 80,
            height: 80,
            color: Colors.green,
          ),
        ],
      ),
    );
  }
}
stack_fit_expand.jpg

顯示內(nèi)容就只最后一個(gè)組件,雖然我們給這個(gè)組件指定了一個(gè)80*80的寬高是不會(huì) 生效的松忍,因?yàn)槲覀円呀?jīng)指定了子元素和Stack一樣大小蒸殿,也就是說(shuō)設(shè)置了StackFit.expand,StackFit.expand的效果優(yōu)先

Positioned

這個(gè)使用控制Widget的位置鸣峭,通過(guò)他可以隨意擺放一個(gè)組件宏所,有點(diǎn)像絕對(duì)布局

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

left、top 摊溶、right爬骤、 bottom分別代表離Stack左、上莫换、右霞玄、底四邊的距離

class PositionScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Postion Title"),
      ),
      body: Stack(
        children: <Widget>[
          Positioned(
            top: 100.0,
            child: Container(
              color: Colors.blue,
              child: Text("第一個(gè)組件"),
            ),
          ),
          Positioned(
            top: 200,
            right: 100,
            child: Container(
              color: Colors.yellow,
              child: Text("第二個(gè)組件"),
            ),
          ),
          Positioned(
            left: 100.0,
            child: Container(
              color: Colors.red,
              child: Text("第三個(gè)組件"),
            ),
          ),
        ],
      ),
    );
  }
}

這個(gè)例子的效果就是

  • 第一個(gè)組件距離頂部Stack 有100的間距
  • 第二個(gè)組件距離頂部200,距離右邊100間距
  • 第三個(gè)組件距離左邊100間距
positioned.jpg

這個(gè)地方有注意地方拉岁,例如說(shuō)第一個(gè)組件我指定距離左邊0個(gè)距離坷剧,距離右邊0個(gè)距離,那么這個(gè)組件的寬度就是屏幕這么寬喊暖,因?yàn)槟阒付ǖ淖笥议g距都是0听隐,也就是沒(méi)有間距

class PositionScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Postion Title"),
      ),
      body: Stack(
        children: <Widget>[
          Positioned(
            top: 100.0,
            left: 0,
            right: 0,
            child: Container(
              color: Colors.blue,
              child: Text("第一個(gè)組件"),
            ),
          ),
          Positioned(
            top: 200,
            right: 100,
            child: Container(
              color: Colors.yellow,
              child: Text("第二個(gè)組件"),
            ),
          ),
          Positioned(
            bottom: 0,
            left: 0,
            right: 0,
            child: Container(
              color: Colors.red,
              child: Text("第三個(gè)組件"),
            ),
          ),
        ],
      ),
    );
  }
}
postion_left_right.jpg

第一個(gè)組件和第三個(gè)組件寬度都是整個(gè)屏幕這個(gè)寬度,第三組件我又指定了距離底部bottom為0哄啄,所以第三組件是在最底下

那么我們?nèi)绻付薼eft&&right&&top&bottom都是0的情況呢?

那么這個(gè)組件就是和Stack大小一樣填滿它

class PositionScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Postion Title"),
      ),
      body: Stack(
        children: <Widget>[
          Positioned(
            left: 0,
            top: 0,
            right: 0,
            bottom: 0,
            child: Container(
              color: Colors.black45,
            ),
          ),
          Positioned(
            top: 100.0,
            left: 0,
            right: 0,
            child: Container(
              color: Colors.blue,
              child: Text("第一個(gè)組件"),
            ),
          ),
          Positioned(
            top: 200,
            right: 100,
            child: Container(
              color: Colors.yellow,
              child: Text("第二個(gè)組件"),
            ),
          ),
          Positioned(
            bottom: 0,
            left: 0,
            right: 0,
            child: Container(
              color: Colors.red,
              child: Text("第三個(gè)組件"),
            ),
          ),
        ],
      ),
    );
  }
}
postion_left_right_top_bottom.jpg

為了演示這個(gè)效果雅任,我在第一個(gè)組件上加上了一個(gè)黑色的標(biāo)記,代碼中添加的第一組件就是和Stack一樣大的咨跌,系統(tǒng)也提供了一個(gè)方法Positioned.fill 這個(gè)方法的效果和圖片上是一樣的

class PositionScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Postion Title"),
      ),
      body: Stack(
        children: <Widget>[
          Positioned.fill(
            child: Container(
              color: Colors.black45,
            ),
          ),
          Positioned(
            top: 100.0,
            left: 0,
            right: 0,
            child: Container(
              color: Colors.blue,
              child: Text("第一個(gè)組件"),
            ),
          ),
          Positioned(
            top: 200,
            right: 100,
            child: Container(
              color: Colors.yellow,
              child: Text("第二個(gè)組件"),
            ),
          ),
          Positioned(
            bottom: 0,
            left: 0,
            right: 0,
            child: Container(
              color: Colors.red,
              child: Text("第三個(gè)組件"),
            ),
          ),
        ],
      ),
    );
  }
}

效果是等價(jià)的

GridTile 是如何使用Positioned 定位的

GridTile 是一個(gè)Flutter 提供的組件的沪么,用來(lái)在GridView中給Item 增加更豐富的展示用的,GridTile 的布局方式就是Stack锌半,在源代碼中就到Positioned 來(lái)進(jìn)行位置控制禽车,主要提供三個(gè)Widget的展示分別為child、header刊殉、footer殉摔,我們看一下源代碼

class GridTile extends StatelessWidget {
  /// Creates a grid tile.
  ///
  /// Must have a child. Does not typically have both a header and a footer.
  const GridTile({
    Key key,
    this.header,
    this.footer,
    @required this.child,
  }) : assert(child != null),
       super(key: key);

  /// The widget to show over the top of this grid tile.
  ///
  /// Typically a [GridTileBar].
  final Widget header;

  /// The widget to show over the bottom of this grid tile.
  ///
  /// Typically a [GridTileBar].
  final Widget footer;

  /// The widget that fills the tile.
  ///
  /// {@macro flutter.widgets.child}
  final Widget child;

  @override
  Widget build(BuildContext context) {
    if (header == null && footer == null)
      return child;

    final List<Widget> children = <Widget>[
      Positioned.fill(
        child: child,
      ),
    ];
    if (header != null) {
      children.add(Positioned(
        top: 0.0,
        left: 0.0,
        right: 0.0,
        child: header,
      ));
    }
    if (footer != null) {
      children.add(Positioned(
        left: 0.0,
        bottom: 0.0,
        right: 0.0,
        child: footer,
      ));
    }
    return Stack(children: children);
  }
}

源代碼不多,child Wigdet的太小和Stack大小一樣 在頂部繪制了head Widget 這個(gè)組件记焊,在底部繪制footer Widget組件逸月,效果圖如下

grid_title_footer.jpg
grid_title_header.jpg

alignment 屬性理解

沒(méi)有使用Positioned定位情況

在我們初探Stack組件中講解的例子就是沒(méi)有使用Positioned定位的情況,默認(rèn)的子組件的對(duì)齊方式就是以左上角為起點(diǎn)開(kāi)始排列子Widget

AlignmentDirectional.bottomEnd 對(duì)齊方式

所有的Widget 以Stack的右下角為起點(diǎn)開(kāi)始對(duì)齊

class StackScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("stack title"),
        actions: <Widget>[
          RaisedButton(
            onPressed: () {
              Navigator.push(context,
                  MaterialPageRoute(builder: (context) => PositionScreen()));
            },
            color: Colors.blue,
            child: Icon(Icons.add),
          ),
        ],
      ),
      body: Stack(
//        fit: StackFit.expand,
       alignment: AlignmentDirectional.bottomEnd,
        children: <Widget>[
          Container(
            width: 100,
            height: 100,
            color: Colors.red,
          ),
          Container(
            width: 90,
            height: 90,
            color: Colors.blue,
          ),
          Container(
            width: 80,
            height: 80,
            color: Colors.green,
          ),
        ],
      ),
    );
  }
}
stack_alignment_bottom_end.jpg
AlignmentDirectional.topEnd 對(duì)齊方式

所有的Widget 以Stack的右上角為起點(diǎn)開(kāi)始對(duì)齊

class StackScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("stack title"),
        actions: <Widget>[
          RaisedButton(
            onPressed: () {
              Navigator.push(context,
                  MaterialPageRoute(builder: (context) => PositionScreen()));
            },
            color: Colors.blue,
            child: Icon(Icons.add),
          ),
        ],
      ),
      body: Stack(
//        fit: StackFit.expand,
       alignment: AlignmentDirectional.topEnd,
        children: <Widget>[
          Container(
            width: 100,
            height: 100,
            color: Colors.red,
          ),
          Container(
            width: 90,
            height: 90,
            color: Colors.blue,
          ),
          Container(
            width: 80,
            height: 80,
            color: Colors.green,
          ),
        ],
      ),
    );
  }
}
stack_alignment_top_end.jpg
AlignmentDirectional.center 對(duì)齊方式

所有的Widget 以Stack的中心位置

stack_alignment_center.jpg
AlignmentDirectional.centerEnd 對(duì)齊方式

所有的Widget 在Stack的中心位置并且右邊跟stack右邊挨著

stack_alignment_center_end.jpg

使用Positioned情況下

class PositionScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Postion Title"),
      ),
      body: Stack(
//        alignment: AlignmentDirectional.bottomEnd,
        overflow: Overflow.visible,
        children: <Widget>[
          Positioned.fill(
            child: Container(
              color: Colors.black45,
            ),
          ),
          Positioned(
            top: 100.0,
            left: 0,
            right: 20,
            child: Container(
              color: Colors.blue,
              child: Text("第一個(gè)組件"),
            ),
          ),
          Positioned(
            top: 200,
            bottom: 20,
            child: Container(
              color: Colors.yellow,
              child: Text("第二個(gè)組件"),
            ),
          ),
          Positioned(
            bottom: 0,
            left: 0,
            right: 0,
            child: Container(
              color: Colors.red,
              child: Text("第三個(gè)組件"),
            ),
          ),
        ],
      ),
    );
  }
}
stack_position.jpg

這種情況是alignment 是默認(rèn)值的效果遍膜,下面我們修改一下alignment的對(duì)應(yīng)的值

1. AlignmentDirectional.bottomEnd

bottomEnd是子Widget的底部和Stack底部對(duì)齊碗硬,并且子Widget的右邊和Stack右邊對(duì)齊

class PositionScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Postion Title"),
      ),
      body: Stack(
        alignment: AlignmentDirectional.bottomEnd,
        overflow: Overflow.visible,
        children: <Widget>[
          Positioned.fill(
            child: Container(
              color: Colors.black45,
            ),
          ),
          Positioned(
            top: 100.0,
            left: 0,
            right: 20,
            child: Container(
              color: Colors.blue,
              child: Text("第一個(gè)組件"),
            ),
          ),
          Positioned(
            top: 200,
            bottom: 20,
            child: Container(
              color: Colors.yellow,
              child: Text("第二個(gè)組件"),
            ),
          ),
          Positioned(
            bottom: 0,
            left: 0,
            right: 0,
            child: Container(
              color: Colors.red,
              child: Text("第三個(gè)組件"),
            ),
          ),
        ],
      ),
    );
  }
}

顯示效果


stack_postion_bottom_end.jpg

大家會(huì)發(fā)現(xiàn)這個(gè)圖的效果和上一個(gè)圖的效果唯一區(qū)別就是黃色的第二個(gè)組件的位置有變化瓤湘,這是為什么呢?

先說(shuō)第一個(gè)組件和第三組件的位置為什么沒(méi)有改變

Positioned(
            top: 100.0,
            left: 0,
            right: 20,
            child: Container(
              color: Colors.blue,
              child: Text("第一個(gè)組件"),
            ),
          ),

          Positioned(
            bottom: 0,
            left: 0,
            right: 0,
            child: Container(
              color: Colors.red,
              child: Text("第三個(gè)組件"),
            ),
          ),

第一個(gè)組件top是100恩尾,說(shuō)明這個(gè)組件距離頂部的距離是固定的弛说,雖然Stack的aligment=AlignmentDirectional.bottomEnd,是不生效的翰意,當(dāng)這兩個(gè)屬性沖突時(shí)木人,以Positioned的距離為主,為什么第一組件右邊也沒(méi)有Stack的右邊對(duì)齊呢冀偶?因?yàn)閞ight=20虎囚,第一個(gè)組件右邊距離已經(jīng)可以確認(rèn)了,所以也不受到aligment=AlignmentDirectional.bottomEnd的影響

第三個(gè)組件也是一樣的蔫磨,第三個(gè)組件的寬度是Stack的寬度,高度取決于Text組件的高度圃伶,最關(guān)鍵的是它的bottom=0堤如,也就是第三個(gè)組件要和Stack組件的低邊界對(duì)齊,所以它的效果和上面的圖是沒(méi)有變化的

Positioned(
  top: 200,
  bottom: 20,
  child: Container(
    color: Colors.yellow,
    child: Text("第二個(gè)組件"),
  ),
),

第二個(gè)組件為什么會(huì)跑到右邊呢窒朋?

因?yàn)榈诙€(gè)組件的高度是可以確認(rèn)出來(lái)的搀罢,top=200,bottom=20侥猩,設(shè)置這兩個(gè)屬性就能推斷出第二組的高度是多大榔至,但是第二個(gè)組件的寬度取決于Text("第二個(gè)組件") 的寬度,顯然是水平方向上是不能填滿Stack的欺劳,這個(gè)時(shí)候AlignmentDirectional.bottomEnd屬性起作用了唧取,bottom的距離已經(jīng)確定了,所以底部的對(duì)齊方式是不會(huì)變化了划提,但是第二組件右邊的對(duì)齊方式是可以收到AlignmentDirectional.bottomEnd影響的枫弟,所以第二組件展示的位置就是圖片上展示的位置

總結(jié)一下使用Positioned 定位方式aligment 方式對(duì)它的影響

Positioned 有四個(gè)屬性top、bottom鹏往、left淡诗、right,(top伊履、bottom)決定了垂直方向上的位置了韩容,(left、right)決定了水平方向上的位置唐瀑,不管水平方向上還是垂直方向上只要設(shè)定了一個(gè)值該方向上位置就已經(jīng)確定過(guò)了群凶,aligment對(duì)這這個(gè)方向上就不會(huì)起作用了,如果Positioned 設(shè)置了其中任意三個(gè)方向的值哄辣,這個(gè)Widget的位置就是固定的座掘,aligment對(duì)它不會(huì)起任何作用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末递惋,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子溢陪,更是在濱河造成了極大的恐慌萍虽,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件形真,死亡現(xiàn)場(chǎng)離奇詭異杉编,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)咆霜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)邓馒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人蛾坯,你說(shuō)我怎么就攤上這事光酣。” “怎么了脉课?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵救军,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我倘零,道長(zhǎng)唱遭,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任呈驶,我火速辦了婚禮拷泽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘袖瞻。我一直安慰自己司致,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布聋迎。 她就那樣靜靜地躺著蚌吸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪砌庄。 梳的紋絲不亂的頭發(fā)上羹唠,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音娄昆,去河邊找鬼佩微。 笑死,一個(gè)胖子當(dāng)著我的面吹牛萌焰,可吹牛的內(nèi)容都是我干的哺眯。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼扒俯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼奶卓!你這毒婦竟也來(lái)了一疯?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤夺姑,失蹤者是張志新(化名)和其女友劉穎墩邀,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體盏浙,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡眉睹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了废膘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片竹海。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖丐黄,靈堂內(nèi)的尸體忽然破棺而出斋配,到底是詐尸還是另有隱情,我是刑警寧澤灌闺,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布艰争,位于F島的核電站,受9級(jí)特大地震影響菩鲜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜惦积,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一接校、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧狮崩,春花似錦蛛勉、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至坦敌,卻和暖如春侣诵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背狱窘。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工杜顺, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蘸炸。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓躬络,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親搭儒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子穷当,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345