60

Flutter 必备:Flex 布局完全解读

 5 years ago
source link: https://www.tuicool.com/articles/qI7zIfJ
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

E7zAr2Z.gif

本文字数: 4894

预计阅读时间: 30分钟

作者介绍

本期特邀作者:张风捷特烈

掘金社区优秀作者,技术领域主要以Android为中心,兼顾涉猎全栈以及多语言。目前正全力研究flutter框架;热爱技术分享,常在掘金社区分享原创文章。

Flutter作为一个跨平台的框架,最能展现它实力的当属界面了,一套界面可以同时运用在多个终端,这是一件非常令人兴奋的事。进入Flutter的世界,你的第一道拦路虎当属布局了,庞大的组件家族让你望而却步,但将他们进行梳理之后你就会发现,这是非常友善的。你可以在需要的时候使用相应的单体组件,需要排布的时候也会有众多的布局组件为你做后盾。

Flutter中的Flex布局作为五虎上将之一,当然虎父无犬子,其子Row和Column也能力非凡,使用时你有没有被mainAxisAlignment、crossAxisAlignment弄得晕头转向?本文将助你把他们纳入麾下,成为你布局战场上的猛将而非敌人。

我们先看一下父子三人在Flutter布局体系中的位置: 多子组件布局

ArIB7ny.jpg!web

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),
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的属性一览

bQBjIfm.png!web

03

轴向: direction:Axis

1enum Axis {
2  horizontal,//水平
3  vertical,//竖直
4}

也就是水平排放还是竖直排放,可以看出默认情况下都是主轴顶头,交叉轴居中,比如horizontal下,主轴为水平轴,交叉轴则为竖直,也就是水平顶头,竖直居中。这里使用MultiShower快速展示,更好地对比出不同之处,MultiShower详见(https://juejin.im/post/5d2842ac6fb9a07eb15d82ad)。

jIn2i2i.png!web

 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

mErMFnr.png!web

 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}

当父容器的宽未约束,Flex默认会将自身尽可能延伸,这便是MainAxisSize.max。

jeq2miu.png!web

此时改为MainAxisSize.min时,它不会延伸自己的区域,自会包裹内容。

q636f2e.png!web

 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}

这个非常好理解,不多言了。

VZvIzyj.jpg!web

 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}

ZB7vqaa.jpg!web

 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}

6N3EB3B.jpg!web

 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长度都是一样的

EF3EreR.png!web

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的强大:

ERVFJ3V.png!web

首先简单的分析一下:

7jMvear.jpg!web

1由上中下三行,可以用Column
2第一行由图标,文字和文字组成,其中两头分处左右,可以用Expanded处理  
3中间比较复杂由一个Row中包含两部分,左边是一个两行Column的内容,右边是文字  
4底部是一个Row,主轴对齐是start

可以画一个布局的结构图,从小到大,将总体拆分成肢体,再完成各肢体,最终拼合成需要的界面,这样逻辑会比较清晰,而不是上来就写布局,导致思维混乱。

Bfymyyz.jpg!web

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  @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            )));
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

深入理解Flutter多线程

Flutter移动端实战手册

新闻推荐系统的CTR预估模型

互联网架构演进之路

加入 搜狐技术作者天团

千元稿费 等你来!

戳这里!

m6fYB3B.jpg!web

ayiqaaB.gif


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK