08、Flutter页面布局

页面布局

本章主要讲解布局及装饰组件的基本用法,主要的布局及装饰组件参见下表:

基础布局处理
宽高尺寸处理
列表及表格布局
其他布局处理
布局综合示例

布局及装饰组件的说明如下:

组件名称 中文名称 简单说明
Align 对齐布局 指定child的对齐方式
AspectRatio 调整宽高比 根据设置的宽高比调整child
Baseline 基准线布局 所有child底部所在的同一条水平线
Center 居中布局 child处于水平和垂直方向的中间位置
Colum 垂直布局 对child在垂直方向进行排列
ConstrainedBox 限定宽高 限定child的最大值
Container 容器布局 容器布局是一个组合的Widgt,包含定位和尺寸
FittedBox 缩放布局 缩放及位置调整
FractionallySizeBox 百分比布局 根据现有空间按照百分比调整child的尺寸
GridView 网格布局 对多行多列同时进行操作
IndexedStack 栈索引布局 IndexedStack继承自Stack,显示第index个child,其他child都是不可见的
LimitedBox 限定宽高布局 对最大宽高进行限制
ListView 列表布局 用列表方式进行布局,比如多项数据的场景
Offstage 开关布局 控制是否显示组件
OverflowBox 溢出父容器显示 允许child超出父容器的范围显示
Padding 填充布局 处理容器与其child之间的间距
Row 水平布局 对child在水平方向进行排列
SizeBox 设置具体尺寸 用一个特定大小的盒子来限定child宽度和高度
Stack/Alignment Alignment栈布局 根据Alignment组件的属性将child定位在Stack组件上
Stack/Positioned Positioned栈布局 根据Positioned组件的属性将child定位在Stack组件上
Table 表格布局 使用表格的行和列进行布局
Transform 矩阵转换 做矩阵变换,对child做平移、旋转、缩放等操作
Wrap 按宽高自动换行 按宽度或高度,让child自动换行布局

基础布局处理

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

Container(容器布局)

Container(容器布局)在Flutter中大量使用,它是一个组合的Widget,内部有绘制Widget、定位Widget和尺寸Widget。

在工程目录的images下放置三张图片,然后在pubspec.yaml中添加图片的位置:

完整的示例代码如下所示:

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

Center(居中布局)

在Center居中布局中,子元素处于水平和垂直方向的中间位置。

示例代码如下:

import 'package:flutter/material.dart';
void main() => runApp(
  MaterialApp(
    title: 'Center居中布局',
    home: LayoutDemo(),
  )
);
class LayoutDemo extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Center居中布局'),),
      body: Center(
        child: Text(
          'Hello Flutter',
          style: TextStyle(fontSize: 36.0),
        ),
      ),
    );
  }
}

Padding(填充布局)

Padding即为填充组件,用于处理容器与其子元素之间的间距,与padding属性对应的是magin属性,magin处理容器与其他组件之间的间距。

属性名 类型 说明
padding EdgeInsetsGeometry 填充值可以使用EdgeInsets方法: EdgeInsets.all(6.0)将容器上下左右填充设置为6.0 EdgeInsets.only()方法来单独设置某一边的间距

接下来我们编写例子,容器里嵌套一个容器,两个容器分别添加边框以此来测试Padding值。

import 'package:flutter/material.dart';
void main() => runApp(
  MaterialApp(
    title: 'Padding填充布局',
    home: LayoutDemo(),
  )
);
class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Padding填充布局'),),
      body: Center(
        child: Container(
           300.0,
          height: 300.0,
          padding: EdgeInsets.all(60.0),// 容器上下左右填充设置为60.0
          decoration: BoxDecoration(
            color: Colors.white,
            border: Border.all(
              color: Colors.green,
               8.0
            )
          ),
          child: FlutterLogo(),
        ),
      ),
    );
  }
}

Align(对齐布局)

Align组件即对齐组件,能将子组件按指定方式对齐,并根据子组件的大小调整自己的大小。

属性名 说明
bottomCenter (0.5, 1.0) 底部中心
bottomLeft (0.0, 1.0) 左下角
bottomRight (1.0, 1.0) 右下角
center (0.5, 0.5) 水平垂直居中
centerLeft (0.0, 0.5) 左侧缘中心
centerRight (1.0, 0.5) 右侧缘中心
topCenter (0.5, 0.0) 顶部中心
topLeft (0.0, 0.0) 左上角
topRight (1.0, 0.0) 右上角

我们编写一个例子,将多张图片分别放置在不同的位置。完整的代码如下:

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

Row(水平布局)

