专栏首页iOS小生活经典布局:如何定义子控件在父容器中的排版位置?

经典布局:如何定义子控件在父容器中的排版位置?

在之前的文章中,我们一起学习了构建视图的基本元素,文本Text、图片Image和按钮,用于展示一组连续视图元素的ListView,以及处理多重嵌套的可滚动视图的CustomScrollView,等等。

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

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

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

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

今天,我着重介绍几类在开发Flutter应用时,最常用也最具有代表性的布局Widget,包括单子Widget布局、多子Widget布局、层叠Widget布局。掌握这些典型的Widget,你就基本掌握了构建一个界面精美的APP所需要的全部布局方式了。接下来,我们就先从单子Widget聊起吧。

单子Widget布局:Container、Padding和Center

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

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

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

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

接下来,我通过一个示例,与你演示如何定义一个Container。

在这个示例中,我将一段较长的文字,包装在一个红色背景、圆角边框、固定宽高的Container中,并分别设置了Container的外边距(距离其父Widget的边距)和内边距(距离其子Widget的边距):

Container(
      child: Text("Container(容器)在UI框架中是一个很常见的概念,Flutter也不例外!~~"),
      width: 180,
      height: 240,
      margin: EdgeInsets.all(44),//外边距
      padding: EdgeInsets.all(18),//内边距
      alignment: Alignment.center,//子Widget居中对齐
      decoration: BoxDecoration(
        color: Colors.red,//背景色
        borderRadius: BorderRadius.circular(10),//圆角边框
      ),
    );

如果我们只需要将子Widget设定间距,则可以使用另一个单子容器控件Padding进行内容填充:

Padding(
      child: Text("Container(容器)在UI框架中是一个很常见的概念,Flutter也不例外!~~"),
      padding: EdgeInsets.all(44),
    );

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

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

比如,我可以把一个Text包在Center里,实现居中展示:

Center(
      child: Text("Center"),
    );

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

现在,我们结合Container,一起看看Center的具体使用方法吧。

Container(
      child: Center(
        child: Text("Container(容器)在UI框架中是一个很常见的概念,Flutter也不例外!~~~"),
      ),
      height: 240,
      width: 180,
      padding: EdgeInsets.all(18),
      margin: EdgeInsets.all(44),
      decoration: BoxDecoration(
        color: Colors.red,
        borderRadius: BorderRadius.circular(10),
      ),
    );

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

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

接下来,我们再来看看多子Widget布局的三种方式,即Row、Column与Expanded。

多子Widget布局:Row、Column和Expanded

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

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

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

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

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.red, height: 80, width: 60)),
        Container(color: Colors.yellow, height: 80, width: 60),
        Container(color: Colors.blue, height: 180, width: 100),
        Expanded(flex: 1, child: Container(color: Colors.green, height: 80, width: 60)),
      ],
    );

于Row和Column而言,Flutter提供了依据坐标轴的布局对齐行为,即根据布局方向划分出主轴和交叉轴:主轴,表示容器依次摆放子Widget的方向;交叉轴,则是与主轴垂直的另一个方向。

比如,Row的主轴是横向,交叉轴是纵向;Column的主轴是纵向,交叉轴是横向。

我们可以根据主轴和交叉轴,设置子Widget在这两个方向上的对齐规则mainAxisAlignment与crossAxisAlignment。比如,对于Row而言,主轴方向start表示靠左对齐、center表示横向居中对齐,end表示靠右对齐,spaceEvenly表示按固定间距对齐;而交叉轴方向start则表示靠上对齐,center表示纵向居中对齐,end表示靠下对齐

下图展示了在Row中设置不同方向的对齐规则后的呈现效果:

Row的主轴对齐方式

Row的纵轴对齐方式:

Column的对齐方式也是类似的,这里不做过多展开。

需要注意的是,对于主轴而言,Flutter默认是让父容器决定其长度,即尽可能大

在上例中,Row的宽度为屏幕宽度,Column的高度为屏幕高度。主轴长度大于所有子Widget的总长度,意味着容器在主轴方向的空间比子Widget要大,这也是我们能通过主轴对齐方式设置子Widget布局效果的原因

如果想让容器与子Widget在主轴上完全匹配,我们可以通过设置Row的mainAxisSize参数为MainAxisSize.min,由所有子Widget来决定主轴方向的容器长度,即主轴方向的长度尽可能小。

