7 基础布局处理

前面几章介绍了基础组件及各种主题风格组件,这样再加上一些布局元素,就可以实现一些基础的页面了。本章主要讲解布局及装饰组件的基本用法。最后再配合一个综合布局示例,来完整地演示如何编写一个复杂的页面。 主要的布局及装饰组件参见表7-1,本章将按照以下分类介绍这些组件:

•基础布局处理

•宽高尺寸处理

•列表及表格布局

•其他布局处理

•布局综合示例

7.1 

基础布局组件包括容器布局,各种缩放、排列方式的组件,下面分别详述。

7.1.1 Container(容器布局)

Container(容器布局)在Flutter里大量使用,它是一个组合Widget,内部有绘制Widget、定位Widget和尺寸Widget。Container组件的常见属性请参考4.1节“容器组件”的介绍。接下来我们看一个用Container进行容器布局的综合小例子。具体实现步骤如下。 步骤1:在helloworld工程下新建一个images文件夹,并放入三张图片,如图7-1所示。 步骤2:打开工程根目录下的工程配置文件,如图7-2所示。 步骤3:修改工程配置文件,添加图片的路径配置,如图7-3所示。 步骤4:编写Container容器布局示例代码,完整的示例代码如下:

import 'package:flutter/material.dart';

void main() => runApp(
      new MaterialApp(
        title: 'Container布局容器示例',
        home: new LayoutDemo(),
      ),
    );

class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //返回一个Container对象
    Widget container = new Container(
      //添加装饰效果
      decoration: new BoxDecoration(
        color: Colors.grey,
      ),
      //子元素指定为一个垂直水平嵌套布局的组件
      child: new Column(
        children: <Widget>[
          new Row(
            children: <Widget>[
              //使用Expanded防止内容溢出
              new Expanded(
                child: new Container(
                  width: 150.0,
                  height: 150.0,
                  //添加边框样式
                  decoration: new BoxDecoration(
                    //上下左右边框设置为宽度10.0,颜色为蓝灰色
                    border: new Border.all(width: 10.0, color: Colors.blueGrey),
                    //上下左右边框弧度设置为8.0
                    borderRadius:
                        const BorderRadius.all(const Radius.circular(8.0)),
                  ),
                  //上下左右增加边距
                  margin: const EdgeInsets.all(4.0),
                  //添加图片
                  child: new Image.asset('images/1.jpeg'),
                ),
              ),
              new Expanded(
                child: new Container(
                  width: 150.0,
                  height: 150.0,
                  decoration: new BoxDecoration(
                    border: new Border.all(width: 10.0, color: Colors.blueGrey),
                    borderRadius:
                        const BorderRadius.all(const Radius.circular(8.0)),
                  ),
                  margin: const EdgeInsets.all(4.0),
                  child: new Image.asset('images/2.jpeg'),
                ),
              ),
            ],
          ),
          new Row(
            children: <Widget>[
              new Expanded(
                child: new Container(
                  width: 150.0,
                  height: 150.0,
                  decoration: new BoxDecoration(
                    border: new Border.all(width: 10.0, color: Colors.blueGrey),
                    borderRadius:
                        const BorderRadius.all(const Radius.circular(8.0)),
                  ),
                  margin: const EdgeInsets.all(4.0),
                  child: new Image.asset('images/3.jpeg'),
                ),
              ),
              new Expanded(
                child: new Container(
                  width: 150.0,
                  height: 150.0,
                  decoration: new BoxDecoration(
                    border: new Border.all(width: 10.0, color: Colors.blueGrey),
                    borderRadius:
                        const BorderRadius.all(const Radius.circular(8.0)),
                  ),
                  margin: const EdgeInsets.all (4.0),
                  child: new Image.asset ('images/ 2.jpeg'),
                ),
              ),
            ],
          ),
        ],
      ),
    );

    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Container布局容器示例'),
      ),
      body: container,
    );
  }
}

上述示例代码的视图展现大致如图7-4所示。