水平布局是一种常用的布局方式,我们主要使用Row组件来完成子组件在水平方向的排列。Row组件常见属性如下所示:

属性名 类型 说明
mainAxisAlignment MainAxisAlignment 主轴的排列方式
crossAxisAlignment CrossAxisAlignment 次轴的排列方式
mainAxisSize MainAxisSize 主轴应该占据多少空间。取值max为最大,min为最小
children List 组件子元素,它的本质是一个List列表

对Row来说,水平方向是主轴,垂直方向是次轴,可以完全参照Web中的Flex布局。

import 'package:flutter/material.dart';
void main() => runApp(
  MaterialApp(
    title: 'Row布局',
    home: LayoutDemo(),
  )
);
class LayoutDemo extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Row布局'),),
      body: Row(
        children: [
          Expanded(
            child: Text('左侧文本', textAlign: TextAlign.center,),
          ),
          Expanded(
            child: Text('中间文本', textAlign: TextAlign.center,),
          ),
          Expanded(
            child: FittedBox(
              fit: BoxFit.contain,
              child: FlutterLogo(),
            ),
          )
        ],
      ),
    );
  }
}

Column(垂直布局)

垂直布局是一种常用的布局方式,主要使用Column组件来完成对子组件纵向的排列。常见属性如下所示:

属性名 类型 说明
mainAxisAlignment MainAxisAlignment 主轴的排列方式
crossAxisAlignment CrossAxisAlignment 次轴的排列方式
mainAxisSize MainAxisSize 主轴应该占据多少空间。取值max为最大,min为最小
children List 组件子元素,它的本质是一个List列表

对于Column来说,垂直方向是主轴,水平方向是次轴,可以完全参照Web中的Flex布局。

import 'package:flutter/material.dart';
void main() => runApp(
  MaterialApp(
    title: 'Colum布局',
    home: LayoutDemo(),
  )
);
class LayoutDemo extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Colum布局'),),
      body: Column(
        children: [
          Text('Flutter'),
          Text('垂直布局'),
          Expanded(
            child: FittedBox(
              fit: BoxFit.contain,
              child: FlutterLogo(),
            ),
          )
        ],
      ),
    );
  }
}

若要增加水平方向左对齐,主轴方向最小化处理即可:

class LayoutDemo extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Colum布局'),),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        mainAxisSize: MainAxisSize.min,
        children: [
          Text('Flutter'),
          Text('Flutter'),
          Text('Flutter'),
          Text('Flutter'),
          Text('Flutter'),
          Text('Flutter'),
        ],
      ),
    );
  }
}

FittedBox(缩放布局)

FittedBox组件主要做两件事情,缩放(Scale)和位置调整(Position)。

FittedBox会在自己的尺寸范围内缩放并调整child位置,使chi;d适合其尺寸。

布局行为分两种情况:

如果外部有约束,按照外部约束调整自身尺寸,然后缩放调整child,按照指定的条件进行布局。
如果没有外部约束,则跟child尺寸一致,指定的缩放以及位置属性将不起作用。有fit和alignment两个重要属性,如下所示:
fit:缩放的方式,默认是BoxFit.contain,在FittedBox范围内尽可能大,但是不超出尺寸。
alignment:设置对齐方式,默认属性是Alignment.center,居中显示child。

填充布局的示例代码如下:

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

Stack/Alignment

Stack组件的每个子组件要么定位,要么不定位,定位的子组件使用Positioned组件包裹的。Satck组件本身包含所有不定位的子组件,子组件根据alignment属性定位(默认左上角)。

然后根据定位的子组件的top、right、bottom和left属性将它们放置在Stack组件上。

属性名 类型 默认值 说明
alignment AlignmentGeometry Alignment.topLeft 定位位置有以下几种: bootomCenter 底部中间位置 bottomLeft 底部左侧位置 bottomRight 底部右侧位置 center 正中间位置 centerLeft 中间居左位置 centerRight 中间居右位置 topCenter 上部居中位置 topLeft 上部居左位置 topRight 上部居右位置

采用Alignment方式布局示例如下:

import 'package:flutter/material.dart';
void main() => runApp(
  MaterialApp(
    title: 'Stack/Alignment',
    home: MyApp(),
  )
);
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var stack = new Stack(
      // 子组件左上角对齐
      alignment: Alignment.topLeft,
      // 底部添加一个头像
      children: [
        CircleAvatar(
          backgroundImage: AssetImage('images/1.jpeg'),
          radius: 100.0,
        ),
        Container(
          decoration: BoxDecoration(
            color: Colors.black38,
          ),
          child: Text(
            '我是超级飞侠',
            style: TextStyle(
              fontSize: 22.0,
              fontWeight: FontWeight.bold,
              color: Colors.white
            ),
          ),
        )
      ],
    );
    return Scaffold(
      appBar: AppBar(title: Text('Stack/Alignment'),),
      body: Center(
        child: stack,
      )
    );
  }
}