Row(
      mainAxisSize: MainAxisSize.min,//让容器宽度与所有子Widget的宽度总和一致
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,//由于容器与子Widget一样宽,因此这行设置排列间距的代码并未起作用
      children: <Widget>[
        Container(color: Colors.red, height: 80, width: 60),
        Container(color: Colors.yellow, height: 80, width: 60),
        Container(color: Colors.blue, height: 180, width: 100),
        Container(color: Colors.green, height: 80, width: 60)
      ],
    );

可以看到,我们设置了主轴大小为MainAxisSize.min之后,Row的宽度变得和其子Widget一样大,因此再设置主轴的对齐方式也就不起作用了。

层叠Widget布局:Stack与Positioned

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

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

Stack提供了层叠布局的容器,而Positioned则提供了设置子Widget位置的能力。接下来,我们通过一个例子来看一下Stack和Position的用法吧。

在这个例子中,我先在Stack中放置了一块300x300的黄色画布,随后在(18,18)处放置了一个50x50的绿色控件,然后在(18,70)处放置了一个文本控件。

Stack(
      children: <Widget>[
        Container(
          height: 300,
          width: 300,
          color: Colors.yellow,
        ),
        Positioned(
          left: 18,
          top: 18,
          child: Container(height: 50, width: 50, color: Colors.green),
        ),
        Positioned(
          left: 18,
          top: 70,
          child: Text("文本控件"),
        )
      ],
    );

运行一下,可以看到,这三个子Widget都按照我们预定的规则叠加在一起了。

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

总结

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

单子容器包括Container、Padding和Center。其中,Container内部提供了间距、背景样式等基础属性,为子Widget的摆放方式,及展现样式都提供了定制能力。而Padding与Center提供的功能,则正如其名一样简洁,就是对齐与居中。

多子Widget布局有Row和Column,使用Expanded控件使用容器内部的剩余空间。

层叠布局Stack,以及与之搭配使用的,定位子Widget位置的Positioned容器,通过它们,实现多控件堆放的布局效果。

以上

本文分享自微信公众号 - iOS小生活(iOSHappyLife)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-08-09

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • web前端面试中10个关于css高频面试题,你都会吗?

    BFC(Block Formatting Context),块级格式化上下文,是一个独立的渲染区域,让处于 BFC 内部的元素与外部的元素相互隔离,使内外元素的...

    coder_koala
  • 设计模式(十):建造者模式

    在软件系统开发过程中,我们常常会需要一个创建过程复杂的对象,这个复杂的对象由多个子部件按照指定的步骤组合而成。由于需求的变化,这个复杂的对象各个部分经常面临着变...

    xujjj
  • promise版本的golang

    之前我们分析了swift版本的PromiseKit,最近也在琢磨能否移植到咱大golang上来,找了好久也没有相对应的粒子。于是经过一次失败的尝试之后这周末花了...

    大话swift
  • golang实践之Excel操作

    工作中我们经常会用到Excel表格,甚至很多软件都有导出Excel的功能,今天就根据自己的需要使用go来帮忙生成Excel。其实这个需要在几个月以前就有了,只是...

    大话swift
  • 苹果外包爆料:你手机里的Siri,听到了嘿嘿嘿的声音

    据《卫报》得到的苹果外包公司员工爆料,这些包含Siri被误唤醒时的内容,都将被一一记录分析。

    量子位
  • Swift教程(一)--基础内容

    Swift是一个全新的用户iOS,MacOS,watchOS和tvOS的应用编程语言。如果你有C和Objective-C开发经验的话,Swift的许多部门会让你...

    roc
  • 防患高通效仿华为,苹果10亿美元收购英特尔手机基带业务!打造5G备胎,加强自主可控

    有人说这是苹果自研基带之心不死,也能当作苹果为5G种下备胎计划,或者会是苹果加强关键供应链自主可控的重要一步。

    量子位
  • 【Unity游戏开发】不接SDK也能在游戏内拉起加QQ群操作?

      一般在游戏进行对外测试的时候都会有一个玩家QQ群,方便玩家反馈问题、交流游戏心得等。那么为了增加玩家加QQ群的欲望,可能会在游戏里面设计一个小功能,点击一下...

    马三小伙儿
  • 《传奇世界》基于cocos2d开源手游

    基于cocos2d游戏引擎开发lua脚本编写的经典游戏《传奇世界》手游版,支持android、ios、windows等不同平台,职业设定(战士、法师、道士), ...

    程序源代码
  • [数据分析]小白也可以用数据分析选购心仪的手机

    9月13日发布的iPhone Xs算是手机界的大新闻了,新款iPhone的价格也再度刷新了手机定价的记录。看完发布会,相信很多人的心情是这样的

    叫我龙总

扫码关注云+社区

领取腾讯云代金券