Flutter学习记录——7.基础布局详解


前面讲解了 Flutter 的几个基础组件,这节课将讲解跟布局相关的 Widget。
每个平台的应用都有其自己的布局方式,例如 Android 有线性布局、相对布局、绝对布局、帧布局、表格布局等等,HTML 前端也有自己的布局方式。Flutter 当然也不例外。那么这节课就带领大家对 Flutter 的基础布局 Widget 中的几个典型的布局Widget进行详细分析,并结合案例进行详细的用法讲解。

1.基本布局

1.1 Scaffold

Flutter 布局系列的 Widget 一般分为两种,一种是只有单个子元素的布局 Widget,也就是SingleChildRenderObjectWidget;另一个种是具有多个子元素的布局 Widget,一般都有 children 参数,继承自MultiChildRenderObjectWidget。非布局系列 Widget 也有无子元素的 Widget,如 Text、Image 组件,这些无子元素的 Widget 属于 LeafRenderObjectWidget。Flutter 中不同的布局 Widget 对子 Widget 的排列渲染方式不同。接下来我们看下其中比较常用的一个布局 Widget——Scaffold。

Scaffold 是一个页面布局脚手架,实现了基本的 Material 布局,继承自 StatefulWidget,是有状态组件。我们知道大部分的应用页面都是含有标题栏,主体内容,底部导航菜单或者侧滑抽屉菜单等等构成,那么每次都重复写这些内容会大大降低开发效率,所以 Flutter 提供了 Material 风格的 Scaffold 页面布局脚手架,可以很快地搭建出这些元素部分:

Scaffold

Scaffold 有下面几个主要属性。

  • appBar:显示在界面上的一个标题栏 AppBar。
  • body: 当前页面的主体内容 Widget。
  • floatingActionButton:页面的主要功能按钮,不配置就不会显示。
  • persistentFooterButtons:固定显示在下方的按钮,比如对话框下方的确定、取消按钮。
  • drawer:侧滑抽屉菜单控件。
  • backgroundColor:body 内容的背景颜色。
  • bottomNavigationBar:显示在页面底部的导航栏。
  • resizeToAvoidBottomPadding:类似于 Android 中的 android:windowSoftInputMode=‘adjustResize’,避免类似弹出键盘这种操作遮挡布局使用的。
  • bottomSheet:底部拉出菜单。

具体可配置的属性参数,我们看下看下 Scaffold 构造方法:

const Scaffold({
    Key key,
    // 标题栏
    this.appBar,
    // 中间主体内容部分
    this.body,
    // 悬浮按钮
    this.floatingActionButton,
    // 悬浮按钮位置
    this.floatingActionButtonLocation,
    // 悬浮按钮动画
    this.floatingActionButtonAnimator,
    // 固定在下方显示的按钮
    this.persistentFooterButtons,
    // 侧滑抽屉菜单
    this.drawer,
    this.endDrawer,
    // 底部菜单
    this.bottomNavigationBar,
    // 底部拉出菜单
    this.bottomSheet,
    // 背景色
    this.backgroundColor,
    this.resizeToAvoidBottomPadding,
    // 重新计算body布局空间大小,避免被遮挡
    this.resizeToAvoidBottomInset,
    // 是否显示到底部,默认为true将显示到顶部状态栏
    this.primary = true,
    this.drawerDragStartBehavior = DragStartBehavior.down,
  })

如果想显示 Snackbar 或 bottomSheet,可以这样调用:

Scaffold.of(context).showSnackBar(new SnackBar(
      content: Text('Hello!'),
    ));

Scaffold.of(context).showBottomSheet…
接下来看个实例的代码:

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

class ScaffoldSamples extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return ScaffoldSamplesState();
  }
}

class ScaffoldSamplesState extends State<ScaffoldSamples> {
  int _selectedIndex = 0;

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return scaffoldWidget(context);
  }

  Widget scaffoldWidget(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("标题栏"),
        actions: <Widget>[
          //导航栏右侧菜单
          IconButton(icon: Icon(Icons.share), onPressed: () {}),
        ],
      ),
      body: Text("Body内容部分"),
      //抽屉
      drawer: Drawer(
        child: DrawerHeader(
          child: Text("DrawerHeader"),
        ),
      ),
      // 底部导航
      bottomNavigationBar: BottomNavigationBar(
        items: <BottomNavigationBarItem>[
          BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('Home')),
          BottomNavigationBarItem(
              icon: Icon(Icons.category), title: Text('Cagergory')),
          BottomNavigationBarItem(
              icon: Icon(Icons.person), title: Text('Persion')),
        ],
        currentIndex: _selectedIndex,
        fixedColor: Colors.blue,
        onTap: _onItemTap,
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () {
          _onAdd();
        },
      ),
    );
  }

  void _onItemTap(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  void _onAdd() {}
}

