Flutter 定义子控件在父容器中排版位置

前言

在 Flutter 中,一个完整的界面通常就是由这些小型、单用途的基本控件元素依据特定的布局规则堆砌而成的。搭建出一个漂亮的布局,需要了解布局规则,以及这些规则与其他平台类似概念的差别。

在 Flutter 中一切皆 Widget,那么布局也不例外。但与基本控件元素不同,布局类的 Widget 并不会直接呈现视觉内容,而是作为承载其他子 Widget 的容器。

这些布局类的 Widget,内部都会包含一个或多个子控件,并且都提供了摆放子控件的不同布局方式,可以实现子控件的对齐、嵌套、层叠和缩放等。而我们要做的就是,通过各种定制化的参数,将其内部的子 Widget 依照自己的布局规则放置在特定的位置上,最终形成一个漂亮的布局。

Flutter 提供了 31 种布局 Widget,对布局控件的划分非常详细,一些相同(或相似)的视觉效果可以通过多种布局控件实现,因此布局类型相比原生 Android、iOS 平台多了不少。比如,Android 布局一般就只有 FrameLayout、LinearLayout、RelativeLayout、GridLayout 和 TableLayout 这 5 种,而 iOS 的布局更少,只有 Frame 布局和自动布局两种。

为了建立起对布局类 Widget 的认知,了解基本布局类 Widget 的布局特点和用法,从而学以致用快速上手开发,将对几类在开发 Flutter 应用时,最常用也最有代表性的布局 Widget,包括单子 Widget 布局、多子 Widget 布局、层叠 Widget 布局,进行学习。


(一)单子 Widget 布局:Container、Padding 与 Center

单子 Widget 布局类容器比较简单,一般用来对其唯一的子 Widget 进行样式包装,比如限制大小、添加背景色样式、内间距、旋转变换等。这一类布局 Widget,包括 Container、Padding 与 Center 三种。

(1)Container

Container 是一种允许在其内部添加其他控件的控件,也是 UI 框架中的一个常见概念。

在 Flutter 中,Container 本事可以单独作为控件存在(比如设置背景色、宽度),也可以作为其他控件的父级存在:Container 可以定义布局过程中子 Widget 如何摆放,以及如何展示。与其他框架不同的是,Flutter 的 Container 仅能包含一个子 Widget。

所以,对于多个子 Widget 的布局场景,我们通常会这样处理:先用一个根 Widget 去包装这些子 Widget,然后把这个根 Widget 放到 Container 中,再由 Container 设置它的对齐 alignment、边距 padding 等基础属性和样式属性。

代码如下所示:

body: Container(
  child: Text('Container(容器)在 UI 框架中是一个很常见的概念,Flutter 也不例外。'),
  // 内边距
  padding: EdgeInsets.all(18.0),
  // 外边距
  margin: EdgeInsets.all(44.0),
  width: 180.0,
  height: 240,
  // 子 Widget 居中对齐
  alignment: Alignment.center,
  //Container 样式
  decoration: BoxDecoration(
    color: Colors.red, // 背景色
    borderRadius: BorderRadius.circular(10.0), // 圆角边框
  ),
)
Container 示例

在需要设置内容间距时,我们可以通过 EdgeInsets 的不同构造函数,分别制定四个方向的不同补白方式,如均使用同样数值留白、只设置左留白或对称方向留白等。

接下来,我们再来看看单子 Widget 布局容器中另一个常用的容器 Center。正如它的名字一样,Center 会将其子 Widget 居中排列。

需要注意的是,为了实现居中布局,Center 所占据的空间一定要比其子 Widget 要大才行,这也是显而易见的:如果 Center 和其子 Widget 一样大,自然就不需要居中,也没空间居中了。因此 Center 通常会结合 Container 一起使用。

代码如下所示:

body: Container(
  child: Center(
    child: Text('Container(容器)在 UI 框架中是一个很常见的概念,Flutter 也不例外。'),
  ),
  // 内边距
  padding: EdgeInsets.all(18.0),
  // 外边距
  margin: EdgeInsets.all(44.0),
  width: 180.0,
  height: 240,
  //Container 样式
  decoration: BoxDecoration(
    color: Colors.red, // 背景色
    borderRadius: BorderRadius.circular(10.0), // 圆角边框
  ),
)

可以看到,我们通过 Center 容器实现了 Container 容器中alignment: Alignment.center的效果。

事实上,为了达到这一效果,Container 容器与 Center 容器底层都依赖了同一容器 Align,通过它实现子 Widget 的对齐方式。


(二)多子 Widget 布局:Row、Column 与 Expanded

对于拥有多个子 Widget 的布局类容器而言,其布局行为无非就是两种规则的抽象:水平方向上应该如何布局、垂直方向上应该如何布局。

