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)
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,
),
],
),
);
}
}
顯示內(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間距
這個(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è)組件"),
),
),
],
),
);
}
}
第一個(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è)組件"),
),
),
],
),
);
}
}
為了演示這個(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組件逸月,效果圖如下
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,
),
],
),
);
}
}
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,
),
],
),
);
}
}
AlignmentDirectional.center 對(duì)齊方式
所有的Widget 以Stack的中心位置
AlignmentDirectional.centerEnd 對(duì)齊方式
所有的Widget 在Stack的中心位置并且右邊跟stack右邊挨著
使用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è)組件"),
),
),
],
),
);
}
}
這種情況是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è)組件"),
),
),
],
),
);
}
}
顯示效果
大家會(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ì)起任何作用