Flutter 常见布局模型

Flutter的常见的布局模型有容器(Container)、弹性盒子布局(Flex、Row、Column、Expanded)、流式布局(Wrap、Flow)、层叠布局(Stack、Position)、滚动布局(ListView、GridView)等。

布局模型也都是 widget,很多布局widget都继承自Container,其布局UI树状图如图:

布局容器 Container

创建一个项目:

bash 复制代码
flutter create buju

lib/main.dart 代码

dart 复制代码
import 'dart:math' as math;
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(),
        body: Container(
          height: 50,
          width: 100,
          alignment: Alignment.center,
          decoration: BoxDecoration(
            color: Colors.grey,
            borderRadius: BorderRadius.all(Radius.circular(10.0)),
            border: Border.all(color: Colors.black, width: 2.0),
          ),
          margin: EdgeInsets.fromLTRB(200, 50, 0, 0),
          transform: Matrix4.rotationZ(30 * math.pi / 180),
          child: Text("Container \n布局"),
        ),
      ),
    );
  }
}

从上面代码可以看到 Container 布局有很多属性:

color 设置容器的背景色。

width 设置容器的宽。

height 设置容器的高。

alignment 设置子widget的对齐方式,如居中。

decoration 设置背景装饰,如阴影、背景图等。

foregroundDecoration 设置前景装饰,比如前景色。

margin 设置容器等外边距。

padding 设置容器等内边距。

transform 设置形变,如需旋转、拉长等。

child 设置容器包裹的子Widget。

更多请见文档

效果图如下:

弹性盒子布局

Flex

direction 设置主轴排列方向

Flex 有个重要的属性 direction,用来指定主轴的方向,子元素会按照主轴的方向排列。

dart 复制代码
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(),
        body: Flex(
          direction: Axis.horizontal,
          children: <Widget>[
            Container(
              width: 30,
              height: 50,
              color: Colors.red,
            ),
            Container(
              width: 30,
              height: 50,
              color: Colors.green,
            ),
            Container(
              width: 30,
              height: 50,
              color: Colors.grey,
            ),
          ],
        ),
      ),
    );
  }
}

这里主轴方向是horizontal水平方向,效果图如下:

改成垂直direction: Axis.vertical,后的效果:

mainAxisAlignment 设置子元素排列方式

另一个重要属性是mainAxisAlignment,用来决定主轴的排列方式。

默认是start:mainAxisAlignment: MainAxisAlignment.start

end:指最后一个元素排在主轴的最后。这里以主轴水平方向排列为例,如下图:

center:所有子元素按照主轴方向依次排列,并居中显示在父容器中。如下图:

spaceAround:每个子元素之间的间隔相等,第一个元素和最后一个元素距离父容器的边距为孩子之间间距的一半。如下图:

spaceBetween:子元素两端对齐,第一个子元素和最后一个子元素分别位于容器中主轴的起始处和终止处,同时各个子元素之间的间隔相等,如下图:

spaceEvenly: 各个子元素之间的间隔相等,同时第一个子元素和最后一个子元素距离父容器的边距也为各子元素之间的间隔。

mainAxisSize 设置主轴大小

mainAxisSize默认是max,Flex容器占据主轴全部空间。

还可以设置为min,Flex容器尽可能占据少的主轴空间。

dart 复制代码
direction: Axis.horizontal,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.min,

这里把Flex容器设置为最小的大小,导致三个子元素挤在了一起。

crossAxisAlignment 设置交叉轴对齐方式

dart 复制代码
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(),
        body: Flex(
          direction: Axis.horizontal,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            Container(
              width: 30,
              height: 100,
              color: Colors.red,
            ),
            Container(
              width: 30,
              height: 50,
              color: Colors.green,
            ),
            Container(
              width: 30,
              height: 50,
              color: Colors.grey,
            ),
          ],
        ),
      ),
    );
  }
}

crossAxisAlignment 用来设置元素在交叉轴方向上的对齐方式。

默认值是center,各个子元素在交叉轴上居中对齐。如下图(这里以主轴沿水平方向):

end:子元素布局在Flex容器交叉轴方向的尾部。

