Flutter必备 | Flex布局完全解读
☝点击上方蓝字,关注我们!
本文字数:4894字
预计阅读时间:30分钟
作者介绍
本期特邀作者:张风捷特烈
掘金社区优秀作者,技术领域主要以Android为中心,兼顾涉猎全栈以及多语言。目前正全力研究flutter框架;热爱技术分享,常在掘金社区分享原创文章。
导读
Flutter作为一个跨平台的框架,最能展现它实力的当属界面了,一套界面可以同时运用在多个终端,这是一件非常令人兴奋的事。进入Flutter的世界,你的第一道拦路虎当属布局了,庞大的组件家族让你望而却步,但将他们进行梳理之后你就会发现,这是非常友善的。你可以在需要的时候使用相应的单体组件,需要排布的时候也会有众多的布局组件为你做后盾。
Flutter中的Flex布局作为五虎上将之一,当然虎父无犬子,其子Row和Column也能力非凡,使用时你有没有被mainAxisAlignment、crossAxisAlignment弄得晕头转向?本文将助你把他们纳入麾下,成为你布局战场上的猛将而非敌人。
我们先看一下父子三人在Flutter布局体系中的位置:多子组件布局
。
01
看一下Flex家族在源码中的位置
Flex家族包括Flex组件、Row组件、Column组件,位于widgets包中的basic文件中。
1---->[flutter/lib/src/widgets/basic.dart:3677]----
2class Flex extends MultiChildRenderObjectWidget {
3 Flex({
4 Key key,
5 this.direction,
6 this.mainAxisAlignment = MainAxisAlignment.start,
7 this.mainAxisSize = MainAxisSize.max,
8 this.crossAxisAlignment = CrossAxisAlignment.center,
9 this.textDirection,
10 this.verticalDirection = VerticalDirection.down,
11 this.textBaseline,
12 List<Widget> children = const <Widget>[],
13 }) : assert(direction != null),
14
15---->[flutter/lib/src/widgets/basic.dart:4015]----
16class Row extends Flex {
17
18---->[flutter/lib/src/widgets/basic.dart:4213]----
19class Column extends Flex {
02
Flex的属性一览
03
轴向:direction:Axis
1enum Axis {
2 horizontal,//水平
3 vertical,//竖直
4}
也就是水平排放还是竖直排放,可以看出默认情况下都是主轴顶头,交叉轴居中,比如horizontal下,主轴为水平轴,交叉轴则为竖直,也就是水平顶头,竖直居中。这里使用MultiShower快速展示,更好地对比出不同之处,MultiShower详见(https://juejin.im/post/5d2842ac6fb9a07eb15d82ad)。
1var direction =[Axis.horizontal,Axis.vertical];
2var show = MultiShower(direction,(e){
3 return Flex(
4 direction: e,
5 children: <Widget>[redBox,blueBox,yellowBox,greenBox],
6
7 );
8},color: Colors.black12,width: 300,height: 200);
9
10var redBox= Container(
11 color: Colors.red,
12 height: 50,
13 width: 50,
14);
15
16var blueBox= Container(
17 color: Colors.blue,
18 height: 30,
19 width: 60,
20);
21
22var yellowBox= Container(
23 color: Colors.yellow,
24 height: 50,
25 width: 100,
26);
27
28var greenBox= Container(
29 color: Colors.green,
30 height: 60,
31 width: 60,
32);
04
主轴方向:
mainAxisAlignment:MainAxisAlignment
主轴方向的排布规则,这里以水平为例,主轴为水平方向;竖直类比即可。
1enum MainAxisAlignment {
2 start,//顶头
3 end,//接尾
4 center,//居中
5 spaceBetween,//顶头接尾,其他均分
6 spaceAround,//中间的孩子均分,两头的孩子空一半
7 spaceEvenly,//均匀平分
1testMainAxisAlignment(){
2 var redBox= Container(
3 color: Colors.red,
4 height: 50,
5 width: 50,
6 );
7
8 var blueBox= Container(
9 color: Colors.blue,
10 height: 30,
11 width: 60,
12 );
13
14 var yellowBox= Container(
15 color: Colors.yellow,
16 height: 10,
17 width: 10,
18 );
19
20 var greenBox= Container(
21 color: Colors.green,
22 height: 50,
23 width: 10,
24 );
25
26 var mainAxisAlignment =[
27 MainAxisAlignment.start,MainAxisAlignment.center,
28 MainAxisAlignment.end,MainAxisAlignment.spaceAround,
29 MainAxisAlignment.spaceBetween,MainAxisAlignment.spaceEvenly];
30
31 var show = MultiShower(mainAxisAlignment,(e){
32 return Flex(
33 direction: Axis.horizontal,
34 mainAxisAlignment: e,
35 children: <Widget>[redBox,blueBox,yellowBox,greenBox],
36
37 );
38 },color: Colors.black12,width: 200,height: 150);
39 return show;
40}
05
交叉轴方向:
crossAxisAlignment:CrossAxisAlignment
1enum CrossAxisAlignment {
2 start,//顶头
3 end,//接尾
4 center,//居中
5 stretch,//伸展
6 baseline,//基线
7}
还是水平为例,交叉轴便是竖轴,这里可以看出他们的布局行为。
其中需要注意的是CrossAxisAlignment.baseline
使用时必须有textBaseline
,其中textBaseline
确定对齐的是那种基线,分为alphabetic
和ideographic
。
1testCrossAxisAlignment(){
2 var redBox= Container(
3 color: Colors.red,
4 height: 50,
5 width: 50,
6 );
7
8 var blueBox= Container(
9 color: Colors.blue,
10 height: 30,
11 width: 60,
12 );
13
14 var yellowBox= Container(
15 color: Colors.yellow,
16 height: 10,
17 width: 10,
18 );
19
20 var greenBox= Container(
21 color: Colors.green,
22 height: 50,
23 width: 10,
24 );
25
26 var crossAxisAlignment =[CrossAxisAlignment.start,CrossAxisAlignment.center,
27 CrossAxisAlignment.end,CrossAxisAlignment.stretch,CrossAxisAlignment.baseline];
28
29 var show = MultiShower(crossAxisAlignment,(e){
30 return Flex(
31 direction: Axis.horizontal,
32 crossAxisAlignment: e,
33 textBaseline: TextBaseline.alphabetic,//基线类型
34 children: <Widget>[redBox,blueBox,yellowBox,greenBox],
35
36 );
37 },color: Colors.black12,width: 200,height: 140);
38
39 return show;
40}
06
主轴尺寸:mainAxisSize
1enum MainAxisSize {
2 min,
3 max,
4}
此时改为MainAxisSize.min时,它不会延伸自己的区域,自会包裹内容。
1testMainAxisSize(){
2 var redBox= Container(
3 color: Colors.red,
4 height: 50,
5 width: 50,
6 );
7
8 var blueBox= Container(
9 color: Colors.blue,
10 height: 30,
11 width: 60,
12 );
13
14 var yellowBox= Container(
15 color: Colors.yellow,
16 height: 10,
17 width: 10,
18 );
19
20 var greenBox= Container(
21 color: Colors.green,
22 height: 50,
23 width: 10,
24 );
25
26 return Center(child: Flex(
27 direction: Axis.horizontal,
28 mainAxisSize: MainAxisSize.max,
29 children: <Widget>[redBox,blueBox,yellowBox,greenBox],
30
31 ),);
32}
33
07
文字方向:
textDirection:TextDirection
1enum TextDirection {
2 ltr,//从左到右
3 rtl,//从右到左
4}
这个非常好理解,不多言了。
1testTextDirection(){
2 var redBox= Container(
3 color: Colors.red,
4 height: 50,
5 width: 50,
6 );
7
8 var blueBox= Container(
9 color: Colors.blue,
10 height: 30,
11 width: 60,
12 );
13
14 var yellowBox= Container(
15 color: Colors.yellow,
16 height: 10,
17 width: 10,
18 );
19
20 var greenBox= Container(
21 color: Colors.green,
22 height: 50,
23 width: 10,
24 );
25
26 var textDirection =[TextDirection.ltr,TextDirection.rtl];
27 var show = MultiShower(textDirection,(e){
28 return Flex(
29 direction: Axis.horizontal,
30 textDirection: e,
31 children: <Widget>[redBox,blueBox,yellowBox,greenBox],
32
33 );
34 },color: Colors.black12,width: 200,height: 140);
35 return show;
36}
08
竖直方向排序:
verticalDirection:VerticalDirection
1enum VerticalDirection{
2 up,
3 down,
4}
1testVerticalDirection(){
2 var redBox= Container(
3 color: Colors.red,
4 height: 50,
5 width: 50,
6 );
7
8 var blueBox= Container(
9 color: Colors.blue,
10 height: 30,
11 width: 60,
12 );
13
14 var yellowBox= Container(
15 color: Colors.yellow,
16 height: 10,
17 width: 10,
18 );
19
20 var greenBox= Container(
21 color: Colors.green,
22 height: 50,
23 width: 10,
24 );
25
26 var verticalDirection =[VerticalDirection.up,VerticalDirection.down];
27
28 var show = MultiShower(verticalDirection,(e){
29 return Flex(
30 direction: Axis.vertical,
31 verticalDirection: e
32 children: <Widget>[redBox,blueBox,yellowBox,greenBox],
33
34 );
35 },color: Colors.black12,width: 200,height: 140);
36
37 return show;
38}
09
基线对齐方式:
textBaseline:TextBaseline
1enum TextBaseline {
2 alphabetic,
3 ideographic,
4}
1testTextBaseline(){
2 var redBox= Text(
3 "张风捷特烈",style: TextStyle(fontSize: 20,backgroundColor: Colors.red),
4 );
5
6 var blueBox= Text(
7 "toly",style: TextStyle(fontSize: 50,backgroundColor: Colors.blue),
8 );
9
10 var yellowBox= Text(
11 "1994",style: TextStyle(fontSize: 30,backgroundColor: Colors.green),
12 );
13
14 var textBaseline =[TextBaseline.alphabetic,TextBaseline.ideographic];
15
16 var show = MultiShower(textBaseline,(e){
17 return Flex(
18 direction: Axis.horizontal,
19 crossAxisAlignment: CrossAxisAlignment.baseline,
20 textBaseline: e,
21 children: <Widget>[redBox,blueBox,yellowBox],
22 );
23 },color: Colors.black12,width: 300,height: 140);
24
25 return show;
26}
10
Expanded与Flex的搭配
还有一点是关于Expanded,也比较保用,它能与Flex布局结合,变更孩子尺寸。
1c1:绿色 c2:红色 c3:黄色
21).Expanded--c2:c1和c3将不变,c2延伸自己占满剩余部分
32).同时Expanded--c2和c3,最终c2和c3的长度是一样的
43).同时Expanded--c1,c2和c3,最终c1,c2,c3长度都是一样的
11
Row和Column在源码中的实现
Row和Column作为最常使用的两大组件,看完Flex之后,看到他们的源码你应该不禁一笑,原来这么简单,Row继承了Flex的属性,仅是direction固定为Axis.horizontal,说明Row是一个水平方向的Flex布局;Column也类似,是一个竖直方向的布局。
1class Row extends Flex {
2 Row({
3 Key key,
4 MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
5 MainAxisSize mainAxisSize = MainAxisSize.max,
6 CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
7 TextDirection textDirection,
8 VerticalDirection verticalDirection = VerticalDirection.down,
9 TextBaseline textBaseline,
10 List<Widget> children = const <Widget>[],
11 }) : super(
12 children: children,
13 key: key,
14 direction: Axis.horizontal, <--------------重点在这
15 mainAxisAlignment: mainAxisAlignment,
16 mainAxisSize: mainAxisSize,
17 crossAxisAlignment: crossAxisAlignment,
18 textDirection: textDirection,
19 verticalDirection: verticalDirection,
20 textBaseline: textBaseline,
21 );
22}
23
24Column({
25 Key key,
26 MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
27 MainAxisSize mainAxisSize = MainAxisSize.max,
28 CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
29 TextDirection textDirection,
30 VerticalDirection verticalDirection = VerticalDirection.down,
31 TextBaseline textBaseline,
32 List<Widget> children = const <Widget>[],
33 }) : super(
34 children: children,
35 key: key,
36 direction: Axis.vertical, <--------------重点在这
37 mainAxisAlignment: mainAxisAlignment,
38 mainAxisSize: mainAxisSize,
39 crossAxisAlignment: crossAxisAlignment,
40 textDirection: textDirection,
41 verticalDirection: verticalDirection,
42 textBaseline: textBaseline,
43 );
44}
至此,Flutter中的Flex布局就已经完全解读完了,所以Flex在手,天下我有。
12
用Flex布局写个小例子
1.布局分析
光说不练假把式,光练不说空把式,下面就用一个布局来实际看一下Flex的强大:
1由上中下三行,可以用Column
2第一行由图标,文字和文字组成,其中两头分处左右,可以用Expanded处理
3中间比较复杂由一个Row中包含两部分,左边是一个两行Column的内容,右边是文字
4底部是一个Row,主轴对齐是start
可以画一个布局的结构图,从小到大,将总体拆分成肢体,再完成各肢体,最终拼合成需要的界面,这样逻辑会比较清晰,而不是上来就写布局,导致思维混乱。
2.布局代码
按照上面的逻辑,以小见大,一步步将界面的组成部分写出来,再进行组合,就像搭积木一样,这样你的布局层次会更加清晰。
1showItem() {
2 var infoStyle = TextStyle(color: Color(0xff999999), fontSize: 13);
3 var littleStyle = TextStyle(color: Colors.black, fontSize: 16);
4
5 var top = Row(//顶部,通过Flex布局的Row进行横向排列,Expanded中间
6 children: <Widget>[
7 Image.asset("images/icon_head.png", width: 20, height: 20),
8 Expanded(
9 child: Padding(
10 child: Text("张风捷特烈"),
11 padding: EdgeInsets.only(left: 4),
12 ),
13 ),
14 Text(
15 "Flutter/Dart",
16 style: infoStyle,
17 )
18 ],
19 );
20
21 var content = Column(//中间文字内容,交叉轴为start
22 mainAxisSize: MainAxisSize.min,
23 crossAxisAlignment: CrossAxisAlignment.start,
24 children: <Widget>[
25 Text(
26 "[Flutter必备]-Flex布局完全解读",
27 style: littleStyle,
28 maxLines: 2,
29 overflow: TextOverflow.ellipsis,
30 ),
31 Padding(
32 child: Text("也就是水平排放还是竖直排放,可以看出默认情况下都是主轴顶头,"
33 "交叉轴居中比如horizontal下主轴为水平轴,交叉轴则为竖直。也就是水平顶头,竖直居中"
34 "这里使用MultiShower快速展示,更好的对比出不同之处",
35 style: infoStyle, maxLines: 2, overflow: TextOverflow.ellipsis),
36 padding: EdgeInsets.only(top: 5),
37 ),
38 ],
39 );
40
41 var center = Row(//中间的部分
42 children: <Widget>[
43 Expanded(
44 child: Padding(
45 child: content,
46 padding: EdgeInsets.all(5),
47 )),
48 ClipRRect(
49 borderRadius: BorderRadius.all(Radius.circular(5)),
50 child: Image.asset("images/wy_300x200.jpg",
51 width: 80, height: 80, fit: BoxFit.cover),)
52 ],
53 );
54
55 var end = Row(//底部
56 children: <Widget>[
57 Icon(
58 Icons.grade,
59 color: Colors.green,
60 size: 20,
61 ),
62 Text(
63 "1000W",
64 style: infoStyle,
65 ),
66 Padding(child:Icon(Icons.tag_faces, color: Colors.lightBlueAccent, size: 20),
67 padding: EdgeInsets.symmetric(horizontal: 5),),
68 Text("2000W", style: infoStyle),
69 ],
70 );
71
72 var result = Card(//总体拼合
73 child: Container(
74 height: 160,
75 color: Colors.white,
76 padding: EdgeInsets.all(10),
77 child: Column(children: <Widget>[top, Expanded(child: center), end])));
78 return result;
79}
3.静态界面的封装使用
一个静态界面也只是一个玩偶,我们的目标是让静态组件成为一个有灵魂的组件,可以很容易复用。主要思路是抽离出写死字段抽离出来,自定义一个描述类作为入参,最终对点击事件进行回调,这样一个组件就封装好了,你只需要准备描述信息即可,动态变更界面。
1---->[使用]----
2ArticlePanel(
3 article: ArticleBean(userName: "张风捷特烈",
4 title: "[Flutter必备]-Flex布局完全解读",
5 info: "也就是水平排放还是竖直排放,可以看出默认情况下都是主轴顶头,"
6 "交叉轴居中比如horizontal下主轴为水平轴,交叉轴则为竖直。也就是水平顶头,竖直居中"
7 "这里使用MultiShower快速展示,更好的对比出不同之处",
8 type: "Flutter/Dart",
9 starCount: "2000",
10 commentCount: "3000",
11 userIcon: Image.asset("images/icon_head.png"),
12 cover: Image.asset("images/wy_300x200.jpg",fit: BoxFit.cover)
13 ),
14 );
4.封装组件
1import 'package:flutter/material.dart';
2
3class ArticlePanel extends StatelessWidget {
4 ArticlePanel({Key key, this.article, this.onTap}) : super(key: key);
5
6 final ArticleBean article;
7 final TapCallback onTap;
8
9
10 Widget build(BuildContext context) {
11 return _showItem(article);
12 }
13
14 _showItem(ArticleBean article) {
15 var infoStyle = TextStyle(color: Color(0xff999999), fontSize: 13);
16 var littleStyle = TextStyle(color: Colors.black, fontSize: 16);
17 var top = Row(
18 //顶部,通过Flex布局的Row进行横向排列
19 children: <Widget>[
20 Container(child: article.userIcon, width: 20, height: 20),
21 Expanded(
22 child: Padding(
23 child: Text(article.userName),
24 padding: EdgeInsets.only(left: 4),
25 ),
26 ),
27 Text(
28 article.type,
29 style: infoStyle,
30 )
31 ],
32 );
33
34 var content = Column(
35 //中间文字内容,交叉轴为start
36 mainAxisSize: MainAxisSize.min,
37 crossAxisAlignment: CrossAxisAlignment.start,
38 children: <Widget>[
39 Text(
40 article.title,
41 style: littleStyle,
42 maxLines: 2,
43 overflow: TextOverflow.ellipsis,
44 ),
45 Padding(
46 child: Text(article.info,
47 style: infoStyle, maxLines: 2, overflow: TextOverflow.ellipsis),
48 padding: EdgeInsets.only(top: 5),
49 ),
50 ],
51 );
52
53 var center = Row(
54 //中间的部分
55 children: <Widget>[
56 Expanded(
57 child: Padding(
58 child: content,
59 padding: EdgeInsets.all(5),
60 )),
61 ClipRRect(
62 borderRadius: BorderRadius.all(Radius.circular(5)),
63 child: Container(
64 width: 80,
65 height: 80,
66 child: article.cover,
67 ),
68 )
69 ],
70 );
71
72 var end = Row(
73 //底部
74 children: <Widget>[
75 Icon(
76 Icons.grade,
77 color: Colors.green,
78 size: 20,
79 ),
80 Text(
81 article.starCount,
82 style: infoStyle,
83 ),
84 Padding(
85 child: Icon(Icons.tag_faces, color: Colors.lightBlueAccent, size: 20),
86 padding: EdgeInsets.symmetric(horizontal: 5),
87 ),
88 Text(article.commentCount, style: infoStyle),
89 ],
90 );
91
92 var result = Card(
93 //总体拼合
94 child: InkWell(
95 onTap: () {
96 if (this.onTap != null) {
97 onTap(article);
98 }
99 },
100 child: Container(
101 height: 160,
102 padding: EdgeInsets.all(10),
103 child:
104 Column(children: <Widget>[top, Expanded(child: center), end]),
105 )));
106
107 return result;
108 }
109}
110
111typedef TapCallback = void Function(ArticleBean bean);
112
113class ArticleBean {
114 Image userIcon; //头像
115 Image cover; //图片
116 String userName; //用户名
117 String title; //标题
118 String type; //类型
119 String info; //简介
120 String starCount; //赞
121 String commentCount;
122
123 ArticleBean(
124 {this.userIcon,
125 this.cover,
126 this.userName,
127 this.title,
128 this.type,
129 this.info,
130 this.starCount,
131 this.commentCount}); //评论数
132
133}
也许你还想看
Flutter移动端实战手册
新闻推荐系统的CTR预估模型
互联网架构演进之路
加入搜狐技术作者天团
千元稿费等你来!
戳这里!☛