Stack/Positioned

Positioned组件是用来定位的。Stack使用Positioned布局主要是因为在Stack组件里面需要包裹一个定位组件。

属性名 类型 说明
top double 子元素相对顶部边界距离
bottom double 子元素相对底部边界距离
left double 子元素相对左侧边界距离
right double 子元素相对右侧边界距离

Positioned定位布局示例代码如下:

import 'package:flutter/material.dart';
void main() => runApp(
  MaterialApp(
    title: 'Stack/Positioned',
    home: MyApp(),
  )
);
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Stack/Positioned'),),
      body: Center(
        // 添加层叠布局
        child: Stack(
          children: [
            // 添加图片
            Container(
              child: Image.asset('images/1.jpeg'),
            ),
            // 设置定位布局
            Positioned(
              bottom: 50.0,
              right: 50.0,
              child: Text(
                'hi flutter',
                style: TextStyle(
                  fontSize: 36.0,
                  fontWeight: FontWeight.bold,
                  fontFamily: 'serif',
                  color: Colors.white
                ),
              ),
            )
          ],
        ),
      ),
    );
  }
}

IndexedStack

IndexedStack继承自Stack,它的作用是显示第index个child,其他child都是不可见的。IndexedStack的尺寸永远是和最大子节点尺寸一致。

import 'package:flutter/material.dart';
void main() => runApp(
    MaterialApp(
      title: 'Stack/Alignment',
      home: MyApp(),
    )
);
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var stack = new IndexedStack(
      index: 1, // 设置索引为1则只显示文本内容
      alignment: FractionalOffset(0.2, 0.2),
      children: [
        CircleAvatar(
          backgroundImage: AssetImage('images/1.jpeg'),
          radius: 100.0,
        ),
        Container(
          decoration: BoxDecoration(
            color: Colors.black38,
          ),
          child: Text(
            '我是超级飞侠',
            style: TextStyle(
                fontSize: 22.0,
                fontWeight: FontWeight.bold,
                color: Colors.white
            ),
          ),
        )
      ],
    );
    return Scaffold(
        appBar: AppBar(title: Text('Stack/Alignment'),),
        body: Center(
          child: stack,
        )
    );
  }
}

OverflowBox溢出父容器显示

OverflowBox组件允许子元素child超出父容器的范围显示。当OverflowBox的最大尺寸大于child的时候,child可以完整显示,当其小于child的时候,则以最大尺寸为基准。

属性名 类型 说明
alignment AlignmentGeometry 对齐方式
minWidth double 允许child的最小宽度。如果child宽度小于这个值,则按照最小宽度显示。
maxWidth double 允许child的最大宽度。如果child宽度大于这个值,则按照最大宽度显示。
minHeight double 允许child的最小高度。如果child宽度小于这个值,则按照最小高度显示。
maxHeight double 允许child的最大高度。如果child宽度大于这个值,则按照最大高度显示。

接下来我们编写两个容器,上层的容器会溢出下层的容器一定范围。

import 'package:flutter/material.dart';
void main() => runApp(
  MaterialApp(
    title: 'OverflowBox溢出父容器显示',
    home: LayoutDemo(),
  )
);
class LayoutDemo extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Scaffold(
     appBar: AppBar(title: Text('OverflowBox溢出父容器显示'),),
      body: Container(
        color: Colors.green,
         200.0,
        height: 200.0,
        padding: EdgeInsets.all(5.0),
        child: OverflowBox(
          alignment: Alignment.topLeft,
          maxWidth: 300.0,
          maxHeight: 500.0,
          child: Container(
            color: Colors.blueGrey,
             400.0,
            height: 400.0,
          ),
        ),
      ),
    );
  }
}

宽高尺寸处理

对组件具体尺寸的设置有多种方式,这里将详述这类组件。

  • SizeBox(设置具体尺寸)

SizeBox组件是一个特定大小的盒子,这个组件强制它的child有特定的宽度和高度。如果宽度或高度为null,将调整自身大小以匹配该维度中的child大小。

属性名 类型 说明
width AlignmentGeometry 宽度值,如果具体设置了则强制child宽度为此值,如果没设置则根据child宽度调整自身。
height double 高度值,如果具体设置了则强制child高度为此值,如果没设置则根据child高度调整自身。

接下来使用SizeBox容器,在其中添加一个Card组件并限定宽200、高300的范围内:

import 'package:flutter/material.dart';
void main() => runApp(
  MaterialApp(
    title: 'SizeBox设置具体尺寸',
    home: LayoutDemo(),
  )
);
class LayoutDemo extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('SizeBox设置具体尺寸'),),
      body: SizedBox(
        // 固定宽为200.0,高为300.0
         200.0,
        height: 300.0,
        child: Card(
          child: Text('SizeBox', style: TextStyle(fontSize: 36.0),),
        ),
      )
    );
  }
}

ConstrainedBox(限定最大最小宽高布局)

ConstrainedBox的作用是限定子元素child的最大宽度、最大高度、最小宽度和最小高度。主要属性如下:

属性名 类型 说明
constraints BoxConstraints 添加child上的额外限制条件,其类型为BoxConstraints(限制各种最大最小宽高)
child Widget 子元素,任意Widget

在示例中宽高为300的Container上添加一个约束最大最小宽高的ConstrainedBox。

import 'package:flutter/material.dart';
void main() => runApp(
  MaterialApp(
    title: 'ConstrainedBox限定宽高',
    home: LayoutDemo(),
  )
);
class LayoutDemo extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('ConstrainedBox限定宽高'),),
      body: ConstrainedBox(
        constraints: BoxConstraints(
          minWidth: 150.0,
          minHeight: 150.0,
          maxWidth: 220.0,
          maxHeight: 220.0,
        ),
        child: Container(
           300.0,
          height: 300.0,
          color: Colors.green,
        ),
      ),
    );
  }
}

LimitedBox(限定最大宽高布局)

LimitedBox组件是限制类型的组件,可对最大宽高进行限制。和ConstrainedBox类似,只不过LimitedBox没有最小宽高限制。

属性名 类型 说明
maxWidth double 限定的最大宽度,默认值是double.infinity
maxHeight double 限定的最大高度,默认值是double.infinity

我们添加两个容器,第一个容器宽度为100,高度填满父容器。第二个容器宽度为250,高度填满父容器。

由于父容器使用LimitedBox限定最大宽度为150,所以实际上它的宽度为150。

import 'package:flutter/material.dart';
void main() => runApp(
  MaterialApp(
    title: 'LimitedBox限定宽高布局',
    home: LayoutDemo(),
  )
);
class LayoutDemo extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('LimitedBox限定宽高布局'),),
      body: Row(
        children: [
          Container(
            color: Colors.grey,
             100.0,
          ),
          LimitedBox(
            maxWidth: 150.0, // 设置最大宽度,限定child在此范围内
            child: Container(
              color: Colors.lightGreen,
               250.0,
            ),
          )
        ],
      ),
    );
  }
}

AspectRatio(调整宽高比)

AspectRatio的作用是根据设置调整子元素child的宽高比,Flutter提供此组件,就免去了自定义所带来的麻烦。

AspectRatio适用于需要固定宽高比的请求,它的布局行为分为两种情况:

AspectRatio首先会在布局限制条件允许范围内尽可能扩展,Widget的高度是由宽高和比例决定的。
如果在满足所有限制条件后无法找到可行的尺寸,AspectRatio最终会优先适应布局限制条件而忽略比例。

AspectRatio的主要属性如下所示:

属性名 类型 说明
aspectRatio double 宽高比,最终可能不会根据这个值去布局,具体看综合因素
child Widget 子元素,任意Widget

定义一个高度为200的区域,内部AspectRatio比例设置为1.5,最终AspectRatio是宽300、高200的区域:

import 'package:flutter/material.dart';
void main() => runApp(
  MaterialApp(
    title: 'AspectRatio调整宽高比例',
    home: LayoutDemo(),
  )
);
class LayoutDemo extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('AspectRatio调整宽高比例'),),
      body: Container(
         200.0,
        child: AspectRatio(
          aspectRatio: 1.5, // 比例可以调整
          child: Container(
            color: Colors.green,
          ),
        ),
      ),
    );
  }
}

FractionallySizeBox(百分比布局)

FractionallySizeBox组件会根据现有空间来调整child的尺寸,所以就算为child设置具体的尺寸也不会生效。

FractionallySizeBox组件主要属性如下:

属性名 类型 说明
alignment AlignmentGeometry 对齐方式,不能为null
widthFactor double 宽度因子,宽度乘以该值就是最后的宽度
heightFactor double 高度因子,用作计算最后实际高度的

示例中有两个容器,底部容器宽高各位200.如果容器宽度因子为0.5,则它实际宽度为100;如果高度因子为1.5,则它实际高度为300。

