前言
接着Flutter基础控件篇[1],这一篇主要记录Flutter容器控件的使用,也就是常用的布局控件(Layout)。
在学习的过程中,除了官方文档之外,还参考了不少其他的博客资料,发现有挺多文章在一些细节问题上其实并没有说明白,甚至有的实测之后证实是错的,有的属性的解释像是谷歌翻译了一下直接就贴过来,实际含义其实都没有说清楚,然后还被多处转载粘贴,一处错误可以在好些篇文章里同样出现。
用心实在的好资料还是少数啊!
这里笔记中的内容不一定全都对,可能有遗漏或者实测场景不够全面,在复杂特殊的某些场景可能有不符合的结果,但是里面所有内容都是经过代码验证根据实际结果总结来的,也没有类似机器翻译的内容在里面让人看完模棱两可啥也没明白跟没看一样。没理解的东西不往上记,没验证的东西不往上记,尽量保证以后回头来看,不会坑到自己。
正文
下面是几个常用的容器布局控件:
Container
容器控件,可以添加一个子控件,设置宽度高度,padding,margin,背景颜色,背景装饰等属性,基本包含了容器控件的常用基本属性。
基本使用:
Widget build(BuildContext context) {
return Container(
width: 200.0,
height: 150.0,
margin: EdgeInsets.fromLTRB(25, 15, 0, 0),
padding: EdgeInsets.fromLTRB(10, 15, 10, 15),
alignment: Alignment(0, 0),
decoration: BoxDecoration(boxShadow: [
//卡片阴影
BoxShadow(
color: Colors.black54,
offset: Offset(5.0, 5.0),
blurRadius: 7.0)
]),
child: Image.network(
Consts.imgUrl,
width: 200.0,
height: 150.0,
));
}
常用属性:
width:宽度,如果设置具体值则为具体值,不设置则包裹子控件大小,设置为double.infinity一般会充满父控件或者充满屏幕(如果没有其他特别的约束条件);
height:高度,使用方法基本同width;
margin:设置控件与其他控件的外边距;
padding:设置内边距;
decoration:背景装饰;
alignment:设置子控件的方位,用Alignment属性值来表示,具体对应的方位如下:
- Alignment topLeft = Alignment(-1.0, -1.0);
- Alignment topCenter = Alignment(0.0, -1.0);
- Alignment topRight = Alignment(1.0, -1.0);
- Alignment centerLeft = Alignment(-1.0, 0.0);
- Alignment center = Alignment(0.0, 0.0);
- Alignment centerRight = Alignment(1.0, 0.0);
- Alignment bottomLeft = Alignment(-1.0, 1.0);
- Alignment bottomCenter = Alignment(0.0, 1.0);
- Alignment bottomRight = Alignment(1.0, 1.0);
Row(横向布局)、Column(纵向布局)
类似Android中的LinearLayout,只是拆分开来,一个横向排列,一个纵向排列,它俩布局除了方向不同之外,其他的属性基本含义都一样:
基本用法:
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.max,//主轴方向(纵向)占空间尽量大,(如果没有其他特别的约束条件则要么填满屏幕,要么填满父布局)
verticalDirection: VerticalDirection.down,//控件顺序正常顺序,VerticalDirection.up则倒叙
crossAxisAlignment: CrossAxisAlignment.center,//纵轴方向上控件排列规则(居中 靠边等)
mainAxisAlignment: MainAxisAlignment.spaceEvenly,//主轴方向上控件排列规则(居中 靠边 均匀分布等)
textBaseline: TextBaseline.alphabetic,
children: [
Text('verticalDirection:VerticalDirection.up'),
Text('crossAxisAlignment: CrossAxisAlignment.center'),
Text('mainAxisAlignment: MainAxisAlignment.spaceEvenly'),
Text('textBaseline: TextBaseline.ideographic'),
],
);
}
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.max,//主轴方向(横向)占空间尽量大,(如果没有其他特别的约束条件则要么填满屏幕,要么填满父布局)
verticalDirection: VerticalDirection.up,//实测 up和down效果一样,好像没生效。
crossAxisAlignment: CrossAxisAlignment.center,//纵轴方向(纵向)上控件排列规则(居中 靠边等)
mainAxisAlignment: MainAxisAlignment.spaceAround,////主轴方向上控件排列规则(居中 靠边 均匀分布等)
textBaseline: TextBaseline.ideographic,
textDirection: TextDirection.rtl,//从左到右排列
children: [
Container(
color: Colors.blue[100],
width: 70,
height: 70,
),
Container(color: Colors.blue[200], width: 70, height: 70),
Container(color: Colors.blue[300], width: 70, height: 70),
Container(color: Colors.blue[400], width: 70, height: 70),
],
);
}
这里mainAxis和crossAxis代表主轴方向和纵轴方向,Row(横向布局)主轴方向就是横向,纵轴方向就是纵向;Column(纵向布局)主轴方向就是纵向,纵轴方向就是横向;
几个常用属性:
mainAxisSize:
- MainAxisSize.max:主轴方向占空间尽量大,(如果没有其他特别的约束条件则要么填满屏幕,要么填满父布局);
- MainAxisSize.min:主轴方向占空间尽量小,基本就是包裹子控件的尺寸了
textDirection
- TextDirection.ltr,从左到右排列;
- TextDirection.rtl,从右到左排列;
textBaseline
- TextBaseline.alphabetic,使用按照排列字母字符的基准线排列方式
- TextBaseline.ideographic,使用按照表意字字符的基准线排列方式(比如中文);
表意字好像是说可以拆解结构的文字类型,通过各种部首的组合可以形成各种文字,例如中文。(这里有一个视频,简单了解什么是表意字)
verticalDirection
- VerticalDirection.up,从下向上排列;
- VerticalDirection.down,从上向下排列;
mainAxisAlignment:
- start:将children放置在主轴的起点;
- center:将children放置在主轴的中心;
- end:将children放置在主轴的末尾;
- spaceAround:将主轴方向上的空白区域均分,使得children之间的空白区域相等,但是首尾child的空白区域为1/2;
- spaceBetween:将主轴方向上的空白区域均分,使得children之间的空白区域相等,首尾child都靠近首尾,没有间隙;
- spaceEvenly:将主轴方向上的空白区域均分,使得children之间的空白区域相等,包括首尾child;
crossAxisAlignment:
- start:将children放置在纵轴的起点;
- center:将children放置在纵轴的中心;
- end:将children放置在纵轴的末尾;
Wrap 流式布局
Wrap包裹子控件,也可以设置横向和纵向,它与Row和Column的区别在于:Row和Column只有一行或一列,子控件尺寸超出了就会显示不全,但是Wrap会自动换行。
Widget build(BuildContext context) {
return Wrap(
direction: Axis.horizontal,
//start end值受textDirection具体值的影响
alignment: WrapAlignment.start,
crossAxisAlignment: WrapCrossAlignment.end,//好像是runAlignment生效了 crossAxisAlignment没有生效
runAlignment: WrapAlignment.center,//纵轴方向对齐方式
spacing: 15,//主轴方向间距15
runSpacing: 10,//纵轴方向间距10
textDirection: TextDirection.ltr,
children: [
Container(color: Colors.green[100], width: 70, height: 70,),
Container(color: Colors.green[200], width: 70, height: 70),
Container(color: Colors.green[300], width: 70, height: 70),
Container(color: Colors.green[400], width: 70, height: 70),
Container(color: Colors.green[100], width: 70, height: 70,),
Container(color: Colors.green[200], width: 70, height: 70),
Container(color: Colors.green[300], width: 70, height: 70),
Container(color: Colors.green[400], width: 70, height: 70),
],
);
}
常用属性:
direction:排列方向
- Axis.horizontal 横向布局(主轴为横向)
- Axis.vertical 纵向布局(主轴为纵向)
alignment:使用WrapAlignment的属性值,但是含义和Row、Column基本一致;
spacing:主轴方向子控件间距;
runSpacing:纵轴方向的间距(主轴是横向则纵轴是纵向,主轴是纵向则纵轴是横向);
runAlignment:纵轴方向的对齐方式;
这里有个疑惑,crossAxisAlignment和runAlignment都表示纵轴方式的对齐方式,实测的结果似乎crossAxisAlignment并没有起作用,只有runAlignment起作用了,这个还有待进一步考证。
Stack 重叠布局
可以把子控件叠在一起来展示。Stack可以搭配Positioned控件一起使用,Positioned控件可以控制子控件在Stack中的具体位置(设置距离左上右下的具体参数)和控件大小(设置宽高),使用场景会非常多。
Widget build(BuildContext context) {
return Stack(
fit:StackFit.loose,
alignment: AlignmentDirectional.center,
textDirection: TextDirection.ltr,
overflow: Overflow.clip,
children: [
Container(width: 120,height: 120,color: Colors.cyan[200],),
Container(width: 90,height: 90,color: Colors.cyan[400],),
Container(width: 60,height: 60,color: Colors.cyan[600],),
Positioned(left: 100,top: 100,child: Container(width: 120,height: 120,color: Colors.cyan[800],),)//指定位置,超出stack范围,测试overflow属性
],
);
}
常用属性:
alignment:设置子控件的方位,用AlignmentDirectional的值来表示,这个类里面的start和end受textDirection具体值的影响,TextDirection.ltr(左到右)对应start为左边,,TextDirection.rtl(右到左)对应start为右边,属性值具体对应的方位如下
- AlignmentDirectional topStart = AlignmentDirectional(-1.0, -1.0);
- AlignmentDirectional topCenter = AlignmentDirectional(0.0, -1.0);
- AlignmentDirectional topEnd = AlignmentDirectional(1.0, -1.0);
- AlignmentDirectional centerStart = AlignmentDirectional(-1.0, 0.0);
- AlignmentDirectional center = AlignmentDirectional(0.0, 0.0);
- AlignmentDirectional centerEnd = AlignmentDirectional(1.0, 0.0);
- AlignmentDirectional bottomStart = AlignmentDirectional(-1.0, 1.0);
- AlignmentDirectional bottomCenter = AlignmentDirectional(0.0, 1.0);
- AlignmentDirectional bottomEnd = AlignmentDirectional(1.0, 1.0);
overflow:
- Overflow.clip 当子控件超出Stack的显示范围时,截掉超出范围的部分不显示;
- Overflow.visible 当子控件超出Stack的显示范围时也显示超出范围的部分;
fit:子控件适应Stack控件尺寸的方式,这个属性对于非Positioned控件内的子控件起作用。
- StackFit.loose:使用此属性时,对于非Positioned控件内的子控件,它的尺寸会小于Stack控件的尺寸,比如Stack的尺寸是300x600,那么子控件的尺寸宽度允许在0到300之间,高度允许在0到600之间;
- StackFit.expand:使用此属性时,对于非Positioned控件内的子控件,它的尺寸会撑满Stack的尺寸。比如Stack的尺寸是300x600,那么子控件的尺寸宽度会是300,高度会是600;
- StackFit.passthrough:对子控件的约束条件直接由Stack的父控件传递给Stack的子控件;
对应Android中的match_parent和wrap_content
在Android中我们知道,每一个控件都可以直接设置宽高属性为match_parent和wrap_content,选择尺寸为充满父布局还是包裹子控件内容,使用非常方便。但是Flutter中没有这样的属性可以直接使用,其实梳理完上面几个常用容器控件的属性之后,大致可以组合出与match_parent和wrap_content相同的效果,这样,习惯了Android布局思维的同学转过来写Flutter控件也会顺手和舒心很多,具体实现方式其实不止一种,以下是在stackoverflow上面找的一个答案,实测有效。
Wrap_content ,Wrap_content :
//use this as child
Wrap(
children: [*your_child*])
Match_parent,Match_parent:
//use this as child
Container(
height: double.infinity,
width: double.infinity,child:*your_child*)
Match_parent,Wrap_content :
//use this as child
Row(
mainAxisSize: MainAxisSize.max,
children: [*your_child*],
);
Wrap_content ,Match_parent:
//use this as child
Column(
mainAxisSize: MainAxisSize.max,
children: [your_child],
);
尾声
最后再贴一遍笔记对应的Demo项目GitHub地址,点这里。
如果发现问题,欢迎斧正!
以上。