Flutter 布局之Row、Cloumn、Stack、Expanded及主轴和交叉轴

979 阅读7分钟

和谐学习!不急不躁!!我是你们的老朋友小青龙~

前言

Flutter 布局的三种方法:RowCloumnStack,分别对应着 横向布局纵向布局叠加布局

下面我们将按照图文结合的方式一一介绍。

本文目录

  1. 主轴(MainAxisAlignment)

  2. Row -- 横向布局

  3. Cloumn -- 纵向布局

  4. Stack -- 叠加布局

  5. 交叉轴(crossAxisAlignment)

  6. Expanded组件

  7. 调整位置之Positioned和magrin

  8. width、height和AspectRatio

  9. 补充

1、主轴(MainAxisAlignment)

在介绍Row、Cloumn之前,我们先了解一个概念--- 主轴

啥是主轴呢?你可以理解成,三位坐标轴上,三个带方向箭头的轴(x轴、y轴、z轴)。【如图👇】

image.png

如果不是很好理解,后面我会结合例子给大家做进一步的解释。让我们继续往下看~

2、Row -- 横向布局

2.1、不设置主轴方向,默认贴近左边(MainAxisAlignment.start

先上效果图 image.png

核心代码

class WidgetRow extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Container(
          child: const Icon(Icons.build,size: 80,),
          color: Colors.yellow,
        ),
        Container(
          child: const Icon(Icons.ac_unit,size: 80,),
          color: Colors.blue,
        ),
        Container(
          child: const Icon(Icons.access_alarm,size: 80,),
          color: Colors.deepOrangeAccent,
        ),
      ],
    );
  }
}