start:子元素布局在Flex容器交叉轴方向的头部。

stretch:子元素填充满交叉轴方向的空间。

verticalDirection 设置交叉轴布局的对齐方向

这个属性和上面的crossAxisAlignment属性配合使用,用于指定交叉轴布局的对齐方向

默认值是down,表示默认对齐的方向是从上到下,crossAxisAlignment默认start时,三个元素在顶部。

如果将verticalDirection改成up,表示从下到上,crossAxisAlignment还是默认start时,效果图如下:

Row 和 Column 都继承自Flex,所以Flex都属性同样适用于Row和Column,只是预先指定好了主轴的方向。

Row表示在水平方向上布局子元素,主轴方向是水平方向。

Column表示在垂直方向上布局子元素,主轴方向是垂直方向。

Flex 更多参数见:https://api.flutter-io.cn/flutter/widgets/Flex-class.html

Expanded 按比例缩放

Expanded 布局能让子元素能够按照一定的比例缩放,来填充满父容器在主轴方向上剩余的剩余空间。通常,Expanded 组件通常是作为Flex 组件的子组件使用。

dart 复制代码
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(),
        body: Flex(
          direction: Axis.horizontal,
          children: <Widget>[
            Expanded(
              flex: 1,
              child: Container(
                height: 150,
                color: Colors.red,
              ),
            ),
            Expanded(
              flex: 1,
              child: Container(
                height: 150,
                color: Colors.green,
              ),
            ),
            Expanded(
              flex: 1,
              child: Container(
                height: 150,
                color: Colors.blue,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

效果图:

如果把 Expanded 的 Flex 属性分别设置为 1,2,3,得到的效果图如下:

流式布局

在弹性布局中,如果宽度或高度超过屏幕尺寸,会抛出异常:

dart 复制代码
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(),
        body: Flex(
          direction: Axis.horizontal,
          children: <Widget>[
            Container(
              width: 150,
              height: 100,
              color: Colors.red,
            ),
            Container(
              width: 280,
              height: 50,
              color: Colors.green,
            ),
            Container(
              width: 50,
              height: 50,
              color: Colors.grey,
            ),
          ],
        ),
      ),
    );
  }
}
bash 复制代码
════════ Exception caught by rendering library ═════════════════════════════════
The following assertion was thrown during layout:
A RenderFlex overflowed by 105 pixels on the right.

这是因为弹性和子布局模型是线性的,不会自动换行。

Wrap 自动换行的容器

Wrap支持子元素自动换行,默认主轴水平方向。

dart 复制代码
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(),
        body: Wrap(
          direction: Axis.horizontal,
          spacing: 10,
          runAlignment: WrapAlignment.start,
          verticalDirection: VerticalDirection.down,
          runSpacing: 10,
          children: <Widget>[
            Container(
              width: 150,
              height: 150,
              color: Colors.red,
            ),
            Container(
              width: 200,
              height: 50,
              color: Colors.green,
            ),
            Container(
              width: 50,
              height: 50,
              color: Colors.grey,
            ),
            Container(
              width: 200,
              height: 150,
              color: Colors.black,
            ),
          ],
        ),
      ),
    );
  }
}

alignment、crossAxisAlignment、verticalDirection 与 Flex 的同名属性作用相同。

runAlignment 用来设置新一行 或新一列 的对齐方式。默认值是start,如果主轴方向是水平方向,start就是在交叉轴方向上从顶部开始布局子元素,如下图。end 是在交叉轴方向上从底部开始布局子元素。center 设置居中对齐,子元素会从交叉轴方向上父容器的中心开始布局。

Flow 布局允许用户自定义布局规则,具体参数见:https://api.flutter-io.cn/flutter/widgets/Flow-class.html

层叠布局:绝对定位

Stack 布局类似Web开发中的绝对定位。

dart 复制代码
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
          appBar: AppBar(),
          body: Center(
            child: Stack(
              children: <Widget>[
                Container(
                  width: 150,
                  height: 150,
                  color: Colors.red,
                ),
                Positioned(
                  left: 8.0,
                  right: 8.0,
                  top: 8.0,
                  child: Text('测试'),
                )
              ],
            ),
          )),
    );
  }
}