这个实例实现效果如前面两张图片所示效果。

1.2 Container

Container 是一个容器类布局 Widget,Container 可以说是多个小组件的一个组合容器,如可以设置 padding、margin、Align、Decoration、Matrix4 等等,可以说用起来很方便,很高效。

我们看下 Container 构造方法相关属性和作用:

Container({
    Key key,
    // 容器子Widget对齐方式
    this.alignment,
    // 容器内部padding
    this.padding,
    // 背景色
    Color color,
    // 背景装饰
    Decoration decoration,
    // 前景装饰
    this.foregroundDecoration,
    // 容器的宽度
    double width,
    // 容器的高度
    double height,
    // 容器大小的限制条件
    BoxConstraints constraints,
    // 容器外部margin
    this.margin,
    // 变换,如旋转
    this.transform,
    // 容器内子Widget
    this.child,
  })

接下来看个实例的代码:

body: Container(
        constraints: BoxConstraints.expand(
          height: Theme.of(context).textTheme.display1.fontSize * 1.1 + 200.0,
        ),
        padding: const EdgeInsets.all(8.0),
        // 背景色
        color: Colors.teal.shade700,
        // 子Widget居中
        alignment: Alignment.center,
        // 子Widget元素
        child: Text('Hello World',
            style: Theme.of(context)
                .textTheme
                .display1
                .copyWith(color: Colors.white)),
        // 前景装饰
        foregroundDecoration: BoxDecoration(
          image: DecorationImage(
            image: NetworkImage('https://www.example.com/images/frame.png'),
            centerSlice: Rect.fromLTRB(270.0, 180.0, 1360.0, 730.0),
          ),
        ),
        // Container旋转
        transform: Matrix4.rotationZ(0.1),
      ),

运行效果如下图所示:

Container

1.3 Center

Center 主要用于对齐,将内部子 Widget 与自身进行居中对齐,并根据子 Widget 的大小自动调整自身大小。

Center Widget 是继承自 Align,Align 继承自 SingleChildRenderObjectWidget,也是单子元素 Widget。

看下 Center 的构造方法:

 Center({
    Key key,
    // 宽度因子
    double widthFactor, 
    // 高度因子
    double heightFactor, 
    // 子元素
    Widget child
   })

大家可能对这个宽度和高度因子的作用不太明白,其实就是设置 Center 的宽度和高度是子元素宽度和高度的倍数的,widthFactor 和 heightFactor 可以不设置,默认 Center 容器宽度横向充满,高度包裹子元素。如将widthFactor 和 heightFactor 设置为 2.0 的话,则 Center 容器的占用宽度和高度是子元素宽度和高度的 2 倍,但是最大不超过屏幕的宽高。

接下来看个实例演示用法:

body: Column(
        children: <Widget>[
          Container(
            color: Colors.blueGrey,
            child: Center(
              widthFactor: 2,
              heightFactor: 2,
              child: Container(
                width: 60,
                height: 30,
                color: Colors.red,
              ),
            ),
          ),
          SizedBox(
            height: 10,
          ),
          Center(
            child: Container(
              width: 60,
              height: 30,
              color: Colors.teal,
            ),
          ),
          SizedBox(
            height: 10,
          ),
          Center(
            child: Container(
              height: 100.0,
              width: 100.0,
              color: Colors.yellow,
              child: Align(
                // 设置对齐位置约束
                alignment: FractionalOffset(0.2, 0.6),
                child: Container(
                  height: 40.0,
                  width: 40.0,
                  color: Colors.red,
                ),
              ),
            ),
          ),
        ],
      ),

运行效果如下图所示:
Center

2.线性布局

2.1 Row

Row 布局组件类似于 Android 中的 LinearLayout 线性布局,它用来做水平横向布局使用,里面的 children 子元素按照水平方向进行排列。

Row 的继承关系如下:

Row -> Flex -> MultiChildRenderObjectWidget -> RenderObjectWidget …

可以看出 Row 是 Flex 的拓展子类,也是多子元素的一个组件之一(内部可以包含多个子元素)。

我们看下 Row 布局组件的大致效果图:

Row

所有元素水平排成一行。

那么接下来我们看下 Row 的构造方法:

