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 children = const [],
})
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: [
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: [
RaisedButton(
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => PositionScreen()));
},
color: Colors.blue,
child: Icon(Icons.add),
),
],
),
body: Stack(
fit: StackFit.expand,
children: [
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: [
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: [
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: [
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 {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Postion Title"),
),
body: Stack(
children: [
Positioned.fill(
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("第三个组件"),
),
),
],
),
);
}
}
效果是等价的
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 children = [
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: [
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: [
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: [
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: [
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情况下
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: [
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("第三个组件"),
),
),
],
),
);
}
}
这种情况是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: [
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("第三个组件"),
),
),
],
),
);
}
}
显示效果
大家会发现这个图的效果和上一个图的效果唯一区别就是黄色的第二个组件的位置有变化,这是为什么呢?
先说第一个组件和第三组件的位置为什么没有改变
Positioned(
top: 100.0,
left: 0,
right: 20,
child: Container(
color: Colors.blue,
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对它不会起任何作用