MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Flutte Column Widget灵活布局技巧分享

2022-02-055.2k 阅读

1. Flutter Column Widget 基础介绍

在 Flutter 开发中,Column 是一个非常重要的布局组件,它可以在垂直方向上排列子 Widget。Column 继承自 MultiChildRenderObjectWidget,这意味着它可以包含多个子 Widget。

Column 的主要功能是将其内部的子 Widget 按照垂直方向依次排列。它在构建用户界面时,常用于将不同类型的组件(如文本、按钮、图片等)垂直堆叠展示。例如,在一个简单的登录页面中,我们可能会使用 Column 来垂直排列用户名输入框、密码输入框以及登录按钮。

以下是一个简单的 Column 示例代码:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Column Example'),
        ),
        body: Column(
          children: <Widget>[
            Text('First Text'),
            Text('Second Text'),
            Text('Third Text')
          ],
        ),
      ),
    );
  }
}

在上述代码中,我们创建了一个包含三个 Text 子 Widget 的 Column。这些 Text Widget 会在垂直方向上依次排列。

2. 主轴与交叉轴

在理解 Column 的布局技巧之前,需要先了解两个重要的概念:主轴(main axis)和交叉轴(cross axis)。

对于 Column 来说,主轴是垂直方向,而交叉轴是水平方向。Column 的布局行为很大程度上依赖于主轴和交叉轴的属性设置。

2.1 主轴对齐方式(MainAxisAlignment)

MainAxisAlignment 用于控制子 Widget 在主轴上的对齐方式。它有以下几种取值:

  • MainAxisAlignment.start:子 Widget 从主轴开始位置排列,这是默认值。例如在 Column 中,子 Widget 会从顶部开始垂直排列。
  • MainAxisAlignment.end:子 Widget 从主轴结束位置排列。在 Column 中,子 Widget 会从底部开始垂直排列。
  • MainAxisAlignment.center:子 Widget 在主轴中间位置排列。在 Column 中,子 Widget 会在垂直方向上居中排列。
  • MainAxisAlignment.spaceBetween:子 Widget 在主轴上均匀分布,两端对齐。在 Column 中,第一个子 Widget 在顶部,最后一个子 Widget 在底部,中间的子 Widget 间距相等。
  • MainAxisAlignment.spaceAround:子 Widget 在主轴上均匀分布,并且每个子 Widget 两侧的间距相等。在 Column 中,垂直方向上每个子 Widget 上下都有相等的间距。
  • MainAxisAlignment.spaceEvenly:子 Widget 在主轴上均匀分布,每个子 Widget 之间以及两端的间距都相等。在 Column 中,垂直方向上子 Widget 之间的间距以及顶部和底部的间距都相等。

下面是一个展示不同主轴对齐方式的代码示例:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('MainAxisAlignment Example'),
        ),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: <Widget>[
            Container(
              width: 100,
              height: 100,
              color: Colors.red,
            ),
            Container(
              width: 100,
              height: 100,
              color: Colors.green,
            ),
            Container(
              width: 100,
              height: 100,
              color: Colors.blue,
            )
          ],
        ),
      ),
    );
  }
}

在上述代码中,我们将 Column 的 mainAxisAlignment 设置为 MainAxisAlignment.spaceEvenly,三个 Container 子 Widget 在垂直方向上均匀分布,且间距相等。

2.2 交叉轴对齐方式(CrossAxisAlignment)

CrossAxisAlignment 用于控制子 Widget 在交叉轴上的对齐方式。对于 Column 而言,交叉轴是水平方向。它有以下几种取值:

  • CrossAxisAlignment.start:子 Widget 在交叉轴开始位置对齐。在 Column 中,子 Widget 会在水平方向上左对齐。
  • CrossAxisAlignment.end:子 Widget 在交叉轴结束位置对齐。在 Column 中,子 Widget 会在水平方向上右对齐。
  • CrossAxisAlignment.center:子 Widget 在交叉轴中间位置对齐。在 Column 中,子 Widget 会在水平方向上居中对齐。
  • CrossAxisAlignment.stretch:子 Widget 在交叉轴上拉伸以填充可用空间。在 Column 中,子 Widget 的宽度会拉伸至与 Column 的宽度相同。
  • CrossAxisAlignment.baseline:子 Widget 根据文本基线对齐。只有当子 Widget 是具有文本基线的 Widget(如 Text)时才有意义。

以下代码展示了 CrossAxisAlignment.stretch 的效果:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('CrossAxisAlignment Example'),
        ),
        body: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: <Widget>[
            Container(
              height: 100,
              color: Colors.red,
            ),
            Container(
              height: 100,
              color: Colors.green,
            ),
            Container(
              height: 100,
              color: Colors.blue,
            )
          ],
        ),
      ),
    );
  }
}

在这个例子中,由于设置了 CrossAxisAlignment.stretch,三个 Container 子 Widget 在水平方向上拉伸,宽度与 Column 的宽度一致。