Row({
    Key key,
    // 主轴方向上的对齐方式(Row的主轴是横向轴)
    MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
    // 在主轴方向(Row的主轴是横向轴)占有空间的值,默认是max
    MainAxisSize mainAxisSize = MainAxisSize.max,
    // 在交叉轴方向(Row是纵向轴)的对齐方式,Row的高度等于子元素中最高的子元素高度
    CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
    // 水平方向子元素的排列方向:从左到右排列还是反向
    TextDirection textDirection,
    // 表示纵轴(垂直)的对齐排列方向,默认是VerticalDirection.down,表示从上到下。这个参数一般用于Column组件里
    VerticalDirection verticalDirection = VerticalDirection.down,
    // 字符对齐基线方式
    TextBaseline textBaseline,
    // 子元素集合
    List<Widget> children = const <Widget>[],
  })

接下来详细看下 Row 的主轴和交叉轴属性。

MainAxisAlignment(主轴属性:主轴方向上的对齐方式,Row 是横向轴为主轴)

enum MainAxisAlignment {
  // 按照主轴起点对齐,例如:按照靠近最左侧子元素对齐
  start,

  // 将子元素放置在主轴的末尾,按照末尾对齐
  end,

  // 子元素放置在主轴中心对齐
  center,

  // 将主轴方向上的空白区域均分,使得子元素之间的空白区域相等,首尾子元素都靠近首尾,没有间隙。有点类似于两端对齐
  spaceBetween,

  // 将主轴方向上的空白区域均分,使得子元素之间的空白区域相等,但是首尾子元素的空白区域为1/2
  spaceAround,

  // 将主轴方向上的空白区域均分,使得子元素之间的空白区域相等,包括首尾子元素
  spaceEvenly,
}

再看下 Row 的交叉属性。

CrossAxisAlignment(交叉属性:在交叉轴方向的对齐方式,Row 是纵向轴。Row 的高度等于子元素中最高的子元素高度)

enum CrossAxisAlignment {
  // 子元素在交叉轴上起点处展示
  start,

  // 子元素在交叉轴上末尾处展示
  end,

  // 子元素在交叉轴上居中展示
  center,

  // 让子元素填满交叉轴方向
  stretch,

  // 在交叉轴方向,使得子元素按照baseline对齐
  baseline,
}

再看下 MainAxisSize 属性。

MainAxisSize(在主轴方向子元素占有空间的方式,Row 的主轴是横向轴。默认是 max)

enum MainAxisSize {
  // 根据传入的布局约束条件,最大化主轴方向占用可用空间,也就是尽可能充满可用宽度
  max,

  // 与max相反,是最小化占用主轴方向的可用空间
  min,
}

接下来我们通过一个实例来学习下 Row 的布局特点。

Column(
        children: <Widget>[
          // 默认横向排列元素
          Row(
            verticalDirection: VerticalDirection.up,
            textBaseline: TextBaseline.ideographic,
            children: <Widget>[
              RaisedButton(
                color: Colors.blue,
                child: Text(
                  '我是按钮一\n 按钮',
                  style: TextStyle(color: Colors.white),
                ),
                onPressed: () {},
              ),
              RaisedButton(
                color: Colors.grey,
                child: Text(
                  '   我是按钮二  ',
                  style: TextStyle(color: Colors.black),
                ),
                onPressed: () {},
              ),
              RaisedButton(
                color: Colors.orange,
                child: Text(
                  '      我是按钮三    ',
                  style: TextStyle(color: Colors.white),
                ),
                onPressed: () {},
              ),
            ],
          ),
          SizedBox(
            height: 10,
          ),
          // 默认横向排列元素
          Row(
            children: <Widget>[
              const FlutterLogo(),
              const Expanded(
                child: Text(
                    'Flutter\'s hot reload helps you quickly and easily experiment, build UIs, add features, and fix bug faster. Experience sub-second reload times, without losing state, on emulators, simulators, and hardware for iOS and Android.'),
              ),
              const Icon(Icons.sentiment_very_satisfied),
            ],
          ),
          SizedBox(
            height: 10,
          ),
          // 居中排列元素
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                " 我们居中显示 |",
                style: TextStyle(color: Colors.teal),
              ),
              Text(" Flutter的Row布局组件 "),
            ],
          ),
        ],
    ),

运行效果如下图所示:

Row

2.2 Column

在学习完了 Row 布局组件后,再学习 Column 很容易,Row 是横向排列元素,Column 是纵向排列子元素,可以对比着学习。

Column 的继承关系如下:

Column -> Flex -> MultiChildRenderObjectWidget -> RenderObjectWidget …