7.1.2 Center(居中布局)

在Center居中布局中,子元素处于水平和垂直方向的中间位置。 示例代码如下:

import 'package:flutter/material.dart';

void main() => runApp(
      new MaterialApp(
        title: 'Center居中布局示例',
        home: new LayoutDemo(),
      ),
    );

class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Center居中布局示例'),
      ),
      body: new Center(
        child: new Text(
          'Hello Flutter',
          style: TextStyle(
            fontSize: 36.0,
          ),
        ),
      ),
    );
  }
}

7.1.3 Padding(填充布局)

Padding即为填充组件,用于处理容器与其子元素之间的间距,与padding属性对应的是margin属性,margin处理容器与其他组件之间的间距。Padding组件的常见属性如下所示: 接下来我们写一个例子,容器里嵌套了一个容器,两个容器分别加了一个边框,以便测试Padding值。 示例代码如下:

import 'package:flutter/material.dart';

void main() => runApp(
      new MaterialApp(
        title: 'Padding填充布局示例',
        home: new LayoutDemo(),
      ),
    );

class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Padding填充布局示例'),
      ),
      body: new Center(

    child: new Container(
    width: 300.0,
      height: 300.0,
      padding: EdgeInsets.all(60.0),//容器上下左右填充设置为60.0
      decoration: new BoxDecoration(
        color: Colors.white,
        border: new Border.all(
          color: Colors.green,
          width: 8.0,
        ),
      ),
      child: new Container(
        width: 200.0,
        height: 200.0,
        decoration: new BoxDecoration(
          color: Colors.white,
          border: new Border.all(
            color: Colors.blue,
            width: 8.0,
          ),
        ),
        child: new FlutterLogo(),
      ),
    ),
    ),
    );
  }
}

上述示例代码的Padding值为60.0。视图展现大致如图7-6所示。 接着再将Padding值改为6.0。注意此时的值小了很多,我们会发现两个容器之间的间距小了很多,如图7-7所示。

7.1.4 Align(对齐布局)

Align组件即对齐组件,能将子组件按指定方式对齐,并根据子组件的大小调整自己的大小。Align组件的属性及说明如下所示: 这里我们写一个例子,放入多张图片,这些图片分别放置在不同的位置。具体步骤如下。 步骤1:在helloworld工程下新建一个images文件夹,并放入9张图片,如图7-8所示。 步骤2:打开工程根目录下的文件。如图7-9所示。 步骤3:修改工程配置文件,添加图片的路径配置。如图7-10所示。 接着添加一个Stack组件,暂时不用管Stack组件是用来做什么的,我们需要在这个组件里放几张图片,分别放在左上角、右上角、左下角、右下角和中间。完整的示例代码如下:

import 'package:flutter/material.dart';
class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Align对齐布局示例'),
      ),
      body: new Stack(
          children: <Widget>[
            //左上角
            new Align(
              alignment: new FractionalOffset(0.0, 0.0),
              child: new Image.asset('images/1.jpeg',width: 128.0,height: 128.0,),
            ),
            //右上角
            new Align(
              alignment: FractionalOffset(1.0,0.0),
              child: new Image.asset('images/1.jpeg',width: 128.0,height: 128.0,),
            ),
            //水平垂直方向居中
            new Align(
              alignment: FractionalOffset.center,
              child: new Image.asset('images/3.jpeg',width: 128.0,height: 128.0,),
            ),
            //左下角
            new Align(
              alignment: FractionalOffset.bottomLeft,
              child: new Image.asset('images/2.jpeg',width: 128.0,height: 128.0,),
            ),
            //右下角
            new Align(
              alignment: FractionalOffset.bottomRight,
              child: new Image.asset('images/2.jpeg',width: 128.0,height: 128.0,),
            ),
          ]
      ),
    );
  }
}
void main() {
  runApp(
    new MaterialApp(
      title: 'Align对齐布局示例',
      home: new LayoutDemo(),
    ),
  );
}

视图的展现大致如图7-11所示。

