Flutter 之Stack 組件
Stack
Stack 這個是Flutter中布局用到的組件,跟Android中FrameLayout很像,都是可以疊加的現實View,具體的使用細節還是有些不同的,我們一一說來
Stack({ Key key, this.alignment = AlignmentDirectional.topStart, this.textDirection, this.fit = StackFit.loose, this.overflow = Overflow.clip, List<Widget> children = const <Widget>[], })
-
alignment : 指的是子Widget的對其方式,默認情況是以左上角為開始點 ,這個屬性是最難理解的,它區分為使用了Positioned和未使用Positioned定義兩種情況,沒有使用Positioned情況還是比較好理解的,下面會詳細講解的
-
fit :用來決定沒有Positioned方式時候子Widget的大小,StackFit.loose 指的是子Widget 多大就多大,StackFit.expand使子Widget的大小和父組件一樣大
-
overflow :指子Widget 超出Stack時候如何顯示,默認值是Overflow.clip,子Widget超出Stack會被截斷,
Overflow.visible超出部分還會顯示的
初探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個組件,第一個組件比較大100100,第二個組件稍微小點90**90
,第三個組件最小80*80,顯示的方式是能看見第一個和第二個組件的部分區域,第三個組件是能全部顯示出來

fit 屬性使用
如果指定是StackFit.expand,所以的子組件會和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, ), ], ), ); } }

顯示內容就只最后一個組件,雖然我們給這個組件指定了一個80*80的寬高是不會 生效的,因為我們已經指定了子元素和Stack一樣大小,也就是說設置了StackFit.expand,StackFit.expand的效果優先
Positioned
這個使用控制Widget的位置,通過他可以隨意擺放一個組件,有點像絕對布局
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("第一個組件"), ), ), Positioned( top: 200, right: 100, child: Container( color: Colors.yellow, child: Text("第二個組件"), ), ), Positioned( left: 100.0, child: Container( color: Colors.red, child: Text("第三個組件"), ), ), ], ), ); } }
這個例子的效果就是
- 第一個組件距離頂部Stack 有100的間距
- 第二個組件距離頂部200,距離右邊100間距
- 第三個組件距離左邊100間距

這個地方有注意地方,例如說第一個組件我指定距離左邊0個距離,距離右邊0個距離,那么這個組件的寬度就是屏幕這么寬,因為你指定的左右間距都是0,也就是沒有間距
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("第一個組件"), ), ), Positioned( top: 200, right: 100, child: Container( color: Colors.yellow, child: Text("第二個組件"), ), ), Positioned( bottom: 0, left: 0, right: 0, child: Container( color: Colors.red, child: Text("第三個組件"), ), ), ], ), ); } }

第一個組件和第三個組件寬度都是整個屏幕這個寬度,第三組件我又指定了距離底部bottom為0,所以第三組件是在最底下
那么我們如果指定了left&&right&&top&bottom都是0的情況呢?
那么這個組件就是和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("第一個組件"), ), ), Positioned( top: 200, right: 100, child: Container( color: Colors.yellow, child: Text("第二個組件"), ), ), Positioned( bottom: 0, left: 0, right: 0, child: Container( color: Colors.red, child: Text("第三個組件"), ), ), ], ), ); } }

為了演示這個效果,我在第一個組件上加上了一個黑色的標記,代碼中添加的第一組件就是和Stack一樣大的,系統也提供了一個方法Positioned.fill 這個方法的效果和圖片上是一樣的
class PositionScreen extends StatelessWidget {
效果是等價的
GridTile 是如何使用Positioned 定位的
GridTile 是一個Flutter 提供的組件的,用來在GridView中給Item 增加更豐富的展示用的,GridTile 的布局方式就是Stack,在源代碼中就到Positioned 來進行位置控制,主要提供三個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 這個組件,在底部繪制footer Widget組件,效果圖如下


alignment 屬性理解
沒有使用Positioned定位情況
在我們初探Stack組件中講解的例子就是沒有使用Positioned定位的情況,默認的子組件的對齊方式就是以左上角為起點開始排列子Widget
AlignmentDirectional.bottomEnd 對齊方式
所有的Widget 以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, 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, ), ], ), ); } }

AlignmentDirectional.topEnd 對齊方式
所有的Widget 以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, 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, ), ], ), ); } }

AlignmentDirectional.center 對齊方式
所有的Widget 以Stack的中心位置

AlignmentDirectional.centerEnd 對齊方式
所有的Widget 在Stack的中心位置並且右邊跟stack右邊挨着

使用Positioned情況下

這種情況是alignment 是默認值的效果,下面我們修改一下alignment的對應的值
1. AlignmentDirectional.bottomEnd
bottomEnd是子Widget的底部和Stack底部對齊,並且子Widget的右邊和Stack右邊對齊
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("第一個組件"), ), ), Positioned( top: 200, bottom: 20, child: Container( color: Colors.yellow, child: Text("第二個組件"), ), ), Positioned( bottom: 0, left: 0, right: 0, child: Container( color: Colors.red, child: Text("第三個組件"), ), ), ], ), ); } }
顯示效果

大家會發現這個圖的效果和上一個圖的效果唯一區別就是黃色的第二個組件的位置有變化,這是為什么呢?
先說第一個組件和第三組件的位置為什么沒有改變
第一個組件top是100,說明這個組件距離頂部的距離是固定的,雖然Stack的aligment=AlignmentDirectional.bottomEnd,是不生效的,當這兩個屬性沖突時,以Positioned的距離為主,為什么第一組件右邊也沒有Stack的右邊對齊呢?因為right=20,第一個組件右邊距離已經可以確認了,所以也不受到aligment=AlignmentDirectional.bottomEnd的影響
第三個組件也是一樣的,第三個組件的寬度是Stack的寬度,高度取決於Text組件的高度,最關鍵的是它的bottom=0,也就是第三個組件要和Stack組件的低邊界對齊,所以它的效果和上面的圖是沒有變化的
Positioned( top: 200, bottom: 20, child: Container( color: Colors.yellow, child: Text("第二個組件"), ), ),
第二個組件為什么會跑到右邊呢?
因為第二個組件的高度是可以確認出來的,top=200,bottom=20,設置這兩個屬性就能推斷出第二組的高度是多大,但是第二個組件的寬度取決於Text("第二個組件") 的寬度,顯然是水平方向上是不能填滿Stack的,這個時候AlignmentDirectional.bottomEnd屬性起作用了,bottom的距離已經確定了,所以底部的對齊方式是不會變化了,但是第二組件右邊的對齊方式是可以收到AlignmentDirectional.bottomEnd影響的,所以第二組件展示的位置就是圖片上展示的位置
總結一下使用Positioned 定位方式aligment 方式對它的影響
Positioned 有四個屬性top、bottom、left、right,(top、bottom)決定了垂直方向上的位置了,(left、right)決定了水平方向上的位置,不管水平方向上還是垂直方向上只要設定了一個值該方向上位置就已經確定過了,aligment對這這個方向上就不會起作用了,如果Positioned 設置了其中任意三個方向的值,這個Widget的位置就是固定的,aligment對它不會起任何作用
作者:飢餓的大灰狼
來源:簡書