3. Column 的大小限制与约束

Column 的大小受到其父 Widget 的约束。同时,Column 也会对其子 Widget 施加一定的约束。

3.1 无界与有界约束

当 Column 的父 Widget 没有对其在某个方向上施加明确的大小限制时,Column 在该方向上就是无界的。例如,如果 Column 的父 Widget 是一个没有设置固定高度的 Container,那么 Column 在垂直方向上就是无界的。

在无界的情况下,Column 会尝试根据其子 Widget 的大小来确定自身的大小。如果子 Widget 本身也是无界的(例如一个没有设置宽度的 Text Widget),可能会导致布局问题。因此,在使用 Column 时,需要特别注意对其子 Widget 进行合理的大小限制。

相反,如果父 Widget 对 Column 在某个方向上设置了固定的大小,那么 Column 在该方向上就是有界的。例如,将 Column 放在一个设置了固定高度的 Container 中,Column 在垂直方向上就是有界的。

3.2 MainAxisSize 属性

Column 有一个 MainAxisSize 属性,用于控制 Column 在主轴方向上占用的空间大小。它有两个取值:

  • MainAxisSize.max:Column 在主轴方向上尽可能占用最大空间。这是默认值。例如在垂直方向上,Column 会尝试填满其父 Widget 在垂直方向上提供的所有可用空间。
  • MainAxisSize.min:Column 在主轴方向上只占用包裹其子 Widget 所需的最小空间。

以下代码展示了 MainAxisSize.min 的效果:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('MainAxisSize Example'),
        ),
        body: Container(
          height: 400,
          color: Colors.grey[200],
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              Container(
                height: 100,
                color: Colors.red,
              ),
              Container(
                height: 100,
                color: Colors.green,
              )
            ],
          ),
        ),
      ),
    );
  }
}

在上述代码中,Column 被放在一个高度为 400 的 Container 中。由于设置了 mainAxisSize 为 MainAxisSize.min,Column 只占用了包裹两个子 Container 所需的最小高度,即 200。

4. 处理子 Widget 的大小

在 Column 布局中,合理处理子 Widget 的大小是实现灵活布局的关键。

4.1 Expanded 与 Flexible

  • Expanded:Expanded 是一个用于在主轴方向上分配剩余空间的 Widget。它只能作为 Column 或 Row 的子 Widget 使用。Expanded 会根据其 flex 属性的值来按比例分配剩余空间。例如,如果有两个 Expanded 子 Widget,且它们的 flex 属性值都为 1,那么它们将平分主轴方向上的剩余空间。

以下是一个 Expanded 的示例代码:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Expanded Example'),
        ),
        body: Column(
          children: <Widget>[
            Expanded(
              flex: 1,
              child: Container(
                color: Colors.red,
              ),
            ),
            Expanded(
              flex: 2,
              child: Container(
                color: Colors.green,
              ),
            )
          ],
        ),
      ),
    );
  }
}

在这个例子中,两个 Expanded 子 Widget 按 1:2 的比例分配了 Column 垂直方向上的剩余空间。

  • Flexible:Flexible 与 Expanded 类似,也是用于分配剩余空间,但 Flexible 不会强制其子 Widget 占用所有剩余空间。Flexible 有一个 fit 属性,默认值为 FlexFit.loose,这意味着子 Widget 可以根据自身的大小需求来占用空间,而不是像 Expanded 那样强制填满剩余空间。

以下代码展示了 Flexible 的使用:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flexible Example'),
        ),
        body: Column(
          children: <Widget>[
            Flexible(
              flex: 1,
              child: Container(
                width: 100,
                color: Colors.red,
              ),
            ),
            Flexible(
              flex: 2,
              child: Container(
                width: 200,
                color: Colors.green,
              ),
            )
          ],
        ),
      ),
    );
  }
}

在上述代码中,两个 Flexible 子 Widget 按照 flex 比例分配剩余空间,但由于它们设置了固定宽度,所以不会像 Expanded 那样完全填满垂直方向上的剩余空间。

4.2 SizedBox

SizedBox 是一个用于设置固定大小的 Widget。在 Column 布局中,我们可以使用 SizedBox 来控制子 Widget 的大小,或者在子 Widget 之间添加固定的间距。

例如,要在两个子 Widget 之间添加 20 像素的垂直间距,可以这样使用 SizedBox:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('SizedBox Example'),
        ),
        body: Column(
          children: <Widget>[
            Container(
              height: 100,
              color: Colors.red,
            ),
            SizedBox(height: 20),
            Container(
              height: 100,
              color: Colors.green,
            )
          ],
        ),
      ),
    );
  }
}

在这个例子中,通过 SizedBox(height: 20) 在两个 Container 子 Widget 之间添加了 20 像素的垂直间距。

5. 嵌套布局与复杂结构

在实际应用中,Column 通常会与其他布局组件嵌套使用,以构建复杂的用户界面结构。

5.1 Column 与 Row 的嵌套