7.1.5 Row(水平布局)

水平布局是一种常用的布局方式,我们主要使用Row组件来完成子组件在水平方向的排列。Row组件常见属性如下所示: 对Row来说,水平方向是主轴,垂直方向是次轴,可以完全参照Web中的Flex布局,如图7-12所示。 示例代码如下:

import 'package:flutter/material.dart';

void main() => runApp(
  new MaterialApp(
    title: '水平布局示例',
    home: new LayoutDemo(),
  ),
);

class LayoutDemo extends StatelessWidget {

  @override
  Widget build(BuildContext context) {

    return new Scaffold(
      appBar: new AppBar(
        title: new Text('水平布局示例'),
      ),
      body: new Row(
        children: <Widget>[
          new Expanded(
            child: new Text('左侧文本', textAlign: TextAlign.center),
          ),
          new Expanded(
            child: new Text('中间文本', textAlign: TextAlign.center),
          ),
          new Expanded(
            child: new FittedBox(
              fit: BoxFit.contain,
              child: const FlutterLogo(),
            ),
          ),
        ],
      ),
    );

  }
}

上述示例代码的视图展现大致如图7-13所示。

7.1.6 Column(垂直布局)

垂直布局是一种常用的布局方式,我们主要使用Column组件来完成对子组件纵向的排列。Column组件常见属性如下所示: 对于Column来说,垂直方向就是主轴,水平方向就是次轴,可以完全参照Web中的Flex布局,如图7-14所示。 第一个垂直布局的示例代码如下:

import 'package:flutter/material.dart';

void main() => runApp(
  new MaterialApp(
    title: '水平布局示例',
    home: new LayoutDemo(),
  ),
);

class LayoutDemo extends StatelessWidget {

  @override
  Widget build(BuildContext context) {

    return new Scaffold(
      appBar: new AppBar(
        title: new Text('垂直布局示例一'),
      ),
      body: new Column(
        children: <Widget>[
          new Text('Flutter'),
          new Text('垂直布局'),
          new Expanded(
            child: new FittedBox(
              fit: BoxFit.contain,
              child: const FlutterLogo(),
            ),
          ),
        ],
      ),
    );

  }
}

上述示例代码的视图展现大致如图7-15所示。 若要增加水平方向靠左对齐,主轴方向最小化处理,其中最后一行文字字号专门放大,可以将第一个示例中Column里的内容换成如下代码:

crossAxisAlignment: CrossAxisAlignment.start,//水平方向靠左对齐
mainAxisSize: MainAxisSize.min,//主轴方向最小化处理
children: <Widget>[
  new Text('Flutter是谷歌的移动UI框架'),
  new Text('可以快速在iOS和Android上构建高质量的原生用户界面'),
  new Text('Flutter可以与现有的代码一起工作。在全世界'),
  new Text('Flutter正在被越来越多的开发者和组织使用'),
  new Text('并且Flutter是完全免费、开源的。'),
  new Text('Flutter!', style: TextStyle(fontSize: 36.0,)),//放大字号

第二个示例代码的视图展现大致如图7-16所示。文字全部向左对齐,上面几行文字都偏小,只有最后一行专门放大处理后文字显得比较大。

7.1.7 FittedBox(缩放布局)

FittedBox组件主要做两件事情,缩放(Scale)和位置调整(Position)。 FittedBox会在自己的尺寸范围内缩放并调整child位置,使child适合其尺寸。做过移动端的读者可能会联想到ImageView组件,它是将图片在其范围内按照规则进行缩放位置调整。FittedBox跟ImageView有些类似,可以猜出,它肯定有一个类似于ScaleType的属性。 布局行为分两种情况: □如果外部有约束的话,按照外部约束调整自身尺寸,然后缩放调整child,按照指定的条件进行布局。 □如果没有外部约束条件,则跟child尺寸一致,指定的缩放以及位置属性将不起作用。 有fit和alignment两个重要属性,如下所示。 fit:缩放的方式,默认的属性是BoxFit.contain,child在FittedBox范围内,尽可能大,但是不超出其尺寸。这里需要注意一点,contain是在保持着child宽高比的大前提下,尽可能填满。一般情况下,宽度或者高度达到最大值时,就会停止缩放。Fit的各个属性对应的布局示意图如图7-17所示。 BoxFit.none没有任何填充模式,如图7-18所示。 BoxFit.fill不按宽高比填充模式,内容不会超过容器范围,效果如图7-19所示。 BoxFit.contain按宽高比等比填充模式,内容不会超过容器范围,效果如图7-20所示。 BoxFit.cover按原始尺寸填充整个容器模式,内容有可能会超过容器范围,效果如图7-21所示。 BoxFit.width及BoxFit.height分别是按宽/高填充整个容器的模式,内容不会超过容器范围,效果如图7-22所示。 BoxFit.scaleDown会根据情况缩小范围,有时和BoxFit.contain一样,有时和BoxFit.none一样,内容不会超过容器范围,如图7-23所示。 alignment:设置对齐方式,默认的属性是Alignment.center,居中显示child。填充布局的示例代码如下:

import 'package:flutter/material.dart';
class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('FittedBox缩放布局示例'),
      ),
      body: new Container(
        color: Colors.grey,
        width: 250.0,
        height: 250.0,
        child: new FittedBox(
          fit: BoxFit.contain,// 改变填充属性值会得到不同的效果
          alignment: Alignment.topLeft,
          child: new Container(
            color: Colors.deepOrange,
            child: new Text("缩放布局"),
          ),
        ),
      ),
    );
  }
}
void main() {
  runApp(
    new MaterialApp(
      title: 'FittedBox缩放布局示例',
      home: new LayoutDemo(),
    ),
  );
}

7.1.8 Stack/Alignment

Stack组件的每一个子组件要么定位,要么不定位,定位的子组件是用Positioned组件包裹的。Stack组件本身包含所有不定位的子组件,子组件根据alignment属性定位(默认为左上角)。然后根据定位的子组件的top、right、bottom和left属性将它们放置在Stack组件上。Stack组件的主要属性如下所示。 采用Alignment方式布局示例代码如下:

import 'package:flutter/material.dart';

void main() {
  runApp(new MaterialApp(
    title: 'Stack布局示例Alignment',
    home: new MyApp(),
  ));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {

    var stack = new Stack(
      //子组件左上角对齐
      alignment: Alignment.topLeft,
      children: <Widget>[
        //底部添加一个头像
        new CircleAvatar(
          backgroundImage: new AssetImage('images/1.jpeg'),
          radius: 100.0,
        ),
        //上面加一个容器,容器里再放一段文本
        new Container(
          decoration: new BoxDecoration(
            color: Colors.black38,
          ),
          child: new Text(
            '我是超级飞侠',
            style: new TextStyle(
              fontSize: 22.0,
              fontWeight: FontWeight.bold,
              color: Colors.white,
            ),
          ),
        ),
      ],
    );

    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Stack层叠布局示例'),
      ),
      body: new Center(
        child: stack,
      ),
    );
  }
}

上述示例代码的视图展现大致如图7-24所示。Stack子组件是层叠的关系,所以示例里文本会在图片之上。

7.1.9 Stack/Positioned

Positioned组件是用来定位的。Stack使用Positioned布局主要是因为在Stack组件里面需要包裹一个定位组件,Positioned组件属性如下所示: Positioned定位布局的示例代码如下:

import 'package:flutter/material.dart';