import 'package:flutter/material.dart';
void main() => runApp(
  MaterialApp(
    title: 'FractionallySizeBox百分比布局',
    home: LayoutDemo(),
  )
);
class LayoutDemo extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('FractionallySizeBox百分比布局'),),
      body: Container(
        color: Colors.blueGrey,
         200.0,
        height: 200.0,
        child: FractionallySizedBox(
          alignment: Alignment.topLeft, // 元素左上角对齐
          widthFactor: 0.5, // 宽度因子
          heightFactor: 1.5, // 高度因子
          child: Container(
            color: Colors.green,
          ),
        ),
      )
    );
  }
}

列表及表格布局

ListView

ListView布局是一种常用的布局方式,ListView结合ListTitle可以布局出一些复杂的列表界面。

import 'package:flutter/material.dart';
void main() => runApp(
  MaterialApp(
    title: 'ListView布局',
    home: MyApp(),
  )
);
class MyApp extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    List<Widget> list = [
      ListTile(
        title: Text('广州市黄埔大道建中路店', style: TextStyle(fontWeight: FontWeight.w400, fontSize: 18.0),),
        leading: Icon(Icons.fastfood, color: Colors.orange,),
      ),
      ListTile(
        title: Text('广州市白云机场路白云机场店', style: TextStyle(fontWeight: FontWeight.w400, fontSize: 18.0),),
        subtitle: Text('广州市白云区机场路T3航站楼'),
        leading: Icon(Icons.airplay, color: Colors.blue,),
      ),
      ListTile(
        title: Text('广州市中山大学附属医院', style: TextStyle(fontWeight: FontWeight.w400, fontSize: 18.0),),
        subtitle: Text('广州市中山大道45号'),
        leading: Icon(Icons.local_hospital, color: Colors.green,),
      ),
      ListTile(
        title: Text('广州市天河区太平洋数码城', style: TextStyle(fontWeight: FontWeight.w400, fontSize: 18.0),),
        subtitle: Text('广州市天河区岗顶太平洋数码城'),
        leading: Icon(Icons.computer, color: Colors.deepPurple,),
      ),
    ];
    return Scaffold(
      appBar: AppBar(title: Text('ListView布局'),),
      body: Center(
        child: ListView(
          children: list,
        ),
      ),
    );
  }
}

ListView还可以实现长文本的滚动效果,一般用于页面内容较多的场景。

import 'package:flutter/material.dart';
void main() => runApp(
  MaterialApp(
    title: '滚动布局示例',
    home: MyApp(),
  )
);
class MyApp extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('滚动布局示例'),),
      body: ListView(
        children: [
          Center(
            child: Text('\n广州天河区公园', style: TextStyle(fontSize: 30.0),),
          ),
          Center(
            child: Text('天河公园', style: TextStyle(fontSize: 16.0),),
          ),
          Center(
            child: Text(
              '''天河公园,是区属综合性公园,位于广州天河区员村,西靠天府路,南连黄埔大道,北接中山大道来往交通十分便利。
              公园总面积为70.7公顷,水体面积占10公顷。天河公园以自然生态景观为主要特色,公园规划为五个功能区:
              百花园景区、文体娱乐区、老人活动区、森林休憩区、后勤管理区。''',
              style: TextStyle(fontSize: 14.0),
            ),
          ),
        ],
      ),
    );
  }
}

GridView

GridView布局为网格布局,通常用来布局多行多列的情况。接下来编写一个布局的九宫格示例。

import 'package:flutter/material.dart';
void main() => runApp(
  MaterialApp(
    title: 'GridView九宫格',
    home: MyApp(),
  )
);
class MyApp extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    // 使用generate构建图片列表
    List<Container> _buildGridTitleList(int count){
      return List<Container>.generate(count, (index) => Container(
        child: Image.asset('images/1.jpeg'),
      ));
    }
    // 渲染GridView
    Widget buildGrid(){
      return GridView.extent(
        maxCrossAxisExtent: 150.0, // 次轴宽度
        padding: EdgeInsets.all(4.0), // 内边距
        mainAxisSpacing: 4.0, // 主轴间隙
        crossAxisSpacing: 4.0, // 次轴间隙
        children: _buildGridTitleList(9),
      );
    }
    return Scaffold(
      appBar: AppBar(title: Text('GridView九宫格'),),
      body: Center(
        child: buildGrid(),
      ),
    );
  }
}

Table

表格布局中,每一行的高度由其内容决定,每一列的宽度由columWidths属性单独控制。