这个例子是对Text组件和Container组件的重叠,其中Positioned是定位组件,只能放在Stack中。

滚动布局

ListView 处理大量数据

当数据量特别大时,上面的布局方式实现起来会非常麻烦,而用滚动布局,直接将数据放入滚动布局模型的 children 数据中即可。

dart 复制代码
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
          appBar: AppBar(),
          body: ListView.builder(
              itemCount: 50,
              itemExtent: 50,
              itemBuilder: (BuildContext context, int index) {
                return Container(
                  height: 50,
                  child: Row(
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: [Text('索引: ${index + 1}')],
                  ),
                );
              })),
    );
  }
}

详细参数见文档:https://api.flutter-io.cn/flutter/widgets/ListView-class.html

ListView.separated 增加分割线

dart 复制代码
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
          appBar: AppBar(),
          body: ListView.separated(
              itemCount: 50,
              separatorBuilder: (BuildContext context, int ndex) {
                return Divider();
              },
              itemBuilder: (BuildContext context, int index) {
                return SizedBox(
                  height: 50,
                  child: Row(
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: [Text('索引: ${index + 1}')],
                  ),
                );
              })),
    );
  }
}

GridView 构建多行多列表格

GridView 的创建需要一个 delegate 代表,即属性 gridDelegate , Flutter SDK 默认提供了两个 SliverGridDelegate 的子类,分别是: SliverGridDelegateWithFixedCrossAxisCount 和 SliverGridDelegateWithMaxCrossAxisExtent 。

dart 复制代码
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
          appBar: AppBar(),
          body: GridView(
            gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 3, crossAxisSpacing: 8, mainAxisSpacing: 8),
            children: [
              Container(
                width: 50,
                height: 50,
                color: Colors.red,
              ),
              Container(
                width: 50,
                height: 50,
                color: Colors.red,
              ),
              Container(
                width: 50,
                height: 50,
                color: Colors.red,
              ),
              Container(
                width: 50,
                height: 50,
                color: Colors.red,
              ),
              Container(
                width: 50,
                height: 50,
                color: Colors.red,
              ),
              Container(
                width: 50,
                height: 50,
                color: Colors.red,
              ),
            ],
          )),
    );
  }
}

效果图:

动态创建 GridView

dart 复制代码
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(),
        body: GridView.builder(
            gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
                maxCrossAxisExtent: 8,
                mainAxisSpacing: 8,
                crossAxisSpacing: 8,
                childAspectRatio: 2.0),
            itemBuilder: (BuildContext context, int index) {
              return Container(
                color: Colors.red,
                width: 50,
                height: 50,
              );
            }),
      ),
    );
  }
}

maxCrossAxisExtent 设置条目的宽度。

mainAxisSpacing 设置主轴的间隔。

crossAxisSpacing 设置交叉轴间隔。

childAspectRatio 设置子元素的宽高比。

相关链接

https://docs.flutter.cn/ui/layout/

相关推荐
江上清风山间明月9 小时前
Flutter开发的应用页面非常多时如何高效管理路由
android·flutter·路由·页面管理·routes·ongenerateroute
Zsnoin能20 小时前
flutter国际化、主题配置、视频播放器UI、扫码功能、水波纹问题
flutter
早起的年轻人20 小时前
Flutter CupertinoNavigationBar iOS 风格导航栏的组件
flutter·ios
HappyAcmen20 小时前
关于Flutter前端面试题及其答案解析
前端·flutter
coooliang1 天前
Flutter 中的单例模式
javascript·flutter·单例模式
coooliang1 天前
Flutter项目中设置安卓启动页
android·flutter
JIngles1231 天前
flutter将utf-8编码的字节序列转换为中英文字符串
java·javascript·flutter
B.-1 天前
在 Flutter 中实现文件读写
开发语言·学习·flutter·android studio·xcode
freflying11192 天前
使用jenkins构建Android+Flutter项目依赖自动升级带来兼容性问题及Jenkins构建速度慢问题解决
android·flutter·jenkins
机器瓦力2 天前
Flutter应用开发:对象存储管理图片
flutter