Column 也是 Flex 的拓展子类,也是多子元素的一个组件之一(内部可以包含多个子元素)。

我们看下 Column 布局组件的大致效果图:

Column

所有元素纵向排成一列。

构造方法是一致的,只不过主轴和交叉轴和 Row 是相反的。这里就不再重复讲解了。

接下来看一个实例:

Column(
        // 纵向排列子元素
        children: <Widget>[
          RaisedButton(
            color: Colors.blue,
            child: Text(
              '我是按钮一',
              style: TextStyle(color: Colors.white),
            ),
            onPressed: () {},
          ),
          RaisedButton(
            color: Colors.grey,
            child: Text(
              '   我是按钮二  ',
              style: TextStyle(color: Colors.black),
            ),
            onPressed: () {},
          ),
          RaisedButton(
            color: Colors.orange,
            child: Text(
              '      我是按钮三    ',
              style: TextStyle(color: Colors.white),
            ),
            onPressed: () {},
          ),
          SizedBox(
            height: 10,
          ),
          const FlutterLogo(),
          Text(
              'Flutter\'s hot reload helps you quickly and easily experiment, build UIs, add features, and fix bug faster. Experience sub-second reload times, without losing state, on emulators, simulators, and hardware for iOS and Android.'),
          const Icon(Icons.sentiment_very_satisfied),
        ],
      ),

运行效果如下图所示:

Column

3.弹性布局

Flex 组件是 Row 和 Column 的父类,主要用于弹性布局,类似于HTML 中的 Flex 弹性盒子布局,可以按照一定比例进行分类布局空间。

Flex 继承自 MultiChildRenderObjectWidget,Flex 也是多子元素的一个组件之一(内部可以包含多个子元素)。

Flex 一般和 Expanded 搭配使用,Expanded 组件从名字就可以看出它的特点,就是让子元素扩展占用 Flex 的剩余空间。

我们看下 Flex 布局组件的大致效果图:

Flex

按钮一占用 2/3 的横向空间,按钮二占用剩余 1/3 空间。

我们看下 Flex 构造方法:

Flex({
    Key key,
    // 子元素排列方向:横向还是纵向
    @required this.direction,
    this.mainAxisAlignment = MainAxisAlignment.start,
    this.mainAxisSize = MainAxisSize.max,
    this.crossAxisAlignment = CrossAxisAlignment.center,
    this.textDirection,
    this.verticalDirection = VerticalDirection.down,
    this.textBaseline,
    List<Widget> children = const <Widget>[],
  })

单独看 Flex 组件没有意义,因为一般直接用它的子类 Row 和 Column 来使用。而 Flex 主要是和 Expanded 搭配使用。我们再看下 Expanded 组件构造方法:

const Expanded({
    Key key,
    // 占用空间比重、权重
    int flex = 1,
    // 子元素
    @required Widget child,
  }) 

我们通过一个实例看下 Flex 和 Expanded 搭配用法:

body: Row(
          children: <Widget>[
            Expanded(
              // flex设置权重,这里是占2/5空间
              flex: 2,
              child: RaisedButton(
                color: Colors.blue,
                child: Text(
                  '我是按钮一',
                  style: TextStyle(color: Colors.white),
                ),
                onPressed: () {},
              ),
            ),
            // flex设置权重,这里是占1/5空间
            Expanded(
              flex: 1,
              child: Column(
                children: <Widget>[
                  Expanded(
                    flex: 2,
                    child: RaisedButton(
                      color: Colors.grey,
                      child: Text(
                        '   我是按钮一  ',
                        style: TextStyle(color: Colors.black),
                      ),
                      onPressed: () {},
                    ),
                  ),
                  Expanded(
                    flex: 1,
                    child: RaisedButton(
                      color: Colors.teal,
                      child: Text(
                        '   我是按钮二  ',
                        style: TextStyle(color: Colors.white),
                      ),
                      onPressed: () {},
                    ),
                  )
                ],
              ),
            ),
            // flex设置权重,这里是占2/5空间
            Expanded(
              flex: 2,
              child: RaisedButton(
                color: Colors.grey,
                child: Text(
                  '   我是按钮二  ',
                  style: TextStyle(color: Colors.black),
                ),
                onPressed: () {},
              ),
            )
          ],
        )

运行效果图如下:

Flex

4.层叠布局

Stack 和 IndexStack 都是层叠布局方式,类似于 Android 里的 FrameLayout 帧布局,内部子元素是有层级堆起来的。

Stack 继承自 MultiChildRenderObjectWidget,Stack 也是多子元素的一个组件之一(内部可以包含多个子元素)。