如同 Android 的 LinearLayout、前端的 Flex 布局一样,Flutter 中也有类似的概念,即将子 Widget 按行水平排列的 Row,按列垂直排列的 Column,以及负责分配这些子 Widget 在布局方向(行 / 列)中剩余空间的 Expanded。

Row 与 Column 的使用方法很简单,我们只需要将各个子 Widget 按序加入到 children 数组即可。在下面的代码中,把 4 个分别设置了不同的颜色和宽高的 Container 加到 Row 与 Column 中:

//Row 的用法示范
Row(
  children: <Widget>[
    Container(color: Colors.yellow, width: 60, height: 80,),
    Container(color: Colors.red, width: 100, height: 180,),
    Container(color: Colors.black, width: 60, height: 80,),

    Container(color: Colors.green, width: 60, height: 80,),
  ],
)

//Column 的用法示范
Column(
  children: <Widget>[
    Container(color: Colors.yellow, width: 60, height: 80,),
    Container(color: Colors.red, width: 100, height: 180,),
    Container(color: Colors.black, width: 60, height: 80,),
    Container(color: Colors.green, width: 60, height: 80,),
  ],
)
Row 示例

Column 示例

单纯使用 Row 和 Column 控件,在子 Widget 的尺寸较小时,无法将容器填满。对于这样的场景,可以通过 Expanded 控件,来制定分配规则填满容器的剩余空间。

比如,我们希望 Row 组件(或 Column 组件)中的绿色容器与黄色容器均分剩下的空间,于是就可以设置它们的弹性系数参数 flex 都为 1,这两个 Expanded 会按照其 flex 的比例(即 1:1)来分割剩余的 Row 横向(Column 纵向)空间:

Row(
  children: <Widget>[
    Expanded(flex: 1, child: Container(color: Colors.yellow, height: 60)), // 设置了 flex=1,因此宽度由 Expanded 来分配
    Container(color: Colors.red, width: 100, height: 180,),
    Container(color: Colors.black, width: 60, height: 80,),
    Expanded(flex: 1, child: Container(color: Colors.green,height: 60),) // 设置了 flex=1,因此宽度由 Expanded 来分配
  ],
)
Expanded 控件示例

(三)层叠 Widget 布局:Stack 与 Positioned

有些时候,需要让一个控件叠加在另一个控件的上面,比如在一张图片上放置一段文字,又或者是在图片的某个区域放置一个按钮。这时候,我们就需要用到层叠布局容器 Stack 了。

Stack 容器与前端中的绝对定位布局非常类似,子 Widget 之间允许叠加,还可以根据父容器上、下、左、右四个角的位置来确定自己的位置。

Stack 提供了层叠布局的容器,而 Positioned 则提供了设置子 Widget 的位置的能力。

代码如下所示:

Stack(
  children: <Widget>[
    Container(color: Colors.yellow, width: 300, height: 300),// 黄色容器
    Positioned(
      left: 18.0,
      top: 18.0,
      child: Container(color: Colors.green, width: 50, height: 50),// 叠加在黄色容器之上的绿色控件
    ),
    Positioned(
      left: 18.0,
      top:70.0,
      child: Text("Stack 提供了层叠布局的容器 "),// 叠加在黄色容器之上的文本
    )
  ],
)
Stack 与 Positioned 容器示例

可以看到,三个子 Widget 都叠加在一起了。

Stack 控件允许其子 Widget 按照创建的先后顺序进行层叠摆放,而 Positioned 控件则用来控制这些子 Widget 的摆放位置。需要注意的是,Positioned 控件只能在 Stack 中使用,在其他容器中使用会报错。


总结

Flutter 的布局容器强大而丰富,可以将小型、单用途的基本视觉元素快速封装成控件。

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

推荐阅读更多精彩内容

  • 前言 本文的目的是为了让读者掌握不同布局类Widget的布局特点,分享一些在实际使用过程遇到的一些问题,在《Flu...
    xqqlv阅读 5,241评论 0 18
  • 国庆后面两天在家学习整理了一波flutter,基本把能撸过能看到的代码都过了一遍,此文篇幅较长,建议保存(star...
    Nealyang阅读 4,337评论 1 17
  • 本文对Flutter的29种布局控件进行了总结分类,讲解一些布局上的优化策略,以及面对具体的布局时,如何去选择控件...
    Q吹个大气球Q阅读 10,515评论 15 153
  • 关于锻炼问题 跑了一天步就腿脚疼得厉害。估计自己一下自己强度太大,一下子适应不了。还是要循序渐进,慢慢来。...
    钱永香阅读 160评论 0 0
  • 你爱凤梨 爱田野 爱辣椒 爱玉米 爱笑爱眨眼睛 有一颗不羁放纵的心
    黄逸林阅读 223评论 0 2