Flutter学习笔记(二)布局

上一篇是对于Flutter当中Widget概念的简要介绍,从而对于无状态及有状态的小部件有一定的认识,并对于它们在实际中的使用进行了了解,包括状态的设置、用户交互的响应等。那么,我们知道要是构建一个应用,在让功能实现前先需要将小部件们合理的组织在一起。在Flutter中,可见的元素以小部件的形式存在,而小部件的位置布局关系等也是以小部件形式存在的。

要点提示

  • 小部件是用来构建UI的类
  • 小部件同时用在布局和UI元素
  • 组合简单的小部件以构建复杂的小部件

Flutter布局的核心机制是小部件。在Flutter中几乎所有事物都是个小部件,甚至布局模式也是。你在应用中看到的图片、图标、文字都是小部件。但你看不到的一些也是小部件,例如用以排布、约束、对齐小部件的行、列和网格。
你通过将小部件组合成更复杂的小部件来创建一个布局。例如,下面第一个的截图展示了下方带标签的三个图标:

小部件组小部件组layout

第二个截图是可视化布局,展示了一行3列每列包含一个图标和一个标签。

注意
笔记中的截图都是设置debugPaintSizeEnabled为true以看到可视化布局的。
更多信息参见可视化调试.

下面是这个UI的小部件树的示意图:
小部件树
其中大部分你可能已经猜到,但是可能会对containers(粉色节点)好奇。Container是允许你自定义其子小部件的小部件类。当你希望添加padding、margin、边框、背景色等时,可以使用Container设置其属性。
这个例子中,每个Text小部件都放在一个Container中来增添margin。整个Row小部件也放在Container中在一行的周围增加padding。
例子中UI其余部分用属性控制。通过color属性设置Icon的颜色,通过Text.style设置字体(如颜色、粗细等)。行和列有属性来设置它的子元素在横向纵向上如何对齐以及占用多少空间。

布局一个小部件

如何在Flutter中布局单独一个小部件?这小节展示你如何创建展示一个简单的小部件,以及一个简单的Hello World应用的完整代码。
Flutter中,只需几步在屏幕上放置文字、图标和图像:

1.选择一个布局小部件

根据你想要如何对齐与约束可见小部件来在各种布局小部件中选择一个。这个例子中使用Center来将其内容在横纵向上置于居中。

2.创建一个可见小部件

例如,创建一个Text小部件:

Text('Hello World'),

创建一个Image小部件:

Image.asset(
  'images/lake.jpg',
  fit: BoxFit.cover,
),

创建一个Icon小部件:

Icon(
  Icons.star,
  color: Colors.red[500],
),

3.添加可见小部件到布局小部件

任一个布局小部件都有下面两者之一:

  • child属性:即只接收一个子小部件——例如CenterContainer
  • children属性:即可接收一个小部件列表——例如RowColumnListViewStack

Text小部件添加到Center小部件:

Center(
  child: Text('Hello World'),
),

4.将布局小部件添加到页

Flutter应用是自身的一个小部件,而大部分小部件都有build()方法,在app的build()方法中实例化并返回一个小部件来显示它。

Material apps

对于Material应用你可以使用Scaffold小部件,它提供了默认横幅、背景颜色且有添加drawer、snack bar和bottom sheet的接口(这里没找到很合适的词翻译)。然后你可以直接将Center小部件添加到主页的body属性。

lib/main.dart (MyApp)
class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialAp appBar: AppBar( title: Text('Flutter layout demo'), ), body: Center( child: Text('Hello World'), ), ), ); } }

注意
Material库遵循Material Design原则实现小部件。设计UI时你可以仅仅使用标准小部件库中的小部件,也可以使用Material库中的。你可以混合两个库中的小部件、定制已有小部件或者可以创建你自己的定制化小部件集。

非Material apps

对于非Material应用,可以将Center小部件添加到app的build()方法:

lib/main.dart (MyApp)
class MyApp extends StatelessWidget { Widget build(BuildContext context) { return Container(ld: Center( child: Text( 'Hello World', textDirection: TextDirection.ltr, style: TextStyle( fontSize: 32, color: Colors.black87, ), ), ), ); } }

默认的非Material应用不带有AappBar、标题栏和背景色,如果你希望在非Material应用中使用这些特性你可以自己去创建。这个应用修改背景色到白色、文字到深灰来模仿Material应用。
当你运行应用,你会看到如图的Hello World:
Hello World App
应用的源码:

纵向横向上布局多个小部件

一种常用的布局模式是将小部件纵向或横向排列,你可以用Row小部件横向组织小部件而用Column小部件纵向组织小部件。

要点提示

  • Row和Column是最常用的两个布局模式
  • Row和Column都是接收一个子小部件列表
  • 子小部件可以是Row、Column或其他复杂小部件
  • 你可以指定Row或Column如何对齐其子小部件
  • 你可以拉伸或压缩指定的子小部件
  • 你可以指定子小部件如何使用Row和Column的可用空间

通过向RowColumn小部件添加子小部件列表在Flutter中创建行列。反过来单个小部件可以独自成一行或一列。下面的例子展示了如何在行列中组织行列。
这个布局组织成Row,这行包含两个子小部件:左侧的一列和右侧的图片。
行布局
左侧列的小部件树包含行和列:
列布局
你可以通过行列交织实现部分Pavlova的代码。

注意
RowColumn是水平与竖直布局的最基本小部件——这些低级小部件允许最大程度的定制化。Flutter也提供专门的更高级的小部件以满足你的需要。例如,你可能会用ListTile来替代Row,它提供有首尾图标和最多3行文字的属性。你可能会用ListView来替代Column,它类似Column但当其内容对于空间过大时自动滚动。更多信息参考常用布局小部件

小部件的对齐

你可以使用mainAxisAlignmentcrossAxisAlignment属性来设置行列如何对齐小部件。对行来说,主轴为水平向,交叉轴为竖直向,对列来说正好相反。

主轴与交叉轴-Row主轴与交叉轴-Column

注意
当你往工程中添加图片时,需要更新pubspec文件以能访问到它——本例中用Image.asset来展示图片。更多信息参考样例pubspec.yaml文件或是Flutter中添加资产和图片。你如果使用Image.network获取网络上图片则不需做此操作。

在接下来的这个例子中,三个图片都是100像素宽,绘制空间(render box)(在本例中为整个屏幕)比300像素要宽,所以将主轴对齐设置为spaceEvenly来将水平的可用空间在图片前后和之间留空进行划分。

Row(
  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  children: [
    Image.asset(‘images/pic1.jpg’),
    Image.asset(‘images/pic2.jpg’),
    Image.asset(‘images/pic3.jpg’),
  ],
);

图片水平排列
源码:row_column

列和行是类似的,下面例子中展示了一列高度都是100像素的3个图像。绘制空间(render box)(在本例中为整个屏幕)比300像素要高,所以将主轴对齐设置为spaceEvenly来将竖直的可用空间在图片前后和之间留空进行划分。

Column(
  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  children: [
    Image.asset(‘images/pic1.jpg’),
    Image.asset(‘images/pic2.jpg’),
    Image.asset(‘images/pic3.jpg’),
  ],
);

图片竖直排列
源码:row_column

调整小部件尺寸

当布局对于设备来说太大了,黑黄条纹相间条带会出现在受影响的边缘。这是一个行太宽的例子
行太宽
小部件可以通过Expanded小部件来调整尺寸以适应行或列。将每个图片用Expanded小部件包围来修复前面的一行图片对于渲染空间太宽的问题:

Row(
  crossAxisAlignment: CrossAxisAlignment.center,
  children: [
    Expanded(
      child: Image.asset(‘images/pic1.jpg’),
    ),
    Expanded(
      child: Image.asset(‘images/pic2.jpg’),
    ),
    Expanded(
      child: Image.asset(‘images/pic3.jpg’),
    ),
  ],
);

图片尺寸过大调整
源码:sizing

或许你或许你许你或许你你许你或或你或你你许你或或或许你希望令一个小部件拥有其兄弟的2倍大小,则使用Expanded小部件的flex属性来指定其缩放因子。该参数默认为1,下面代码设置中间图片的缩放因子为2:

Row(
  crossAxisAlignment: CrossAxisAlignment.center,
  children: [
    Expanded(
      child: Image.asset(‘images/pic1.jpg’),
    ),
    Expanded(
      flex: 2,
      child: Image.asset(‘images/pic2.jpg’),
    ),
    Expanded(
      child: Image.asset(‘images/pic3.jpg’),
    ),
  ],
);

图片大小缩放
源码:szing

压紧(Packing)小部件