而 IndexedStack 继承自 Stack,扩展了 Stack的一些特性。它的作用是显示第 index 个子元素,其他子元素都是不可见的。所以 IndexedStack 的尺寸永远是跟最大的子元素尺寸一致。

Stack 的布局行为,是根据子元素是 positioned 还是 non-positioned 来区分的:

  • 对于 positioned 的子元素,它们的位置会根据所设置的 top、bottom、right 或 left 属性来确定,这几个值都是相对于 Stack 的左上角;
  • 对于 non-positioned 的子元素,它们会根据 Stack 的 aligment 来设置位置。

Stack 布局的子元素层级堆叠顺序:最先布局绘制的子元素在最底层,越后绘制的越在顶层。类似于 Web 中的 z-index。

看下 Stack 布局组件的效果图:

Stack

默认按照左上角,有层级的绘制排列。

看下 Stack 的构造方法:

Stack({
    Key key,
    // 对齐方式,默认是左上角(topStart)
    this.alignment = AlignmentDirectional.topStart,
    // 对齐方向
    this.textDirection,
    // 定义如何设置无定位子元素尺寸,默认为loose
    this.fit = StackFit.loose,
    // 超过的部分子元素处理方式
    this.overflow = Overflow.clip,
    // 子元素
    List<Widget> children = const <Widget>[],
  })

我们看下 alignment:

  // 左上角
  static const Alignment topLeft = Alignment(-1.0, -1.0);

  // 主轴顶部对齐,交叉轴居中
  static const Alignment topCenter = Alignment(0.0, -1.0);

  // 主轴顶部对齐,交叉轴偏右
  static const Alignment topRight = Alignment(1.0, -1.0);

  // 主轴居中,交叉轴偏左
  static const Alignment centerLeft = Alignment(-1.0, 0.0);

  // 居中
  static const Alignment center = Alignment(0.0, 0.0);

  // 主轴居中,交叉轴偏右
  static const Alignment centerRight = Alignment(1.0, 0.0);

  // 主轴底部对齐,交叉轴偏左
  static const Alignment bottomLeft = Alignment(-1.0, 1.0);

  // 主轴底部对齐,交叉轴居中
  static const Alignment bottomCenter = Alignment(0.0, 1.0);

  // 主轴底部对齐,交叉轴偏右
  static const Alignment bottomRight = Alignment(1.0, 1.0);

看下 fit 属性:

enum StackFit {
  // 子元素宽松的取值,可以从min到max的尺寸
  loose,

  // 子元素尽可能的占用剩余空间,取max尺寸
  expand,

  // 不改变子元素的约束条件
  passthrough,
}

最后看下 overflow 属性:

enum Overflow {
  // 超出部分不会被裁剪,正常显示
  visible,

  // 超出部分会被裁剪
  clip,
}

我们看下 IndexedStack 构造方法:

IndexedStack({
    Key key,
    AlignmentGeometry alignment = AlignmentDirectional.topStart,
    TextDirection textDirection,
    StackFit sizing = StackFit.loose,
    // 多了一个索引,索引的这个元素显示,其他元素隐藏
    this.index = 0,
    // 子元素
    List<Widget> children = const <Widget>[],
  })

接下来通过一个实例来演示下 Stack 和 IndexedStack 的用法:

body: Column(
          children: <Widget>[
            // Stack层叠布局
            Stack(
              children: <Widget>[
                Container(
                  width: 300,
                  height: 300,
                  color: Colors.grey,
                ),
                Container(
                  width: 200,
                  height: 200,
                  color: Colors.teal,
                ),
                Container(
                  width: 100,
                  height: 100,
                  color: Colors.blue,
                ),
                Text(
                  "Stack",
                  style: TextStyle(color: Colors.white),
                ),
              ],
            ),
            SizedBox(
              height: 10,
            ),
            // IndexedStack层叠布局
            IndexedStack(
              // 指定显示的子元素序号,其余子元素隐藏
              index: 2,
              children: <Widget>[
                Container(
                  width: 300,
                  height: 300,
                  color: Colors.grey,
                ),
                Container(
                  width: 200,
                  height: 200,
                  color: Colors.teal,
                ),
                Container(
                  width: 100,
                  height: 100,
                  color: Colors.blue,
                ),
                Text(
                  "Stack",
                  style: TextStyle(color: Colors.white),
                ),
              ],
            )
          ],
        )

运行效果图如下:

Stack和IndexedStack

发布了253 篇原创文章 · 获赞 52 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_41151659/article/details/103330681