属性名 类型 说明
columnWidths Map<int, TableColumnWidth> 设置每一列的宽度
defaultColumnWidth TableColumnWidth 默认的每一列的宽度值,默认情况下均分
textDirection TextDirection 文字方向
border TableBorder 表格边框
defaultVerticalAlignment TableCellVerticalAlignment 默认垂直方向对齐方式: top:放置在顶部 middle:垂直居中 bottom:放置在底部 baseline:文本baseline对齐 fill:充满整个cell
textBaseline TextBaseline defaultVerticalAlignment为baseline的时候,会用到这个属性

接下来编写一个员工基本信息统计的表格,完整代码如下:

import 'package:flutter/material.dart';
void main() => runApp(
  MaterialApp(
    title: 'Table表格布局',
    home: MyApp(),
  )
);
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Table表格布局'),),
      body: Center(
        child: Table(
          // 设置表格有多少列,并指定列宽Map<int, TableColumnWidth>
          columnWidths: <int, TableColumnWidth>{
            0: FixedColumnWidth(100.0),
            1: FixedColumnWidth(40.0),
            2: FixedColumnWidth(80.0),
            3: FixedColumnWidth(80.0),
          },
          // 设置表格边框样式
          border: TableBorder.all(color: Colors.black38,  2.0, style: BorderStyle.solid),
          children: [
            // 添加第一行数据
            TableRow(
              children: [
                Text('姓名'),
                Text('性别'),
                Text('年龄'),
                Text('身高'),
              ]
            ),
            // 添加第二行数据
            TableRow(
                children: [
                  Text('张三'),
                  Text('男'),
                  Text('26'),
                  Text('172'),
                ]
            ),
            // 添加第三行数据
            TableRow(
                children: [
                  Text('李四'),
                  Text('男'),
                  Text('28'),
                  Text('178'),
                ]
            ),
          ],
        ),
      ),
    );
  }
}

其他布局处理

Transform(矩阵转换)

TransForm的主要作用就是做矩阵变换。Container中矩阵变换就使用了Tranform。它可以对child做平移、旋转及缩放等操作。

属性名 类型 说明
tranform Matrix4 一个4x4的矩阵。一些复合操作仅靠三维是不够的,必须采用额外的一维来补充
origin Offset 旋转点,相对于左上角定点的偏移。默认旋转点是左上角顶点
alignment AlignmentGeometry 对齐方式
transformHitTests bool 点击区域是否也做相应的改变

接下来编写一个例子,使容器旋转一定的角度,并且用的是Matrix4.rotationZ(0.3)方法进行旋转。

import 'package:flutter/material.dart';
void main() => runApp(
  MaterialApp(
    title: 'Transform矩阵转换',
    home: LayoutDemo(),
  )
);
class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Transform矩阵转换'),),
      body: Center(
        child: Container(
          color: Colors.grey,
          child: Transform(
            alignment: Alignment.topRight,
            transform: Matrix4.rotationZ(0.3),
            child: Container(
              padding: EdgeInsets.all(8.0),
              color: Color(0xFFE8581C),
              child: Text('Transform矩阵转换'),
            ),
          ),
        ),
      ),
    );
  }
}

Baseline(基准线布局)

Baseline基准线是指将所有元素底部放在同一条水平线上。根据child的baseline来调整child的位置。

Baseline组件的主要属性如下所示:

属性名 类型 说明
baseline double baseline数值,必须要有,从顶部算
baselineType TextBaseline baseline类型,也是必须要有的,目前有两种类型。 alphabetic:对齐字符底部的水平线 ideographic:对齐表意字符的水平线

示例中三个元素在同一个水平线上。baseline也设定相同的值,示例代码如下:

import 'package:flutter/material.dart';
void main() => runApp(
  MaterialApp(
    title: 'Baseline基准线布局',
    home: LayoutDemo(),
  )
);
class LayoutDemo extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Baseline基准线布局'),),
      body: Row(
        // 水平等间距排列子组件
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          // 设置基准线
          Baseline(
            baseline: 80.0,
            // 对齐字符底部水平线
            baselineType: TextBaseline.alphabetic,
            child: Text('AaBbCc', style: TextStyle(fontSize: 18.0, textBaseline: TextBaseline.alphabetic),),
          ),
          Baseline(
            baseline: 80.0,
            baselineType: TextBaseline.alphabetic,
            child: Container(
               40.0,
              height: 40.0,
              color: Colors.green,
            ),
          ),
          Baseline(
            baseline: 80.0,
            baselineType: TextBaseline.alphabetic,
            child: Text(
              'DeEeFf',
              style: TextStyle(
                fontSize: 26.0,
                textBaseline: TextBaseline.alphabetic
              ),
            ),
          )
        ],
      ),
    );
  }
}