默认行或列小部件会沿着主轴方向占满空间,但如果你希望将子小部件们压紧在一起,可以设置mainAxisSizeMainAxisSize.min。下面的例子即是使用该属性将星形图标压紧在一起。

Row(
  mainAxisSize: MainAxisSize.min,
  children: [
    Icon(Icons.star, color: Colors.green[500]),
    Icon(Icons.star, color: Colors.green[500]),
    Icon(Icons.star, color: Colors.green[500]),
    Icon(Icons.star, color: Colors.black),
    Icon(Icons.star, color: Colors.black),
  ],
)

压紧小部件
源码:pavlova

行列交织

布局框架允许你将行和列进行任意层次的嵌套。让我们看一下下面布局中框出来部分的代码:
行列交织
框出部分由两行构成,评分行包含五个星星和评论数量,图标行包含三列图标和文字。
评分行的小部件树:
评分行小部件树
ratings变量创建了一个包含五颗星星的更小行和文字的一行:

var stars = Row(
  mainAxisSize: MainAxisSize.min,
  children: [
    Icon(Icons.star, color: Colors.green[500]),
    Icon(Icons.star, color: Colors.green[500]),
    Icon(Icons.star, color: Colors.green[500]),
    Icon(Icons.star, color: Colors.black),
    Icon(Icons.star, color: Colors.black),
  ],
);

final {% label warning  %} = Container(
  padding: EdgeInsets.all(20),
  child: Row(
    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
    children: [
      stars,
      Text(
        '170 Reviews',
        style: TextStyle(
          color: Colors.black,
          fontWeight: FontWeight.w800,
          fontFamily: 'Roboto',
          letterSpacing: 0.5,
          fontSize: 20,
        ),
      ),
    ],
  ),
);

小贴士
为了减少严重交织的布局代码的视觉困扰,将UI的小片实现在变量或函数中。

在评分行下方的图标行包含3列,每列均包含一个图标和两行文字,如下面小部件树中所见:
图标行小部件树
iconList变量定义了图标行:

final descTextStyle = TextStyle(
  color: Colors.black,
  fontWeight: FontWeight.w800,
  fontFamily: 'Roboto',
  letterSpacing: 0.5,
  fontSize: 18,
  height: 2,
);

// DefaultTextStyle.merge() 允许你创建一个默认的文本样式被其子孙继承
final iconList = DefaultTextStyle.merge(
  style: descTextStyle,
  child: Container(
    padding: EdgeInsets.all(20),
    child: Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        Column(
          children: [
            Icon(Icons.kitchen, color: Colors.green[500]),
            Text('PREP:'),
            Text('25 min'),
          ],
        ),
        Column(
          children: [
            Icon(Icons.timer, color: Colors.green[500]),
            Text('COOK:'),
            Text('1 hr'),
          ],
        ),
        Column(
          children: [
            Icon(Icons.restaurant, color: Colors.green[500]),
            Text('FEEDS:'),
            Text('4-6'),
          ],
        ),
      ],
    ),
  ),
);

leftColumn变量包含评分及图标行,同时包含描述Pavlova的标题和文字:

final leftColumn = Container(
  padding: EdgeInsets.fromLTRB(20, 30, 20, 20),
  child: Column(
    children: [
      titleText,
      subTitle,
      ratings,
      iconList,
    ],
  ),
);

左侧的一列放在Container中以约束宽度,最终UI通过Card中整个一行(包含左侧的列和图片)构成。
Pavlova的图片来自Pixabay,你可以使用Image.network()将网络图片嵌入。但是,在本例中图片存在项目的图片目录中,加到了pubspec文件中,并用Images.asset()获取。更多信息参见添加资产和图片

body: Center(
  child: Container(
    margin: EdgeInsets.fromLTRB(0, 40, 0, 30),
    height: 600,
    child: Card(
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Container(
            width: 440,
            child: leftColumn,
          ),
          mainImage,
        ],
      ),
    ),
  ),
),

小贴士
这个Pavlova例子在宽的设备上水平运行最好,比如平板。如果你在iOS模拟器上执行,可在Hardware > Device菜单选择不同的设备,如我们推荐的iPad Pro。你可以通过Hardware > Rotate改变它的方向到横屏。你也可以通过Window > Scale改变模拟器窗口的大小(不改变逻辑分辨率)。

应用源码:pavlova

常用布局小部件

Flutter有丰富布局小部件库。

未完待续。。。。。