美文网首页
Flutter的布局Widget

Flutter的布局Widget

作者: Imkata | 来源:发表于2023-03-05 14:30 被阅读0次

    为了实现界面内组件的各种排布方式,我们需要进行布局,和其他端不同的是,Flutter中因为万物皆Widget,所以布局也是使用Widget来完成的。

    Flutter中的布局组件非常多,有31个用于布局的组件,Flutter布局组件

    在学习的过程中,我们没必要一个个全部掌握,掌握最常用的,一些特殊的组件用到时去查文档即可。

    Flutter将布局组件分成了 单子布局组件(Single-child layout widgets) 和 多子布局组件(Multi-child layout widgets)

    一. 单子布局组件

    单子布局组件的含义是其只有一个子组件,可以通过设置一些属性设置该子组件所在的位置信息等。

    比较常用的单子布局组件有:Align、Center、Padding、Container。

    1.1. Align组件

    1.1.1. Align介绍

    看到Align这个词,我们就知道它有我们的对齐方式有关。

    在其他端的开发中(iOS、Android、前端)Align通常只是一个属性而已,但是Flutter中Align也是一个组件。

    我们可以通过源码来看一下Align有哪些属性:

    const Align({
      Key key,
      this.alignment: Alignment.center, // 对齐方式,默认居中对齐
      this.widthFactor, // 宽度因子,不设置的情况,会尽可能大
      this.heightFactor, // 高度因子,不设置的情况,会尽可能大
      Widget child // 要布局的子Widget
    })
    

    这里我们特别解释一下widthFactorheightFactor作用:

    • 因为子组件在父组件中的对齐方式必须有一个前提,就是父组件得知道自己的范围(宽度和高度);
    • 如果widthFactorheightFactor不设置,那么默认Align会尽可能的大(尽可能占据自己所在的父组件);
    • 我们也可以对他们进行设置,比如widthFactor设置为3,那么相对于Align的宽度是子组件宽度的3倍;

    1.1.2. Align演练

    我们简单演练一下Align。

    class MyHomeBody extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Align(
          child: Icon(Icons.pets, size: 36, color: Colors.red),
          alignment: Alignment.bottomRight,
          widthFactor: 3,
          heightFactor: 3,
        );
      }
    }
    

    注意:当然我们也可以包裹一个Container,这样就可以设置宽高了,当包裹Container以后,widthFactor和heightFactor就没有效果了。而且实际开发中我们也是Container使用的比较多,widthFactor基本不用。

    class AlignDemo extends StatelessWidget {
      const AlignDemo({
        Key key,
      }) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Container(
          width: 300,
          height: 300,
          color: Colors.red,
          child: Align(
              alignment: Alignment(1, 1),
              child: Icon(Icons.pets, size: 50)
          ),
        );
      }
    }
    

    1.2. Center组件

    1.2.1. Center介绍

    Center组件我们在前面已经用过很多次了。

    源码分析可知,Center没有任何作用,就是把所有参数再传给Align,又因为Align的alignment属性默认值就是Alignment.center,所以其实他们是一样的。

    class Center extends Align {
      const Center({ 
        Key key, 
        double widthFactor, 
        double heightFactor, 
        Widget child 
      }) : super(key: key, widthFactor: widthFactor, heightFactor: heightFactor, child: child);
    }
    

    1.3. Padding组件

    1.3.1. Padding介绍

    Padding组件在其他端也是一个属性而已,但是在Flutter中是一个Widget,但是Flutter中没有Margin这样一个Widget,这是因为外边距也可以通过Padding来完成。

    Padding通常用于设置子Widget到父Widget的边距(你可以称之为是父组件的内边距或子Widget的外边距)。

    源码分析:

    const Padding({
      Key key,
      @required this.padding, // EdgeInsetsGeometry类型(抽象类),使用EdgeInsets
      Widget child,
    })
    

    1.3.2. Padding演练

    代码演练:

    class MyHomeBody extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Padding(
          padding: EdgeInsets.all(20),
          child: Text(
            "莫听穿林打叶声,何妨吟啸且徐行。竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生。",
            style: TextStyle(
              color: Colors.redAccent,
              fontSize: 18
            ),
          ),
        );
      }
    }
    

    1.4. Container组件

    Container组件类似于其他Android中的View,iOS中的UIView。

    如果你需要一个视图,有一个背景颜色、图像、有固定的尺寸、需要一个边框、圆角等效果,那么就可以使用Container组件。

    1.4.1. Container介绍

    Container在开发中被使用的频率是非常高的,特别是我们经常会将其作为容器组件。

    下面我们来看一下Container有哪些属性:

    Container({
      this.alignment,
      this.padding, //容器内补白,属于decoration的装饰范围
      Color color, // 背景色
      Decoration decoration, // 背景装饰
      Decoration foregroundDecoration, //前景装饰
      double width,//容器的宽度
      double height, //容器的高度
      BoxConstraints constraints, //容器大小的限制条件
      this.margin,//容器外补白,不属于decoration的装饰范围
      this.transform, //变换
      this.child,
    })
    

    大多数属性在介绍其它容器时都已经介绍过了,不再赘述,但有两点需要说明:

    • 容器的大小可以通过widthheight属性来指定,也可以通过constraints来指定,如果同时存在时,widthheight优先。实际上Container内部会根据widthheight来生成一个constraints
    • colordecoration是互斥的,实际上,当指定color时,Container内会自动创建一个decoration;
    • decoration属性稍后我们详细学习;

    1.4.2. Container演练

    简单进行一个演示:

    class MyHomeBody extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Center(
          child: Container(
            color: Color.fromRGBO(3, 3, 255, .5),
            width: 100,
            height: 100,
            child: Icon(Icons.pets, size: 32, color: Colors.white),
          ),
        );
      }
    }
    

    1.4.3. BoxDecoration

    Container有一个非常重要的属性 decoration

    • 他对应的类型是Decoration类型,但是它是一个抽象类。
    • 在开发中,我们经常使用它的实现类BoxDecoration来进行实例化。

    BoxDecoration常见属性:

      const BoxDecoration({
        this.color, // 颜色,会和Container中的color属性冲突
        this.image, // 背景图片
        this.border, // 边框,对应类型是Border类型,里面每一个边框使用BorderSide
        this.borderRadius, // 圆角效果
        this.boxShadow, // 阴影效果
        this.gradient, // 渐变效果
        this.backgroundBlendMode, // 背景混合
        this.shape = BoxShape.rectangle, // 形变
      })
    

    效果演示:

    class HYHomeContent extends StatefulWidget {
      @override
      _HYHomeContentState createState() => _HYHomeContentState();
    }
    
    class _HYHomeContentState extends State<HYHomeContent> {
    
      @override
      Widget build(BuildContext context) {
        return Container(
          // 背景色
          // color: Colors.red,
          // 宽度
          width: 200,
          // 高度
          height: 200,
          // 设置子元素的对齐方式
          alignment: Alignment(0, 0),
          // 设置内边距
          padding: EdgeInsets.all(20),
          // 设置外边距
          margin: EdgeInsets.all(10),
          child: Text("Hello World"),
          // 形变
          transform: Matrix4.rotationZ(50),
          decoration: BoxDecoration(
              // 和外面的color是一个意思,同时设置会有冲突,只能设置一个
              color: Colors.red,
              border: Border.all(
                  width: 5,
                  color: Colors.purple
              ),
              // 设置圆角
              borderRadius: BorderRadius.circular(100),
              // 设置阴影
              // boxShadow: [
                //  BoxShadow(color: Colors.orange, offset: Offset(10, 10), spreadRadius: 5, blurRadius: 10),
                //  BoxShadow(color: Colors.blue, offset: Offset(-10, 10), spreadRadius: 5, blurRadius: 10),
              // ]
          ),
        );
      }
    }
    

    1.4.4. 实现圆角图像

    上一个章节我们提到可以通过 Container + BoxDecoration来实现圆角图像。

    实现代码如下:

    class HomeContent extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Center(
          child: Container(
            width: 200,
            height: 200,
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(20),
              image: DecorationImage(
                image: NetworkImage("https://tva1.sinaimg.cn/large/006y8mN6gy1g7aa03bmfpj3069069mx8.jpg"),
              )
            ),
          ),
        );
      }
    }
    

    二. 多子布局组件

    在开发中,我们经常需要将多个Widget放在一起进行布局,比如水平方向、垂直方向排列,甚至有时候需要他们进行层叠,比如图片上面放一段文字等;

    这个时候我们需要使用多子布局组件(Multi-child layout widgets)。

    比较常用的多子布局组件是Row、Column、Stack,我们来学习一下他们的使用。

    2.1. Flex组件

    事实上,我们即将学习的Row组件和Column组件都继承自Flex组件。

    • Flex组件和Row、Column属性主要的区别就是多一个direction。
    • 当direction的值为Axis.horizontal的时候,则是Row。
    • 当direction的值为Axis.vertical的时候,则是Column。

    在学习Row和Column之前,我们先学习主轴交叉轴的概念。

    因为Row是一行排布,Column是一列排布,那么它们都存在两个方向,并且两个Widget排列的方向应该是对立的。

    它们之中都有主轴(MainAxis)和交叉轴(CrossAxis)的概念:

    • 对于Row来说,主轴(MainAxis)和交叉轴(CrossAxis)分别是下图
    • 对于Column来说,主轴(MainAxis)和交叉轴(CrossAxis)分别是下图

    2.1. Row组件

    2.1.1. Row介绍

    Row组件用于将所有的子Widget排成一行,实际上这种布局应该是借鉴于Web的Flex布局。

    如果熟悉Flex布局,会发现非常简单。

    从源码中查看Row的属性:

      Row({
        super.key,
        super.mainAxisAlignment,
        super.mainAxisSize,
        super.crossAxisAlignment,
        super.textDirection,
        super.verticalDirection,
        super.textBaseline, // NO DEFAULT: we don't know what the text's baseline should be
        super.children,
      }) : super(
        direction: Axis.horizontal,
      );
    
    1. mainAxisAlignment:表示的是子元素在主轴的排列方式,有以下取值:
    * MainAxisAlignment:
    *  - start: 主轴的开始位置挨个摆放元素(默认值)
    *  - end: 主轴的结束位置挨个摆放元素
    *  - center: 主轴的中心点对齐
    *  - spaceBetween: 左右两边的间距为0, 其它元素之间平分间距
    *  - spaceAround: 左右两边的间距是其它元素之间的间距的一半
    *  - spaceEvenly: 所有的间距平分空间
    
    1. mainAxisSize:设置主轴的size,默认是max。
    * Row特点:
    *  - 水平方向尽可能占据比较大的空间,垂直方向包裹内容。
    *  - 如果水平方向也是希望包裹内容, 那么设置mainAxisSize = min。
    
    1. crossAxisAlignment:子元素在交叉轴上的排布方式。
    * CrossAxisAlignment:
    *  - start: 交叉轴的起始位置对齐
    *  - end: 交叉轴的结束位置对齐
    *  - center: 中心点对齐(默认值)
    *  - baseline: 基线对齐(必须有文本的时候才起效果),设置基线对齐的时候必须指定是哪种基线,也就是必须设置textBaseline的值。
    *  - stretch: 默认情况下,垂直方向是包裹内容的,也就是垂直方向的高度是最大的子元素的高度。设置完这个属性以后,就是先让Row占据交叉轴尽可能大的空间,再将所有的子Widget交叉轴的高度拉伸到最大。设置完这个属性后,一般子元素会将屏幕的垂直高度铺满,如果不想拉伸这么长,我们一般的做法是包裹一层Container,然后再给container设置高度。
    
    1. textBaseline:文字的基线,两种基线的差异不大,很少使用。
    /// A horizontal line used for aligning text.
    enum TextBaseline {
      /// The horizontal line used to align the bottom of glyphs for alphabetic characters.
      alphabetic,
    
      /// The horizontal line used to align ideographic characters.
      ideographic,
    }
    
    1. textDirection:文字的方向,默认是从左到右,开发中很少使用(只对Row有效果)。
    enum TextDirection {
      /// The text flows from right to left (e.g. Arabic, Hebrew).
      rtl,
      /// The text flows from left to right (e.g., English, French).
      ltr,
    }
    
    1. verticalDirection:垂直方向,默认是down(和textDirection的意思有点一样,只对 Column有效果)
    enum VerticalDirection {
      /// Boxes should start at the bottom and be stacked vertically towards the top.
      ///
      /// The "start" is at the bottom, the "end" is at the top.
      up,
    
      /// Boxes should start at the top and be stacked vertically towards the bottom.
      ///
      /// The "start" is at the top, the "end" is at the bottom.
      down,
    }
    

    2.1.2. Row演练

    我们来对部分属性进行简单的代码演练,其他一些属性大家自己学习一下。

    class MyHomeBody extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          crossAxisAlignment: CrossAxisAlignment.end,
          mainAxisSize: MainAxisSize.max,
          children: <Widget>[
            Container(color: Colors.red, width: 60, height: 60),
            Container(color: Colors.blue, width: 80, height: 80),
            Container(color: Colors.green, width: 70, height: 70),
            Container(color: Colors.orange, width: 100, height: 100),
          ],
        );
      }
    }
    

    2.1.3. mainAxisSize

    默认情况下,Row会尽可能占据多的宽度,让子Widget在其中进行排布,这是因为mainAxisSize属性默认值是MainAxisSize.max

    我们来看一下,如果这个值被修改为MainAxisSize.min会什么变化:

    2.1.4. TextBaseline

    关于TextBaseline的取值解析

    2.1.5. Expanded

    如果我们希望红色和黄色的Container Widget不要设置固定的宽度,而是占据剩余的部分,这个时候应该如何处理呢?

    这个时候我们可以使用 Expanded 来包裹 Container Widget,并且将它的宽度不设置值;

    • flex属性,弹性系数,Row会根据两个Expanded的弹性系数来决定它们占据剩下空间的比例
    class MyHomeBody extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          crossAxisAlignment: CrossAxisAlignment.end,
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            Expanded(
              flex: 1,
              child: Container(color: Colors.red, height: 60),
            ),
            Container(color: Colors.blue, width: 80, height: 80),
            Container(color: Colors.green, width: 70, height: 70),
            Expanded(
              flex: 1,
              child: Container(color: Colors.orange, height: 100),
            )
          ],
        );
      }
    }
    

    2.2. Column组件

    Column组件用于将所有的子Widget排成一列,学会了前面的Row后,Column只是和row的方向不同而已。

    2.2.1. Column介绍

    我们直接看它的源码:我们发现和Row属性是一致的,不再解释

      Column({
        Key key,
        MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
        MainAxisSize mainAxisSize = MainAxisSize.max,
        CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
        TextDirection textDirection,
        VerticalDirection verticalDirection = VerticalDirection.down,
        TextBaseline textBaseline,
        List<Widget> children = const <Widget>[],
      })
    

    2.2.2. Column演练

    我们直接将Row的代码中Row改为Column,查看代码运行效果:

    class MyHomeBody extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          crossAxisAlignment: CrossAxisAlignment.end,
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            Expanded(
              flex: 1,
              child: Container(color: Colors.red, width: 60),
            ),
            Container(color: Colors.blue, width: 80, height: 80),
            Container(color: Colors.green, width: 70, height: 70),
            Expanded(
              flex: 1,
              child: Container(color: Colors.orange, width: 100),
            )
          ],
        );
      }
    }
    

    2.3. Stack组件

    在开发中,我们多个组件很有可能需要重叠显示,比如在一张图片上显示文字或者一个按钮等。

    在Android中可以使用Frame来实现,在Web端可以使用绝对定位,在Flutter中我们需要使用层叠布局Stack。

    2.3.1. Stack介绍

    我们还是通过源码来看一下Stack有哪些属性:

    Stack({
      Key key,
      this.alignment = AlignmentDirectional.topStart,
      this.textDirection,
      this.fit = StackFit.loose,
      this.overflow = Overflow.clip,
      List<Widget> children = const <Widget>[],
    }) 
    

    参数解析:

    • alignment:此参数决定如何去对齐没有定位(没有使用Positioned)或部分定位的子widget。所谓部分定位,在这里特指没有在某一个轴上定位:left、right为横轴,top、bottom为纵轴,只要包含某个轴上的一个定位属性就算在该轴上有定位。
    • textDirection:和Row、Wrap的textDirection功能一样,都用于决定alignment对齐的参考系即:textDirection的值为TextDirection.ltr,则alignment的start代表左,end代表右;textDirection的值为TextDirection.rtl,则alignment的start代表右,end代表左。
    • fit:此参数用于决定没有定位的子widget如何去适应Stack的大小。StackFit.loose表示使用子widget的大小,StackFit.expand表示扩伸到Stack的大小。
    • overflow:此属性决定如何显示超出Stack显示空间的子widget,值为Overflow.clip时,超出部分会被剪裁(隐藏),值为Overflow.visible 时则不会。

    2.3.2. Stack演练

    Stack会经常和Positioned一起来使用,Positioned可以决定组件在Stack中的位置,用于实现类似于Web中的绝对定位效果。

    我们来看一个简单的演练:

    class MyHomeBody extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Stack(
          children: <Widget>[
            Container(
              color: Colors.purple,
              width: 300,
              height: 300,
            ),
            Positioned(
              left: 20,
              top: 20,
              child: Icon(Icons.favorite, size: 50, color: Colors.white)
            ),
            Positioned(
              bottom: 20,
              right: 20,
              child: Text("你好啊,李银河", style: TextStyle(fontSize: 20, color: Colors.white)),
            )
          ],
        );
      }
    }
    

    注意:Positioned组件只能在Stack中使用。

    相关文章

      网友评论

          本文标题:Flutter的布局Widget

          本文链接:https://www.haomeiwen.com/subject/hibekdtx.html