• 布局构建教程
    • 你将会学习到
  • 第一步: 创建 app 基础代码
  • 第一步: 对布局进行图形分解
  • 第二步: 实现标题行
  • 第三步: 实现按钮行
  • 第四步: 实现文本区域
  • 第五步: 实现图片区域
  • 第六步: 最终的收尾

    布局构建教程

    你将会学习到

    • Flutter 的布局机制是如何工作的。

    • 如何竖直或者水平地对 widgets 进行布局。

    • 如何构建一个 Flutter 布局。

    这是一份如何在 Flutter 中构建布局的指南。你将为如下 app 创建布局:

    The finished appThe finished app

    这份指南之前溯源一步解释了 Flutter 中的布局方式,以及展示了如何在屏幕中放置单个 widget。经过了如何水平以及竖直放置 widgets 的讨论之后,一些最常使用的 widgets 都涉及到了。

    如果你想对布局机制有个”全局”的理解,可以先从 Flutter 中的布局 开始.

    第一步: 创建 app 基础代码

    确保你已经 安装和配置 好了你的环境,然后做如下步骤:

    • 创建一个简单的 Flutter app ——”Hello World”。

    • 按照如下方法修改 app 标题栏的标题以及 app 的标题:

    布局构建教程 - 图2{codelabs/startup_namer/step1_base → layout/base}/lib/main.dart

    @@ -10,10 +10,10 @@

    10

    10

    @override

    11

    11

    Widget build(BuildContext context) {

    12

    12

    return MaterialApp(

    13

    • title: 'WelcometoFlutter',

    13

    • title: 'Flutterlayoutdemo',

    14

    14

    home: Scaffold(

    15

    15

    appBar: AppBar(

    16

    • title: Text('WelcometoFlutter'),

    16

    • title: Text('Flutterlayoutdemo'),

    17

    17

    ),

    18

    18

    body: Center(

    19

    19

    child: Text('Hello World'),

    第一步: 对布局进行图形分解

    第一步需要将布局分解成它的各个基础元素:

    • 识别出它的行和列。

    • 这个布局是否包含网格布局?

    • 是否有重叠的元素?

    • 界面是否需要选项卡?

    • 留意需要对齐、内间距、或者边界的区域。

    首先,识别出稍大的元素。在这个例子中,四个元素排成一列:一个图像,两个行区域,和一个文本区域。

    Column elements (circled in red)Column elements (circled in red)

    接着,对每一行进行图解。第一行,也就是标题区域,有三个子元素:一个文本列,一个星形图标,和一个数字。它的第一个子元素,文本列,包含两行文本。第一列占据大量空间,因此它应当被封装在一个 Expanded widget 当中。

    Title section

    第二行,也就是按钮区域,同样有三个子元素:每个子元素是一个包含图标和文本的列。

    Button section

    一旦图解好布局,采取自下而上的方法来实现它就变得尤为轻松了。为了最大程度减少,深层嵌套的布局代码带来的视觉混乱,需要用一些变量和函数来替代某些实现。

    第二步: 实现标题行

    首先,你可以构建标题部分左侧列。添加如下代码到 MyApp 类的 build() 方法内顶部。

    lib/main.dart (titleSection)

    1. Widget titleSection = Container(
    2. padding: const EdgeInsets.all(32),
    3. child: Row(
    4. children: [
    5. Expanded(
    6. /*1*/
    7. child: Column(
    8. crossAxisAlignment: CrossAxisAlignment.start,
    9. children: [
    10. /*2*/
    11. Container(
    12. padding: const EdgeInsets.only(bottom: 8),
    13. child: Text(
    14. 'Oeschinen Lake Campground',
    15. style: TextStyle(
    16. fontWeight: FontWeight.bold,
    17. ),
    18. ),
    19. ),
    20. Text(
    21. 'Kandersteg, Switzerland',
    22. style: TextStyle(
    23. color: Colors.grey[500],
    24. ),
    25. ),
    26. ],
    27. ),
    28. ),
    29. /*3*/
    30. Icon(
    31. Icons.star,
    32. color: Colors.red[500],
    33. ),
    34. Text('41'),
    35. ],
    36. ),
    37. );
    • 将 Column 元素放到 Expanded widget 中可以拉伸该列,以利用该行中所有剩余的闲置空间。设置 crossAxisAlignment 属性值为 CrossAxisAlignment.start,这会将该列放置在行的起始位置。

    • 将第一行文本放入 Container 容器中使得你可以增加内间距。列中的第二个子元素,同样为文本,显示为灰色。

    • 标题行中的最后两项是一个红色星形图标,和文字”41”。整行都在一个 Container 容器布局中,而且每条边都有 32 像素的内间距。

    如下添加标题部分到 app body 中:

    布局构建教程 - 图6{../base → step2}/lib/main.dart

    @@ -12,11 +46,13 @@
    1246 return MaterialApp(
    1347 title: 'Flutter layout demo',
    1448 home: Scaffold(
    1549 appBar: AppBar(
    1650 title: Text('Flutter layout demo'),
    1751 ),
    18- body: Center(
    19-child: Text('Hello World'),
    52+ body: Column(
    53+children: [
    54+ titleSection,
    55+ ],
    2056 ),
    2157 ),
    2258 );

    小提示

    • 在粘贴代码到你的 app 中时,行首缩进可能会发生偏移。你可以通过使用 代码自动格式化 来修复这个问题。

    • 为了获得更便捷的开发体验,请尝试 Flutter 的 热加载 功能。

    • 如果你有任何问题,可以将你的代码与 lib/main.dart 比对.

    第三步: 实现按钮行

    按钮区域包含三列使用相同布局-一行文本上面一个图标。此行的各列被等间隙放置,文本和图标被着以初始色。

    由于构建每列的代码基本相同,因此可以创建一个名为 buildButtonColumn() 的私有辅助函数,以颜色、图标和文本为入参,返回一个以指定颜色绘制自身 widgets 的一个 column 列对象。

    lib/main.dart (_buildButtonColumn)

    1. class MyApp extends StatelessWidget {
    2. @override
    3. Widget build(BuildContext context) {
    4. // ···
    5. }
    6.  
    7. Column _buildButtonColumn(Color color, IconData icon, String label) {
    8. return Column(
    9. mainAxisSize: MainAxisSize.min,
    10. mainAxisAlignment: MainAxisAlignment.center,
    11. children: [
    12. Icon(icon, color: color),
    13. Container(
    14. margin: const EdgeInsets.only(top: 8),
    15. child: Text(
    16. label,
    17. style: TextStyle(
    18. fontSize: 12,
    19. fontWeight: FontWeight.w400,
    20. color: color,
    21. ),
    22. ),
    23. ),
    24. ],
    25. );
    26. }
    27. }

    这个函数直接将图标添加到这列里。文本在以一个仅有上间距的 Container 容器中,使得文本与图标分隔开。

    通过调用函数并传递针对某列的颜色,Icon 图标和文本,来构建包含这些列的行。然后在行的主轴方向通过使用 MainAxisAlignment.spaceEvenly ,将剩余的空间均分到每列各自的前后及中间。只需在 build() 方法中的 titleSection 声明下添加如下代码:

    lib/main.dart (buttonSection)

    1. Color color = Theme.of(context).primaryColor;
    2.  
    3. Widget buttonSection = Container(
    4. child: Row(
    5. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
    6. children: [
    7. _buildButtonColumn(color, Icons.call, 'CALL'),
    8. _buildButtonColumn(color, Icons.near_me, 'ROUTE'),
    9. _buildButtonColumn(color, Icons.share, 'SHARE'),
    10. ],
    11. ),
    12. );

    添加按钮部分到 body 属性中去:

    布局构建教程 - 图7{step2 → step3}/lib/main.dart

    @@ -46,3 +59,3 @@
    4659 return MaterialApp(
    4760 title: 'Flutter layout demo',
    4861 home: Scaffold(
    @@ -52,8 +65,9 @@
    5265 body: Column(
    5366 children: [
    5467 titleSection,
    68+ buttonSection,
    5569 ],
    5670 ),
    5771 ),
    5872 );
    5973 }

    第四步: 实现文本区域

    将文本区域定义为一个变量。将文本放置到一个 Container 容器中,然后为每条边添加内边距。只需在 buttonSection 声明下添加如下代码:

    lib/main.dart (textSection)

    1. Widget textSection = Container(
    2. padding: const EdgeInsets.all(32),
    3. child: Text(
    4. 'Lake Oeschinen lies at the foot of the Blüemlisalp in the Bernese '
    5. 'Alps. Situated 1,578 meters above sea level, it is one of the '
    6. 'larger Alpine Lakes. A gondola ride from Kandersteg, followed by a '
    7. 'half-hour walk through pastures and pine forest, leads you to the '
    8. 'lake, which warms to 20 degrees Celsius in the summer. Activities '
    9. 'enjoyed here include rowing, and riding the summer toboggan run.',
    10. softWrap: true,
    11. ),
    12. );

    通过设置 softwrap 为 true,文本将在填充满列宽后在单词边界处自动换行。

    添加文本部分到 body 属性:

    布局构建教程 - 图8{step3 → step4}/lib/main.dart

    @@ -59,3 +72,3 @@
    5972 return MaterialApp(
    6073 title: 'Flutter layout demo',
    6174 home: Scaffold(
    @@ -66,6 +79,7 @@
    6679 children: [
    6780 titleSection,
    6881 buttonSection,
    82+ textSection,
    6983 ],
    7084 ),
    7185 ),

    第五步: 实现图片区域

    四个列元素中的三个已经完成了,只剩下图片部分了。如下添加图片文件到示例工程中:

    • 在工程的顶部创建一个 images 目录。

    • 添加 lake.jpg

    注意 wget 不能保存二进制文件。原始的图片虽然可以在 Creative Commons 许可下在线获取,但是文件较大,下载缓慢。

    • 更新 pubspec.yaml 文件,添加一个 assets 标签。这使得在你的代码中可以访问到该图片。

    布局构建教程 - 图9{step4 → step5}/pubspec.yaml

    @@ -17,3 +17,5 @@

    17

    17

    flutter:

    18

    18

    uses-material-design: true

    19

    • assets:

    20

      • images/lake.jpg

    小提示

    • Note that pubspec.yaml is case sensitive. So, you should write assets: andimage address as above shown format.
    • For image address proper indentation must be there.

    现在你可以在你的代码中引用该图片了:

    布局构建教程 - 图10{step4 → step5}/lib/main.dart

    @@ -77,6 +77,12 @@
    7777 ),
    7878 body: Column(
    7979 children: [
    80+ Image.asset(
    81+ 'images/lake.jpg',
    82+ width: 600,
    83+ height: 240,
    84+ fit: BoxFit.cover,
    85+ ),
    8086 titleSection,
    8187 buttonSection,
    8288 textSection,

    BoxFit.cover 告诉系统图片应当尽可能等比缩小到刚好能够覆盖住整个渲染 box。

    第六步: 最终的收尾

    在最后的步骤中,需要在一个 ListView 中排列好所有的元素,而不是在一个 Column 中,因为当 app 运行在某个小设备上时,ListView 支持 app body 的滚动。

    布局构建教程 - 图11{step5 → step6}/lib/main.dart

    @@ -72,13 +77,13 @@
    7277 return MaterialApp(
    7378 title: 'Flutter layout demo',
    7479 home: Scaffold(
    7580 appBar: AppBar(
    7681 title: Text('Flutter layout demo'),
    7782 ),
    78- body: Column(
    83+ body: ListView(
    7984 children: [
    8085 Image.asset(
    8186 'images/lake.jpg',
    8287 width: 600,
    8388 height: 240,
    8489 fit: BoxFit.cover,

    Dart code:main.dartImage:imagesPubspec:pubspec.yaml

    大功告成!当你热加载 app 时,你应当可以看到和本页开头截图一样的 app 布局了。

    你可以参考文档 为你的 Flutter 应用加入交互体验 来给这个布局增加交互。