Offstage(控制是否显示组件)

Offstage的作用很简单,通过一个参数来控制child是否显示,也算是比较常用的组件。主要属性如下:

属性名 类型 默认值 说明
offstage bool true 默认为true表示不显示,当为false的时候会显示该组件

编写一个控制文本显示隐藏的小示例,需要添加一个控制状态的变量offstage。示例中点击右下角按钮可以显示或隐藏文本内容。

import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    final appTitle = 'Offstage控制是否显示组件';
    return MaterialApp(
      title: appTitle,
      home: MyHomePage(title:appTitle),
    );
  }
}
class MyHomePage extends StatefulWidget {
  final String title;
  const MyHomePage({Key key, this.title}) : super(key: key);
  @override
  _MyHomePage createState() => new _MyHomePage();
}
class _MyHomePage extends State<MyHomePage> {
  // 控制状态是否显示文本组件
  bool offstage = true;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title),),
      body: Center(
        child: Offstage(
          offstage: offstage,
          child: Text('我出来啦!', style: TextStyle(fontSize: 36.0),),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: (){
          // 设置是否显示文本组件
          setState(() {
            offstage = !offstage;
          });
        },
        tooltip: "显示隐藏",
        child: Icon(Icons.flip),
      ),
    );
  }
}

Wrap(按宽高自动换行布局)

Wrap使用了Flex中的概念,某种意义上说跟Row、Column更加相似。单行的Wrap跟Row表现几乎一致,单列的Wrap则跟Column表现几乎一致。

但Row和Column都是单行单列的,Wrap却突破了这个限制,主轴空间不足时,则向次轴上去扩展显示。

对于一些需要按宽度和高度让child自动换行布局的场景可以使用Wrap。

属性名 类型 默认值 说明
direction Axis Axis.horizontal 主轴(mainAxis)的方向,默认为水平
alignment WrapAlignment 主轴方向上的对齐方式,默认为start
spacing double 0.0 主轴方向上的间距
runAlignment WrapAlignment WrapAlignment.start run的对齐方式,run可以理解为新的行或列,如果是水平布局的话,run可以理解为新的一行
runSpacing double 0.0 run的间距
crossAxisAlignment WrapCrossAlignment WrapCrossAlignment.start 主轴(crossAxis)方向上的对齐方式
textDirection TextDirection 文本方向
verticalDirection VerticalDirection 定义children摆放顺序,默认是down

接下来在示例中的容器放一些头像。使用Wrap组件包装后,头像可以按从左到右和从上到下的顺序排列头像元素。

import 'package:flutter/material.dart';
void main() => runApp(
  MaterialApp(
    title: 'Wrap按宽高自动换行布局',
    home: MyApp(),
  )
);
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Wrap按宽高自动换行布局'),),
      body: Wrap(
        spacing: 8.0,// Chip之间的间隙大小
        runSpacing: 4.0, // 行之间的间隙大小
        children: [
          Chip(
            // 添加圆形头像
            avatar: CircleAvatar(
              backgroundColor: Colors.lightGreen.shade800,
              child: Text('西门', style: TextStyle(fontSize: 10.0),),
            ),
            label: Text('西门吹雪'),
          ),
          Chip(
            avatar: CircleAvatar(
              backgroundColor: Colors.lightGreen.shade800,
              child: Text('司空', style: TextStyle(fontSize: 10.0),),
            ),
            label: Text('司空摘星'),
          ),
          Chip(
            avatar: CircleAvatar(
              backgroundColor: Colors.lightGreen.shade800,
              child: Text('婉清', style: TextStyle(fontSize: 10.0),),
            ),
            label: Text('木婉清'),
          ),
          Chip(
            avatar: CircleAvatar(
              backgroundColor: Colors.lightGreen.shade800,
              child: Text('一郎', style: TextStyle(fontSize: 10.0),),
            ),
            label: Text('萧十一郎'),
          ),
        ],
      ),
    );
  }
}

布局综合示例

这里我们通过一个风景区的介绍来讲解布局的综合运用,效果图如下所示:

布局分析

整体布局使用垂直布局组件ListView进行滚动布局。一共有四大块:武当山图片、风景区地址、按钮组和景区介绍文本块。

注意:垂直方向使用ListView的原因是文本可能会很长而超出屏幕,如果使用Column则部分文本可能会看不到。

1) 风景区布局

风景区地址从横向上看需要使用一个水平排列的组件Row,水平方向总共有三个Child,分别为左侧文本区域、右侧图标及数字区域。

其中左侧文本区域继续细分需要使用垂直布局组件Column,上下各放置一个文本组件即可。右侧图标及数字是两个组件,所以横向上看是三个组件。

