最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Flutter | 布局组件

    正文概述 掘金(345丶)   2021-03-02   662

    布局类组件都会包含一个或多个组件,不同的布局类组件对子组件(layout)方式不同。在 Flutter 中 Element 树才是最终的绘制树,Element 树是通过 Widget 树来创建的 (通 Widget.createElement()) ,Widget 其实就是 Element 的配置数据。

    在 Fluter 中,根据 Widget 是否需要包含子节点将 Widget 分为了三类,分别对应三种 Element,如下表:

    Widget对应的 Element用途
    LeafRenderObjectWidgetLeafRenderObjectElementwidget 树的叶子节点,用于没有子节点的 Widget,通过基础组件都属于这一类,如 Image等SingleChildRenderObjectWidgetSingleChindRenderObjectElement包含一个 子 Widget,如:ConstrainedBox,DecoratedBox等MultiChildRenderObjectWidgetMultiChildRenderObjectElement包含多个子Widget,一般都有一个 children 参数,接收一个 Widget 数组,如 Row,Column,Stack 等

    布局组件就是直接或间接继承(包含)MultiChildRenderObjectWidget 的 Widget,他们一般都会有一个 children 属性用于接收子 Widget 。

    一个普通的 Widget 继承路线为:

    继承 (Stateless/Stateful)Widget ,然后实现 build 方法

    在 build 方法中通过创建继承自 (Leaf/SingleChild/MultiChild)RenderObjectWidget 的类,然后实现对应的方法来构建最终的渲染UI界面的对象(RenderObject)

    (Leaf/SingleChild/MultiChild)RenderObjectWidget 则是继承自 RenderObjectWidget ,最终继承自 Widget。

    在 RenderObjectWidget 和 Widget 中定义着 创建,更新RenderObject 的方法。以及 createElement 方法。

    其实 createElement 方法是在 (Leaf/SingleChild/MultiChild)RenderObjectWidget 类中实现的,而创建,更新 ObjectRender 则是在 (Leaf/SingleChild/MultiChild)RenderObjectWidget 的实现类中完成的


    线性布局(Row 和 Column)

    线性布局指的是沿着水平或者垂直方向排布子组件。在 Flutter 中通过 Row 和 Column 来实现线性布局,类似于 Android 中的 LinearLayout 控件

    Row 和 Column 都继承子 Flex,至于 Fiex 暂不多说

    主轴和纵轴

    在线性布局中,如果布局是水平方向,主轴就是指水平方向,纵轴即垂直方向;如果布局是垂直方向,主轴就是垂直方向,那么纵轴就是水平方向。

    在线性布局中,有两个定义对齐方式的枚举类 MainAxisAlignmentCrossAxisAlignment ,分别代表主轴对齐和纵轴对齐

    Row

    Row 可以在水平方向排列子 Widget。其定义如下:

    Row({
      //......
      MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
      MainAxisSize mainAxisSize = MainAxisSize.max,
      CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
      TextDirection? textDirection,
      VerticalDirection verticalDirection = VerticalDirection.down,
      TextBaseline textBaseline = TextBaseline.alphabetic,
      List<Widget> children = const <Widget>[],
    })
    
    • textDirection :水平方向组件的布局顺序,默认为系统当前 Locale 环境的文本方向(中文,英语都是左往右,而阿拉伯是右往左)

    • mainAxisSize:表示 Row 在主轴(水平)占用的空间,如 MainAxisSize.max 表示尽可能多的占用水平方向的空间,此时无论子 Widget 占用多少空间,Row 的宽度始终等于水平方向的最大宽度; MainAxisSize.min 表示尽可能的少占用水平空间,当子 Widget 没有占满水平剩余空间,则 Row 的实际宽度等于所有的子组件占用的水平空间。

      其实就相当于 Android 中的 match_parentwarp_parent

    • mainAxisAlignment:表示子组件在 Row 所占水平空间的对齐方式,如果 mainAxisSize 值为 min,则此属性毫无意义,对应的值有 start ,center,end 等。

      需要注意的是,textDirectionmainAxisAlignment 的参考系。例如 textDirectiontextDirection.ltr 时,则 MainAxisAlignment.start 表示左对齐,如果为 rtl 则,start 表示右对齐

    • crossAxisAlignment:表示子组件在纵轴的对齐方式,他的值也是 start,center,end 。只不过参考系是 verticalDirection 的值,具体的和上面的差不多,只是方向变了

    • children:子组件数组

    栗子

    class RowTest extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("线性布局 Row,Column"),
          ),
          body:  Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [Text("Hello word"), Text("345")],
              ),
              Row(
                mainAxisSize: MainAxisSize.min,
                mainAxisAlignment: MainAxisAlignment.center,
                children: [Text("Hello word"), Text("345")],
              ),
              Row(
                mainAxisSize: MainAxisSize.max,
                mainAxisAlignment: MainAxisAlignment.end,
                children: [Text("Hello word"), Text("345")],
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.end,
                textDirection: TextDirection.rtl,
                children: [Text("Hello word"), Text("345")],
              ),
              Row(
                crossAxisAlignment: CrossAxisAlignment.start,
                verticalDirection: VerticalDirection.up,
                children: [
                  Text(
                    "Hello word",
                    style: TextStyle(fontSize: 30),
                  ),
                  Text("345")
                ],
              )
            ],
          )
        );
      }
    }
    

    Flutter | 布局组件

    Column

    Column 可以在垂直方向排列其子组件,参数和 Row 一样,只不过排列的方式是垂直的,主轴和纵轴相反。

    栗子

    class ColumnTest extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("线性布局 Row,Column"),
          ),
          body: Column(
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [Text("Hi"), Text("World")],
          ),
        );
      }
    }
    

    Flutter | 布局组件

    由于有指定 主轴的 size,所以默认为 max。则这个 Column 会占用尽可能多的空间,这个栗子中为屏幕的高度

    crossAxisAlignment 为 center,表示在纵轴上居中对齐。Colum 的宽度取决于其子 Widget 中宽度最大的 Widget,所以 hi 会被显示在 world 的中间部分

    如何让 hi 和 world 在屏幕中间对齐呢,有如下两种办法:

    class ColumnTest extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("线性布局 Row,Column"),
          ),
          body: ConstrainedBox(
            constraints: BoxConstraints(minWidth: double.infinity),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.center,
              mainAxisAlignment: MainAxisAlignment.center,
              children: [Text("Hi"), Text("World")],
            ),
          ),
        );
      }
    }
    

    特殊情况

    如果 Row 嵌套 Row ,或者 Column 嵌套 Column,那么之后最外面的 Row/Column 会占用尽可能大的空间,

    class ColumnTest extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
              title: Text("线性布局 Row,Column"),
            ),
            body: Container(
              color: Colors.green,
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  mainAxisSize: MainAxisSize.max, //有效,外层Colum高度为整个屏幕
                  children: <Widget>[
                    Container(
                      color: Colors.red,
                      child: Column(
                        mainAxisSize: MainAxisSize.max, //无效,内层Colum高度为实际高度
                        children: <Widget>[
                          Text("hello world "),
                          Text("I am Jack "),
                        ],
                      ),
                    )
                  ],
                ),
              ),
            ));
      }
    }
    
    Flutter | 布局组件

    这种情况可以使用 Expanded 组件

    children: <Widget>[
     Expanded(
       child:  Container(
         color: Colors.red,
         child: Column(
           mainAxisSize: MainAxisSize.max, //无效,内层Colum高度为实际高度
           children: <Widget>[
             Text("hello world "),
             Text("I am Jack "),
           ],
         ),
       ),
     )
    ]
    
    Flutter | 布局组件

    弹性布局 Flex

    弹性布局允许子组件按照一定比例来分配父容器空间。Flutter 中弹性布局主要通过 Flex 和 Expanded 来配合实现

    Flex 组件可以沿着水平或者垂直方向排列子组件,如果知道主轴方向,使用 Row 或者 Column 会更方便一些。Row 和 Column 都继承子 Flex,参数也都基本相同,所以能使用 Flex 的地方基本上都可以使用 Row 或者 Column。

    Flex 可以和 Expanded 组件配合实现弹性布局,大多数参数基本都和线性布局一样,这里不做介绍,定义如下

    Flex({
      Key? key,
      required this.direction,
      List<Widget> children = const <Widget>[],
    }) 
    
    • direction:弹性布局的方向,Row 默认为 水平方向,Column 默认为垂直方向

    Flex 继承自 MultiChildRenderObjectWidget ,对应的 RenderObject 为 RenderFlex,RenderFlex 中实现了其布局算法

    Expanded

    可以按比例 扩伸 Row,Column 和 Flex 子组件所占用的空间

    const Expanded({
      int flex = 1, 
      @required Widget child,
    })
    
    • flex:弹性系数,如果为 0 或者 null,则没有弹性,既不会扩展占用的空间。如果大于 0,所有的 Expanded 按照 flex 的比例来分隔主轴的全部空闲空间

    栗子

    class FlexTest extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("弹性布局Flex"),
          ),
          body: Column(
            children: [
              Flex(
                direction: Axis.horizontal,
                children: [
                  Expanded(
                    flex: 1,
                    child: Container(
                      height: 30,
                      color: Colors.red,
                    ),
                  ),
                  Expanded(
                    flex: 2,
                    child: Container(
                      height: 30,
                      color: Colors.blue,
                    ),
                  )
                ],
              ),
              Padding(
                padding: const EdgeInsets.only(top: 20),
                child: SizedBox(
                  height: 100,
                  child: Flex(
                    direction: Axis.vertical,
                    children: [
                      Expanded(
                        flex: 2,
                        child: Container(
                          height: 30,
                          color: Colors.yellow,
                        ),
                      ),
                      Spacer(
                        flex: 1,
                      ),
                      Expanded(
                        flex: 1,
                        child: Container(
                          height: 30,
                          color: Colors.green,
                        ),
                      )
                    ],
                  ),
                ),
              )
            ],
          ),
        );
      }
    }
    

    效果如下所示:

    Flutter | 布局组件

    栗子中的 Spacer 的功能是占用指定比例的空间,实际上它只是 Expanded 的一个包装类


    流式布局 Wrap ,Flow

    在使用 Row 和 Column 时,如果子 Widget 超出 屏幕范围,则会报溢出错误,如:

    class WrapAndFlowTest extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("WarpAndFlow"),
          ),
          body: Container(
            height: 100,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.start,
              mainAxisSize: MainAxisSize.max,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [Text("345" * 100)],
            ),
          ),
        );
      }
    }
    
    Flutter | 布局组件

    可以看到,右边部分报出溢出错误。这是因为 Row 默认只有一行,如果超出屏幕,不会折行,并且会报错

    我们把超出自动折行的布局称为流式布局。Flutter 中通过 Wrap 和 Flow 来支持流式布局。

    Wrap 定义如下

    Wrap({
      ...
      this.direction = Axis.horizontal,
      this.alignment = WrapAlignment.start,
      this.spacing = 0.0,
      this.runAlignment = WrapAlignment.start,
      this.runSpacing = 0.0,
      this.crossAxisAlignment = WrapCrossAlignment.start,
      this.textDirection,
      this.verticalDirection = VerticalDirection.down,
      List<Widget> children = const <Widget>[],
    })
    

    可以看到有很多属性在 Row ,Colum 中都有,如 direction,textDirection等,这些参数意义都相同,这里不过多介绍

    • spacing:主轴方向子 Widget 的间距
    • runSpacing:纵轴方向的间距
    • runAlignment:纵轴方向的对齐方式

    例子

    class WrapAndFlowTest extends StatelessWidget {
      final List<String> _list = const [
        "爱是你我",
        "一壶老酒",
        "最炫民族风",
        "怒放的生命",
        "再见青春",
        "北京,北京"
      ];
    
      List<Widget> getMusicList() {
        /* List<Widget> widgets = new List();
        _list.forEach((element) {
          widgets.add(RaisedButton(
            child: Text(element),
            onPressed: () => print(element),
          ));
        });*/
    
        return _list
            .map((e) => RaisedButton(
                  child: Text(e),
                  onPressed: () => print(e),
                ))
            .toList();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
              title: Text("WarpAndFlow"),
            ),
            body: Padding(
              padding: EdgeInsets.all(10),
              child: Flex(
                direction: Axis.horizontal,
                children: [
                  Expanded(
                    flex: 1,
                    child: Wrap(
                      spacing: 25,
                      runSpacing: 4,
                      alignment: WrapAlignment.center,
                      children: getMusicList(),
                    ),
                  )
                ],
              ),
            ));
      }
    }
    
    Flutter | 布局组件

    Flow

    Flow 主要用于以下需要高度自定义布局或者性能要求较高(如动画中) 的场景,

    Flow 有如下优点

    • 性能好:Flow 是一个队子组件尺寸以及位置调整非常高效的控件。Flow 用转换矩阵对子组件进行位置调整的时候进行了优化:在 Flutter 定位过后,如果子组件尺寸发生了变化,在 FlowDelegate 中的 paintChildren() 方法中调用 context.paintChild 进行重绘,而 contextPaintChild 进行重绘的时候使用了转换矩阵,并没有实际调整组件的位置

    • 灵活:由于需要自定实现 FlowDelegate 的 parintChildren() 方法,所以我们需要手动计算每一个组件的位置,因此,可以自定义布局策略

    缺点

    • 使用复杂
    • 不能自适应子组件大小,必须通过指定父容器大小或者实现 TestFlowDelegate 的 getSize 返回固定大小

    示例

    class TestFlowDelegate extends FlowDelegate {
    
      EdgeInsets margin = EdgeInsets.zero;
    
      TestFlowDelegate({this.margin});
    
      @override
      void paintChildren(FlowPaintingContext context) {
        var x = margin.left;
        var y = margin.top;
        //计算每一个自 widget 的位置
        for (int i = 0; i < context.childCount; i++) {
          //获取宽度
          var width = context
              .getChildSize(i)
              .width + x + margin.right;
          //是否需要换行
          print('$width ---- ${context.size.width}');
          if (width < context.size.width) {
            //绘制第一个
            context.paintChild(i, transform: Matrix4.translationValues(x, y, 0));
            x = width + margin.left;
          } else {
            //绘制后面的
            x = margin.left;
            y += context
                .getChildSize(i)
                .height + margin.top + margin.bottom;
            //绘制子widget(有优化)  
            context.paintChild(i, transform: Matrix4.translationValues(x, y, 0));
            x += context
                .getChildSize(i)
                .width + margin.left + margin.right;
          }
        }
      }
    
      @override
      bool shouldRepaint(covariant FlowDelegate oldDelegate) {
        return oldDelegate != this;
      }
    
      @override
      Size getSize(BoxConstraints constraints) {
        return Size(double.infinity, 200);
      }
    }
    
    class WrapAndFlowTest extends StatelessWidget {
      final List<String> _list = const [
        "爱是你我",
        "一壶老酒",
        "最炫民族风",
        "怒放的生命",
        "再见青春",
        "北京,北京"
      ];
    
      List<Widget> getMusicList() {
        return _list
            .map((e) => Container(
                  width: 140,
                  height: 40,
                  child: RaisedButton(
                    child: Text(e),
                    onPressed: () => print(e),
                  ),
                ))
            .toList();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
              title: Text("WarpAndFlow"),
            ),
            body: Padding(
              padding: EdgeInsets.all(10),
              child: Flow(
                delegate: TestFlowDelegate(margin: EdgeInsets.all(10)),
                children: getMusicList(),
              ),
            ));
      }
    }
    
    Flutter | 布局组件

    可以看到主要的任务就是实现 paintChildren,他的主要任务就是确定每个子 Widget 的位置,由于 Flow 不能自适应 Widget 的大小,所以在 getSize 中返回一个固定大小来指定 Flow 的大小


    层叠布局 Stack,Positioned

    层叠布局和 Android 中的 FrameLayout 布局是相似的,子组件可以通过父容器的四个角的位置来确定自身的位置。

    绝对定位允许子组件堆叠起来(按照代码中声明的顺序)。Flutter 中使用 Stack 和 Positioned 这两个 组件来配合实现决定定位。

    Stack 允许组件堆叠,而 Positioned 用于根据 Stack 的四个角来确定子组件的位置

    Stack

    Stack({
      this.alignment = AlignmentDirectional.topStart,
      this.textDirection,
      this.fit = StackFit.loose,
      this.overflow = Overflow.clip,
      List<Widget> children = const <Widget>[],
    })
    
    • alignment:此参数决定如何去对齐没有定位**(没有使用 Positioned)** 或部分定位的子组件。

      部分定位指的是没有在某一个轴上定位leftright 为横轴,topbottom 为纵轴,只要包含某个轴上的一个定位属性就算在该轴上有定位

    • textDirection:和 RowColumn 中的 textDirection 功能一样,都用于确定 alignment 对齐的参考系,即 textDirection 值为 ltr,则 alignment 代表左,end 为右。如果 textDirecion为 rtl,start 则为 有,end 为左

    • fit:此参数用于确定没有定位的子组件如何使用 Stack 的大小。StackFit.loose 表示使用子组件的大小, expand 表示扩伸到 Stack 的大小

    • overflow:此属性决定如何显示超出 Stack 显示空间的子组件;值为 Overflow.clip 时,超出部分会被剪裁(隐藏),只为 Overflow.visible 则不会

    Positioned

    const Positioned({
      Key key,
      this.left, 
      this.top,
      this.right,
      this.bottom,
      this.width,
      this.height,
      @required Widget child,
    })
    

    lefttoprightbottom 分别代表 tack 四个边的距离,widget 耦合 height 用于指定需要定位元素的宽度和高度。

    注意,Positioned 的 widget,height 是用于配合 left,top ,right,bottom 来定位组件,举个例子,在 水平方向是,只能指定 left,right,width 三个属性的两个,如指定 left 和 widget 后,right 会自动推算出 (left+widget),如果同时指定三个属性则会报错,垂直方向同理

    栗子:

    class StackAndPositionedTest extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
              title: Text("StackAndPositioned"),
            ),
            ///通过 ConstrainedBox 来确保 Stack 占满屏幕
            body: ConstrainedBox(
              constraints: BoxConstraints.expand(),
              child: Stack(
                alignment: Alignment.center,
                children: [
                  Container(
                    child: Text(
                      "hello world",
                      style: TextStyle(color: Colors.white),
                    ),
                    color: Colors.red,
                  ),
                  Positioned(
                    left: 18,
                    child: Text("I am 345"),
                  ),
                  Positioned(
                    top: 18,
                    child: Text("your friend"),
                  )
                ],
              ),
            ));
      }
    }
    
    Flutter | 布局组件

    代码中第一个组件 hellow world 没有使用 Positioned 组件,所以 会受到 Aligment.center 的约束,所以他在图中是居中显示的。

    第二个子组件 I am 345 只指定了 水平方位 left,属于部分定位,即垂直没有定位,那么他在垂直方向上会按照 aligment 进行对齐,即为垂直居中

    第三个 your friend 和 第二个一样,只不过是制定了 垂直 top,没有水平定位,则水平方向居中

    修改代码如下:

     Stack(
      alignment: Alignment.center,
      fit: StackFit.expand,
      children: [
        Positioned(
          left: 18,
          child: Text("I am 345"),
        ),
        Container(
          child: Text(
            "hello world",
            style: TextStyle(color: Colors.white),
          ),
          color: Colors.red,
        ),
        Positioned(
          top: 18,
          child: Text("your friend"),
        )
      ],
    )
    

    上面使用了 fit 属性,并且是 expand,表示没有使用定位的子组件会扩伸到 Stack 的大小

    由于第二个子组件的宽高和 Stack 一样大,所以就会导致第一个组件被覆盖

    第三个组件在最上层,正常显示


    对齐与相对定位 Align

    通过 StackPositioned 可以指定一个或多个子组件相对于父元素的各个边进行精确偏移,并且可以重叠,

    但是如果只想简单调整一个子组件在父元素中的位置的话,使用 Align 组件会更简单一些

    Align

    Align({
      Key key,
      this.alignment = Alignment.center,
      this.widthFactor,
      this.heightFactor,
      Widget child,
    })
    

    Align 组件可以调整子组件的位置,并根据子组件的宽高来确定自身的宽高

    • aligment:需要一个 AlignmentGeometry 类型的值,表示子组件在父组件中的起始位置,AlignmentGeometry 是一个抽象类,常用的有两个子类 Aligment 和 FractionalOffset
    • widthFactor 和 heightFactor 用于确定 Align 自身的宽高属性;他们是两个缩放因子,分别会乘以子组件的宽高,最终的结果就是 Align 的宽高,如果为 null,则组件的宽高会占用尽可能多的空间

    栗子

    class AlignTest extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("Align"),
          ),
          body: Container(
            height: 120,
            width: 120,
            color: Colors.blue[50],
            child: Align(
              alignment: Alignment.topRight,
              child: FlutterLogo(
                size: 60,
              ),
            ),
          ),
        );
      }
    }
    
    Flutter | 布局组件

    FlutterLogo 是 Fluter sdk 的一个组件,内容就是 Flutter 的商品

    在 Container 中 制定了 宽高为 120,如果不指定 Container 的宽高,同时指定 widthFactor 和 heightFactor 为 2也可以达到相同的效果

    Alignment.topRight 表示子组件的位置为 顶部右上角,具体的值为 Aligment(1,-1)

    Aligment

    Aligment 继承自 AligmentGemetry,表示矩形内的一个点,他有两个属性 x,y,分别代表水平和垂直的偏移,定义如下:

    Alignment(this.x, this.y)
    

    Aligment 会以矩形的中心点作为坐标的原点(Aligemtn(0.0,0.0)), x,y 的值从 -1 到 1, 分别代表矩形从左到右的距离 和 顶部 到底边的距离。因此 2 个水平/垂直 单位则等于 矩形的宽/高

    如 Aligment(-1,-1) 代表左侧顶点,1,1代表 右侧底部终点;1,-1,则是右侧顶点,即为 Aligment.topRight。 为了使用方便,矩形的原点,四个顶点都已经在 Aligment 中定义了静态常量。

    Aligment 可以通过其 坐标转换公式将其坐标转为子元素的具体偏移坐标:

    偏移量 = (Aligment.x * childWidth/2 + childWidth /2 , Aligment.y * chilHeight/2 + childHeight /2)
    

    其中 childWidth 为子元素的宽度,childHeight 为子元素的高度

    回过头在看一下上面的栗子,我们将 Aligment(1,-1) 带入上面的公式,可知 FlutterLogo 的偏移坐标正是 (60,0)

    修改上面的栗子如下:

    class AlignTest extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("Align"),
          ),
          body: Container(
            // height: 120,
            // width: 120,
            color: Colors.blue[50],
            // child: Align(
            //   alignment: Alignment.topRight,
            //   child: FlutterLogo(
            //     size: 60,
            //   ),
            // ),
            child: Align(
              // 2x60/2+60/2 ,0x60/2 + 60/2
              //=90           =30
              alignment: Alignment(2, 0),
              widthFactor: 2,
              heightFactor: 2,
              child: FlutterLogo(
                size: 60,
              ),
            ),
          ),
        );
      }
    }
    

    根据代码中注释的计算,可以得出 x偏移 90,y 偏移30,结果如下:

    Flutter | 布局组件

    FractionalOffset

    FractionalOffset 继承自 Aligment , 他和 Aligment 的唯一区别就是坐标点不同

    FactionalOffset 的坐标原点为矩形左侧顶点,这和系统布局一直,所以理解比较容易一点。他的坐标转换公式为:

    偏移=(FractionalOffset.x*childWidth , FractionalOffset * childHeight)
    

    小栗子:

    body: Container(
      height: 120,
      width: 120,
      color: Colors.blue[50],
      child: Align(
        alignment: FractionalOffset(0.2, 0.6),
        // 0.2 *60 , 0.6 * 60
        child: FlutterLogo(
          size: 60,
        ),
      ),
    )
    

    带入公式,偏移量为 (12,60),结果如下:

    Flutter | 布局组件

    Align 和 Stack 对比

    Align 和 Stack/Positioned 都可以用于指定子元素相对于父元素的偏移,他们主要区别如下

    • 定位参考系统不同
      • Stack/Positioned 定位参考的是父容器的四个顶点
      • Align 则需要先通过参数 alignment 来确定具体的坐标,最终的偏移是通过 aligment 的公式计算出的
    • Stack 可以有多个子元素,并且可以堆叠,而 Align 只有一个元素,不存在堆叠

    Center 组件

    Center 组件用来居中子元素,在之前我们已经使用过他了,下面来介绍一下他,Center 定义如下

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

    Center 继承子 Align,它相比 Align 只少了一个 aligment 参数;

    由于 Align 中 alignment 值为 center,所以,Center 组件的对齐方式为 Aglinment.center 了

    widthFactory 或者 heightFactory 的长度为 null 时表示尽可能占用更多的空间,这点需要特别注意一下

    总结

    • Row / Column

      沿水平或者垂直方向排列子组件

    • Flex

      弹性布局,个人感觉有点类似于 Android 线性布局中的 layout_weight 属性,子组件通过 flex 表示当前组件需要占总大小的多少。

    • 流式布局 Wrap/Flow

      Wrap 自动排列,可以指定 对齐属性等,超过宽度自动折行

      Flow 高度自定义的 Widget,需要手动计算折行位置,排列等,比较适用于高度的自定义

    • 层叠布局 Stack,Positioned

      Stack 层叠布局,可以有多个子组件,子组件 Postioned 用于可根据 Stack 的四个角来确定当前组件的位置,没有使用 Positioned ,则会按照 aligment 进行排列

    • Align

      只能有一个子组件,通过 Aligment / FractionalOffset 进行定位

    • Aligment / FractionalOffset

      两者都代表这偏移量 ,FractionalOffset 继承自 Aligment

      Aligment 的原点为 Widget 的中心,即中心点为 Aligment(0,0),具体的偏移可根据公式计算

      FractionalOffset 的原点为Widget 的左上角顶点,即FractionalOffset(0,0),和系统布局一样。具体偏移需要公式计算

    • Center

      继承自 Align,相比与 Align 少了 aligment 参数,该参数默认为居中



    起源地下载网 » Flutter | 布局组件

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元