Flutter基础控件篇[2]--布局

前言

接着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: <Widget>[
        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: <Widget>[
        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: <Widget>[
        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: <Widget>[
        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: <Widget>[*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: <Widget>[*your_child*],
);
Wrap_content ,Match_parent:
 //use this as child
Column(
  mainAxisSize: MainAxisSize.max,
  children: <Widget>[your_child],
);

尾声

最后再贴一遍笔记对应的Demo项目GitHub地址,点这里
如果发现问题,欢迎斧正!
以上。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,710评论 4 376
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,839评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,295评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,776评论 0 223
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,198评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,074评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,200评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,986评论 0 214
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,733评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,877评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,348评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,675评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,393评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,209评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,212评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,003评论 2 280

推荐阅读更多精彩内容