☝点击上方蓝字,关注我们!

本文字数: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 @required 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),1415---->[flutter/lib/src/widgets/basic.dart:4015]----16class Row extends Flex {1718---->[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.cn/post/6844903888307355662)。
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); 910var redBox= Container(11 color: Colors.red,12 height: 50,13 width: 50,14);1516var blueBox= Container(17 color: Colors.blue,18 height: 30,19 width: 60,20);2122var yellowBox= Container(23 color: Colors.yellow,24 height: 50,25 width: 100,26);2728var 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 );1314 var yellowBox= Container(15 color: Colors.yellow,16 height: 10,17 width: 10,18 );1920 var greenBox= Container(21 color: Colors.green,22 height: 50,23 width: 10,24 );2526 var mainAxisAlignment =[27 MainAxisAlignment.start,MainAxisAlignment.center,28 MainAxisAlignment.end,MainAxisAlignment.spaceAround,29 MainAxisAlignment.spaceBetween,MainAxisAlignment.spaceEvenly];3031 var show = MultiShower(mainAxisAlignment,(e){32 return Flex(33 direction: Axis.horizontal,34 mainAxisAlignment: e,35 children: <Widget>[redBox,blueBox,yellowBox,greenBox],3637 );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 );1314 var yellowBox= Container(15 color: Colors.yellow,16 height: 10,17 width: 10,18 );1920 var greenBox= Container(21 color: Colors.green,22 height: 50,23 width: 10,24 );2526 var crossAxisAlignment =[CrossAxisAlignment.start,CrossAxisAlignment.center,27 CrossAxisAlignment.end,CrossAxisAlignment.stretch,CrossAxisAlignment.baseline];2829 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],3536 );37 },color: Colors.black12,width: 200,height: 140);3839 return show;40}
06
主轴尺寸:mainAxisSize
1enum MainAxisSize {2 min,3 max,4}
当父容器的宽未约束,Flex默认会将自身尽可能延伸,这便是MainAxisSize.max。
此时改为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 );1314 var yellowBox= Container(15 color: Colors.yellow,16 height: 10,17 width: 10,18 );1920 var greenBox= Container(21 color: Colors.green,22 height: 50,23 width: 10,24 );2526 return Center(child: Flex(27 direction: Axis.horizontal,28 mainAxisSize: MainAxisSize.max,29 children: <Widget>[redBox,blueBox,yellowBox,greenBox],3031 ),);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 );1314 var yellowBox= Container(15 color: Colors.yellow,16 height: 10,17 width: 10,18 );1920 var greenBox= Container(21 color: Colors.green,22 height: 50,23 width: 10,24 );2526 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],3233 );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 );1314 var yellowBox= Container(15 color: Colors.yellow,16 height: 10,17 width: 10,18 );1920 var greenBox= Container(21 color: Colors.green,22 height: 50,23 width: 10,24 );2526 var verticalDirection =[VerticalDirection.up,VerticalDirection.down];2728 var show = MultiShower(verticalDirection,(e){29 return Flex(30 direction: Axis.vertical,31 verticalDirection: e32 children: <Widget>[redBox,blueBox,yellowBox,greenBox],3334 );35 },color: Colors.black12,width: 200,height: 140);3637 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 ); 910 var yellowBox= Text(11 "1994",style: TextStyle(fontSize: 30,backgroundColor: Colors.green),12 );1314 var textBaseline =[TextBaseline.alphabetic,TextBaseline.ideographic];1516 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);2425 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}2324Column({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由上中下三行,可以用Column2第一行由图标,文字和文字组成,其中两头分处左右,可以用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 );2021 var content = Column(//中间文字内容,交叉轴为start22 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 );4041 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 );5455 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 );7172 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 @override 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 )));106107 return result;108 }109}110111typedef TapCallback = void Function(ArticleBean bean);112113class 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;122123 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}); //评论数132133}
也许你还想看
Flutter移动端实战手册
新闻推荐系统的CTR预估模型
互联网架构演进之路
加入搜狐技术作者天团
千元稿费等你来!
戳这里!☛