void main() {
  runApp(new MaterialApp(
    title: '层叠定位布局示例',
    home: new MyApp(),
  ));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {

    return new Scaffold(
      appBar: new AppBar(
        title: new Text('层叠定位布局示例'),
      ),
      body: new Center(
        //添加层叠布局
        child: new Stack(
          children: <Widget>[
            //添加网络图片
            new 
Image.network('https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1541655494719&di=6b49d24b5172a34828b9d6506e4bf100&imgtype=0&src=http%3A%2F%2Fn.sinaimg.cn%2Fsinacn11%2F266%2Fw640h426%2F20180813%2Fce56-hhqtawx8254771.jpg'),
            //设置定位布局
            new Positioned(
                bottom: 50.0,
                right: 50.0,
                child: new Text(
                  'hi flutter',
                  style: new TextStyle(
                    fontSize: 36.0,
                    fontWeight: FontWeight.bold,
                    fontFamily: 'serif',
                    color: Colors.white,
                  ),
                )
            ),
          ],
        ),
      ),
    );
  }
}

上述示例代码的视图展现大致如图7-25所示。

7.1.10 IndexedStack

IndexedStack继承自Stack,它的作用是显示第index个child,其他child都是不可见的,所以IndexedStack的尺寸永远是和最大的子节点尺寸一致。由于IndexedStack是继承自Stack的,所以它只比Stack多了一个index属性,即对应child的索引。 这里我们改造Stack布局示例,把Stack组件替换为IndexedStack组件,同时添加index属性值,当index设置为1时,运行结果只显示“我是超级飞侠”几个字及它的容器。因为CircleAvatar组件索引为0,所以不显示。 示例的完整代码如下:

import 'package:flutter/material.dart';

void main() {
  runApp(new MaterialApp(
    title: 'IndexedStack布局示例',
    home: new MyApp(),
  ));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {

    var stack = new IndexedStack(
      index: 1,//设置索引为1就只显示文本内容了
      alignment: const FractionalOffset(0.2, 0.2),
      children: <Widget>[
        new CircleAvatar(
          backgroundImage: new AssetImage('images/1.jpeg'),
          radius: 100.0,
        ),
        new Container(
          decoration: new BoxDecoration(
            color: Colors.black38,
          ),
          child: new Text(
            '我是超级飞侠',
            style: new TextStyle(
              fontSize: 22.0,
              fontWeight: FontWeight.bold,
              color: Colors.white,
            ),
          ),
        ),
      ],
    );

    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Stack层叠布局示例'),
      ),
      body: new Center(
        child: stack,
      ),
    );
  }
}

上述示例代码的视图展现大致如图7-26所示。

7.1.11 OverflowBox(溢出父容器显示)

通常,子组件是无法打破由父组件传递下来的约束条件,但是我们总会有一些情况下子组件的尺寸大于父组件。那怎么解决呢?在子组件及父组件之间再加入一个OverflowBox组件,那么子组件就不会以父组件为约束条件,而是以OverflowBox组件的属性为约束条件。OverflowBox组件由其minWidth、minHeight 、maxWidth、maxHeight四个属性给子组件划定了一个矩形的范围。子组件不能超出OverflowBox组件范围,但是可以溢出父组件范围显示。 OverflowBox组件的主要属性如下所示: 接下来,我们写一个例子,添加两个组件:绿色父组件和蓝灰色子组件,子组件会溢出父组件一定范围。如果没有OverflowBox组件,子组件蓝灰色的盒子是不会超出父组件绿色的盒子的。完整的示例代码如下:

 import 'package:flutter/material.dart';

class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        appBar: new AppBar(
          title: new Text('OverflowBox溢出父容器显示示例'),
        ),
        body: Container(
          color: Colors.green,
          width: 200.0,
          height: 200.0,
          padding: const EdgeInsets.all(50.0),
          child: OverflowBox(
            alignment: Alignment.topLeft,
            maxWidth: 400.0,
            maxHeight: 400.0,
            child: Container(
              color: Colors.blueGrey,
              width: 300.0,
              height: 300.0,
            ),
          ),
        ));
  }
}
void main() {
  runApp(
    new MaterialApp(
      title: 'OverflowBox溢出父容器显示示例',
      home: new LayoutDemo(),
    ),
  );
}

上述示例代码的视图展现大致如图7-27所示。结果显示出,子组件蓝灰色的盒子超出父组件绿色盒子了。