Column 和 Row 可以相互嵌套,以实现水平和垂直方向上的混合布局。例如,我们可以在 Column 中放置多个 Row,每个 Row 中又可以包含不同的子 Widget。

以下是一个 Column 与 Row 嵌套的示例代码:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Column and Row Nested Example'),
        ),
        body: Column(
          children: <Widget>[
            Row(
              children: <Widget>[
                Container(
                  width: 100,
                  height: 100,
                  color: Colors.red,
                ),
                Container(
                  width: 100,
                  height: 100,
                  color: Colors.green,
                )
              ],
            ),
            Row(
              children: <Widget>[
                Container(
                  width: 100,
                  height: 100,
                  color: Colors.blue,
                ),
                Container(
                  width: 100,
                  height: 100,
                  color: Colors.yellow,
                )
              ],
            )
          ],
        ),
      ),
    );
  }
}

在上述代码中,Column 包含了两个 Row,每个 Row 又包含了两个 Container 子 Widget,实现了一种简单的网格状布局。

5.2 Column 与 Stack 的结合

Stack 是一个用于层叠布局的组件,它可以与 Column 结合使用,实现更复杂的布局效果。例如,我们可以在 Column 上叠加一些装饰性的元素,或者实现一些重叠的布局。

以下是一个 Column 与 Stack 结合的示例代码:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Column and Stack Combined Example'),
        ),
        body: Stack(
          children: <Widget>[
            Column(
              children: <Widget>[
                Container(
                  height: 200,
                  color: Colors.grey[200],
                ),
                Container(
                  height: 200,
                  color: Colors.white,
                )
              ],
            ),
            Positioned(
              top: 100,
              left: 50,
              child: Container(
                width: 100,
                height: 100,
                color: Colors.red,
              ),
            )
          ],
        ),
      ),
    );
  }
}

在这个例子中,我们在 Column 之上叠加了一个红色的 Container,通过 Positioned 来控制其位置。

6. 响应式布局与 Column

在不同屏幕尺寸的设备上,实现良好的响应式布局是非常重要的。Column 在响应式布局中也有其独特的应用技巧。

6.1 使用 LayoutBuilder

LayoutBuilder 可以帮助我们根据父 Widget 的可用空间来动态调整布局。在 Column 布局中,我们可以利用 LayoutBuilder 来根据屏幕宽度或高度调整子 Widget 的排列方式。

以下是一个使用 LayoutBuilder 在 Column 中实现响应式布局的示例代码:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Responsive Column with LayoutBuilder'),
        ),
        body: LayoutBuilder(
          builder: (BuildContext context, BoxConstraints constraints) {
            if (constraints.maxWidth > 600) {
              return Row(
                children: <Widget>[
                  Expanded(
                    child: Container(
                      height: 200,
                      color: Colors.red,
                    ),
                  ),
                  Expanded(
                    child: Container(
                      height: 200,
                      color: Colors.green,
                    ),
                  )
                ],
              );
            } else {
              return Column(
                children: <Widget>[
                  Container(
                    height: 200,
                    color: Colors.red,
                  ),
                  Container(
                    height: 200,
                    color: Colors.green,
                  )
                ],
              );
            }
          },
        ),
      ),
    );
  }
}

在上述代码中,当屏幕宽度大于 600 像素时,两个 Container 会以 Row 的方式水平排列;当屏幕宽度小于等于 600 像素时,两个 Container 会以 Column 的方式垂直排列。

6.2 MediaQuery

MediaQuery 提供了关于当前设备屏幕尺寸、方向等信息。我们可以利用 MediaQuery 在 Column 布局中根据设备的屏幕信息来调整布局。

例如,根据屏幕方向调整 Column 中子 Widget 的排列方式:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final orientation = MediaQuery.of(context).orientation;
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Responsive Column with MediaQuery'),
        ),
        body: orientation == Orientation.portrait
          ? Column(
              children: <Widget>[
                Container(
                  height: 200,
                  color: Colors.red,
                ),
                Container(
                  height: 200,
                  color: Colors.green,
                )
              ],
            )
          : Row(
              children: <Widget>[
                Expanded(
                  child: Container(
                    height: 200,
                    color: Colors.red,
                  ),
                ),
                Expanded(
                  child: Container(
                    height: 200,
                    color: Colors.green,
                  ),
                )
              ],
            ),
      ),
    );
  }
}

在这个例子中,当设备处于竖屏模式(Orientation.portrait)时,两个 Container 以 Column 方式垂直排列;当设备处于横屏模式时,两个 Container 以 Row 方式水平排列。

通过以上对 Flutter Column Widget 的各种布局技巧的介绍,开发者可以更加灵活地构建出美观、高效且适应不同设备的用户界面。在实际开发中,需要根据具体的需求和场景,合理运用这些技巧,以达到最佳的布局效果。同时,不断实践和尝试新的布局组合,将有助于提升 Flutter 应用的开发能力和用户体验。