有个问题是左侧和右侧之间的空隙如何处理?这里需要用到Expandaed组件来包裹风景区地址以达到填充空隙的目的。

其中,Expanded组件可以使Row、Column、Flex等子组件在其主轴方向上展开并填充可用空间。必须用在Row、Column、Flex内。

2) 按钮组布局

在横向上用Row组件排列三个按钮布局组件。在纵向上用Column做三个相同的按钮,上面问按钮图标,下面为按钮文本。

完整的代码示例如下:

import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    // 风景区地址部分
    Widget addressContainer = Container(
      padding: EdgeInsets.all(32.0),
      child: Row(
        children: [
          // 使用Expanded防止内容溢出
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start, // 顶部对齐
              children: [
                Container(
                  padding: EdgeInsets.only(bottom: 8.0),
                  child: Text('风景区地址', style: TextStyle(fontWeight: FontWeight.bold),),
                ),
                Text('湖北省十堰市丹江口市', style: TextStyle(color: Colors.grey[500]),)
              ],
            ),
          ),
          Icon(
            Icons.star,
            color: Colors.red[500],
          ),
          Text('66')
        ],
      ),
    );
    // 构建按钮组中单个按钮,参数为图标及文本
    Column buildButtonColumn(IconData icon, String lable){
      return Column(
        mainAxisSize: MainAxisSize.min, // 垂直方向大小最小化
        mainAxisAlignment: MainAxisAlignment.center, // 垂直方向居中对齐
        children: [
          Icon(icon, color: Colors.lightGreen[600],), // 上面为图标部分
          Container(
            margin: EdgeInsets.only(top: 8.0),
            child: Text(lable, style: TextStyle(fontSize: 12.0, fontWeight: FontWeight.w400, color: Colors.lightGreen[600]),),
          ),
        ],
      );
    }
    // 按钮组部分
    Widget buttonsContainer = Container(
      // 容器横向布局
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly, // 水平方向均匀排列每个元素
        children: [
          buildButtonColumn(Icons.call, '电话'),
          buildButtonColumn(Icons.near_me, '导航'),
          buildButtonColumn(Icons.share, '分析'),
        ],
      ),
    );
    // 风景区介绍文本部分
    Widget textContainer = Container(
      padding: EdgeInsets.all(32.0),
      child: Text(
        '''
          武当山,中国道教圣地,又名大和山、谢罗山、参上山、仙室山 古有“大岳”“玄岳”“大岳”之称。
          位于湖北西北部十堪市丹江口市境内 东接闻名古城襄阳市,西靠车城十堪市,南望原始森林神
          农架,北临高峡平湖丹江口水库
          明代,武当山被皇帝封为“大岳飞“治世玄岳”,被尊为“皇室家庙 武当山以“四大名山皆拱棒,
          五方仙岳共朝宗”的“五岳之冠 地位问名于世。
          1994年12月武当山古建筑群入选《世界遗产名录》, 2006年被整体列为全国重点文物保护单位 
          2007年武当山和长城、丽江、周庄等景区一起入选“欧洲人最喜爱的中国十大景区
          2010-2013 年,武当山分别被评为国家 4A 级旅游区、国家森林公园、中国十大避暑名山、海峡两
          岸交流基地 入选最美“国家地质公园
          截至 2013 武当山有古建筑 53 处,建筑面积 2.7 万平方米 建筑遗址 占地面积 20 多万
          平方米,全山保存各类文物 5035 件。
          武当山是道教名山和武当武术的发源地 被称为“亘古无双胜境 天下第一仙山 武当武术,是中
          华武术的重要流派 元未明初,道士张三丰集其大成 开创武当派。
        ''',
        softWrap: true,
      ),
    );
    return MaterialApp(
      title: '武当山风景区',
      // 自定义主题,主题颜色为绿色风格
      theme: ThemeData(
        brightness: Brightness.light, // 应用程序整体主题的亮度
        primaryColor: Colors.lightGreen[600], // App主要部分的背景色
        accentColor: Colors.orange[600], // 前景色(文本,按钮等)
      ),
      home: Scaffold(
        appBar: AppBar(title: Text('武当山风景区', style: TextStyle(color: Colors.white),), centerTitle: true,),
        body: ListView(
          children: [
            // 风景图片填充显示
            Image.asset('images/wudangshan.jpg',  600.0, height: 240.0, fit: BoxFit.cover,),
            addressContainer,
            buttonsContainer,
            textContainer,
          ],
        ),
      ),
    );
  }
}
原文地址:https://www.cnblogs.com/pengjingya/p/14929298.html