2.2、设置主轴方向,贴近右边 (MainAxisAlignment.end

image.png

2.3、设置主轴方向,居中 (MainAxisAlignment.center

image.png

2.4、设置主轴方向,间距等分(MainAxisAlignment.spaceBetween

即,将几个子部件放到屏幕上以后,剩下的空间平均分配到子部件们之间。

如图所示:第一个子部件和最后一个部件与两端间距为0,因为两周没有其它子部件。

image.png

2.5、设置主轴方向,环绕等分(MainAxisAlignment.spaceAround

spaceAround,顾名思义就是环绕四周。
就是将几个子部件放到屏幕上以后,剩下的空间平均分配到每个子部件四周。

image.png

为了更清晰的表达,我这边又画了一个图:

image.png

2.6、设置主轴方向,剩余空间等分(MainAxisAlignment.spaceEvenly

spaceEvenly,即将几个子部件放到屏幕上以后,剩下的空间平均等分。
参与剩余空间等分的成员包括:所有子部件之间的间距、第一个子部件和最后一个部件与两端间距。

image.png

为了更清晰的表达,我这边又画了一个图:

image.png

3、Cloumn -- 纵向布局

3.1、不设置主轴方向,默认贴近上边(MainAxisAlignment.start

核心代码

class WidgetColumn extends StatelessWidget {
  const WidgetColumn({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Container(
          child: const Icon(Icons.build,size: 80,),
          color: Colors.yellow,
        ),
        Container(
          child: const Icon(Icons.ac_unit,size: 80,),
          color: Colors.blue,
        ),
        Container(
          child: const Icon(Icons.access_alarm,size: 80,),
          color: Colors.deepOrangeAccent,
        ),
      ],
    );
  }
}

image.png

3.2、设置主轴方向,贴近右边 (MainAxisAlignment.end

image.png

3.3、设置主轴方向,居中 (MainAxisAlignment.center

image.png

3.4、设置主轴方向,间距等分(MainAxisAlignment.spaceBetween

image.png

3.5、设置主轴方向,环绕等分(MainAxisAlignment.spaceAround

image.png

3.6、设置主轴方向,剩余空间等分(MainAxisAlignment.spaceEvenly

image.png

4、Stack -- 叠加布局

4.1、简单的叠加

代码

class WidgetStack extends StatelessWidget {
  const WidgetStack({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: const Alignment(0,0),
      child: Stack(
        children: [
          Container(
            alignment: const Alignment(0, 0),
            child: const Icon(Icons.build,size: 80,),
            color: Colors.yellow,
            height: 300,
            width:300,
          ),
          Container(
            child: const Icon(Icons.ac_unit,size: 80,),
            color: Colors.blue,
            height: 200,
            width:200,
          ),
          Container(
            child: const Icon(Icons.access_alarm,size: 80,),
            color: Colors.deepOrangeAccent,
            height: 100,
            width:100,
          ),
        ],
      ),
    ) ;

  }
}

image.png

4.2、叠加 + 调整位置(Positioned

如果想在叠加的基础上,还想对子部件进行位置的调整,可以考虑对子部件套一个`Positioned`

image.png

4.3、叠加 + 中心对齐(Positioned

如果想实现多个子部件叠加,且要求它们的中心的对齐,可以用`Positioned`组件嵌套。

并设置left、top、right、bottom四个数值

注意:当同一轴上两个方向都设置了约束,那么对应的宽或高就不需要设置了
例如,已经设置了Positioned的right和left,那么就不需要设置子部件width,因为这样会导致冲突而报错。

image.png

5、交叉轴(crossAxisAlignment

5.1、什么是交叉轴

交叉轴,顾名思义就是和主轴垂直交叉的轴。比如

  • Row是水平布局,那么交叉轴指的就是纵向那条轴

  • Cloumn是纵向布局,那么交叉轴指的就是横向那条轴

5.2、交叉轴的值分别有什么效果

交叉轴CrossAxisAlignment有这么几个选项值

  • start
Row布局,   (交叉轴)纵向靠上👆
Cloumn布局,(交叉轴)横向靠左👈

为了得到明显的效果,给子部件设置大小不一样。

image.png

image.png

  • end
Row布局,   (交叉轴)纵向靠下👇
Cloumn布局,(交叉轴)横向靠右👉

image.png

image.png

  • center
Row布局,   (交叉轴)纵向居中
Cloumn布局,(交叉轴)横向居中

image.png

image.png

  • stretch
Row布局,   (交叉轴)纵向拉伸
Cloumn布局,(交叉轴)横向拉伸

image.png

image.png

  • baseline 这个有点特殊,如果你单独设置,那是会报错的

image.png

从控制台我们可以得知,原来错误的原因是少设置了一个属性textBaseline

image.png

改完之后再运行

image.png

运行是可以了,但是这个效果似乎...和end一样,那么有什么用呢?

既然它叫TextBaseline,开头单词是Text,那么是否和Text有关系呢?

我们把子部件由Icon换成Text

image.png

纵向布局,效果是这样的

image.png

经测试发现,TextBaseline对应着文本对齐,在某些业务场合会需要这样布局。

6、Expanded组件

6.1、交叉轴的特性

Expanded的特性是在主轴方法会自动填充剩余空间,且当同一层级,存在多个Expanded时,会均分剩余空间

image.png

如上图所示,Row横向布局,除去最左侧固定的50像素*50像素大小(灰色),剩余的空间被3个Expanded均分。

利用这个特性,我们可以实现把某一区域进行几等分。

6.2、Expanded之flex属性

Expanded有个flex属性,作用有点类似于细胞分裂,无论分成多少份,最终每个细胞都是一样大小,如果某个Expanded拆分成3份,那么最终显示的时候,3份占用的大小就是这个Expanded的实际大小。

举个例子:

class WidgetRow extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Container(width: 50,height: 50,color: Colors.grey,),
        Expanded(child: Container(
          child: const Text('标题',style: TextStyle(fontSize: 10),),
          color: Colors.yellow,
        ),flex: 1,),
        Expanded(child: Container(
          child: const Text('今日头条',style: TextStyle(fontSize: 14),),
          color: Colors.blue,
        ),flex: 2),
        Expanded(child: Container(
          child: const Text('国庆长假',style: TextStyle(fontSize: 18),),
          color: Colors.deepOrangeAccent,
        ),flex: 3),
      ],
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,//主轴方向
      crossAxisAlignment:CrossAxisAlignment.baseline,//交叉轴填充
      textBaseline: TextBaseline.alphabetic,
    );
  }
}

image.png

利用这个特性,我们可以实现对某一区域等比划分。

7、调整位置之Positionedmagrin

class WidgetStack01 extends StatelessWidget {
  const WidgetStack01({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    //
    return Container(
      alignment: const Alignment(0,0),
      child: Stack(
        children: [
          Container(
            alignment: const Alignment(0, 0),
            child: const Icon(Icons.build,size: 80,),
            color: Colors.yellow,
            height: 300,
            width:300,
          ),
          Container(
            child: const Icon(Icons.ac_unit,size: 80,),
            color: Colors.blue,
            height: 200,
            width: 200,
          ),
            Container(
              child: const Icon(Icons.access_alarm,size: 80,),
              color: Colors.deepOrangeAccent,
              height: 100,
              width:100,
            ),
        ],
      ),
    ) ;

  }
}

运行效果如下

image.png

要求:红色方块距离黄色方块20像素,请问如何实现?

光看图,我们会想到用margin,设置右边距20像素,一顿操作下来发现没效果

image.png

原因是margin是相对父视图,而黄色方块不是红色方块的父视图,所以这个方案行不通。

于是我们想到了Positioned,修改后代码如下

image.png

要注意的是,套上Positioned之后,PositionedrightContainermargin都会生效,所以应该把margin这行代码注释掉。

8、width、height和AspectRatio

class AspectRatioDemo extends StatelessWidget {
  const AspectRatioDemo({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: const Alignment(0,0),
      color: Colors.blueGrey,
      child: Container(
        color: Colors.deepOrangeAccent,
        // width: 100,
        height: 100,
        child: AspectRatio(
          aspectRatio: 1/2,
          child: Container(
              color: Colors.green,
              width: 200,
              height: 200,
            ),
          ),
      ),
    );
  }
}

image.png

image.png

image.png

上面3个图告诉我们:

  • AspectRatio会作用于当前Container

  • widthheightAspectRatio三者共存时,AspectRatio不会起作用。

image.png

上图告诉我们:

  • AspectRatio会对它子部件造成影响,且AspectRatio的子部件设置widthheight均无效。

结论:AspectRatio最好是用于设置当前Container的宽高比。

9、补充

main.dart 代码

import 'package:flutter/material.dart';
import 'other_components/layout_demo.dart';

void main() {
  // 演示各种组建
  runApp(ComponentsApp());
}

// 演示组件
class ComponentsApp extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return MaterialApp(
      home:Scaffold(
        appBar:AppBar(
          title:const Text("演示组件"),
        ),
        body: LayoutDemo(),
      ),
    );
  }
}

layout_demo.dart 部分代码

import 'dart:ui';
import 'package:flutter/material.dart';

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

    return Container(
      color: Colors.brown,
      child: WidgetRow(), //这个WidgetRow根据情况替换
    );
  }
}