跨平台

Flutter了解之布局组件

2020-10-19  本文已影响0人  平安喜乐698
目录
  1. 线性布局(Row和Column)
  2. 弹性布局(Flex)
  3. 流式布局
  4. 层叠布局 Stack、Positioned(实现绝对定位)、Align(相对定位)

Flutter官方并没有对Widget进行官方分类,对其分类主要是为了对Widget进行功能区分。

Widget分类(根据是否需要包含子节点)

布局类组件可以包含一个或多个子组件,不同的布局类组件对子组件布局方式不同。
布局类组件就是指直接或间接继承(包含)MultiChildRenderObjectWidget的Widget,有一个children属性用于接收子Widget。

Widget分类(根据是否需要包含子节点)
继承关系: Widget > RenderObjectWidget > (Leaf/SingleChild/MultiChild)RenderObjectWidget 

RenderObjectWidget类中定义了创建、更新RenderObject的方法,子类必须实现他们。RenderObject是最终布局、渲染UI界面的对象,对于布局类组件来说,其布局算法都是通过对应的RenderObject对象来实现的。比如Stack(层叠布局)对应的RenderObject对象就是RenderStack,而层叠布局的实现就在RenderStack中。

StatelessWidget和StatefulWidget就是两个用于组合Widget的基类,它们本身并不关联最终的渲染对象(RenderObjectWidget)。
Flutter中的很多Widget是直接继承自StatelessWidget或StatefulWidget,然后在build()方法中构建真正的RenderObjectWidget,如Text,它其实是继承自StatelessWidget,然后在build()方法中通过RichText来构建其子树,而RichText才是继承自MultiChildRenderObjectWidget。
为了方便叙述,也可以直接说Text属于MultiChildRenderObjectWidget。

1. 线性布局(Row和Column,都继承自Flex,属于基础组件库)

子组件沿水平或垂直方向排布。
有主轴和纵轴之分,如果布局是沿水平方向,主轴就是水平方向,纵轴即垂直方向;如果布局沿垂直方向,主轴就是垂直方向,纵轴就是水平方向。有两个定义对齐方式的枚举类MainAxisAlignment和CrossAxisAlignment,分别代表主轴对齐和纵轴对齐。

类似于Android中的LinearLayout控件。

  1. Row

在水平方向排列子widget

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

说明:
1. textDirection
水平方向子组件的布局顺序(从左往右、从右往左)
默认为系统当前Locale环境的文本方向(如中文、英语都是从左往右,而阿拉伯语是从右往左)。

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

3. mainAxisAlignment
水平方向对齐方式。textDirection是mainAxisAlignment的参考系。
如果mainAxisSize值为MainAxisSize.min,则此属性无意义,因为子组件的宽度等于Row的宽度。只有当mainAxisSize的值为MainAxisSize.max时,此属性才有意义。
MainAxisAlignment.start表示沿textDirection的初始方向对齐,如textDirection取值为TextDirection.ltr时,则MainAxisAlignment.start表示左对齐,textDirection取值为TextDirection.rtl时表示从右对齐。而MainAxisAlignment.end和MainAxisAlignment.start正好相反;MainAxisAlignment.center表示居中对齐。MainAxisAlignment.spaceEvenly:均匀分布,MainAxisAlignment.spaceBetween:2边没距离,元素间距相同。MainAxisAlignment.spaceAround:元素间距是两边间距的2倍。MainAxisAlignment.spaceEvenly:两边间距是元素间距的2倍。

4. verticalDirection
垂直方向子组件的顺序(从上到下、从下到上)
默认是VerticalDirection.down 从上到下。

5. crossAxisAlignment:
纵轴方向的对齐方式。它的取值和MainAxisAlignment一样(包含start、end、 center三个值),不同的是crossAxisAlignment的参考系是verticalDirection,即verticalDirection值为VerticalDirection.down时crossAxisAlignment.start指顶部对齐,verticalDirection值为VerticalDirection.up时,crossAxisAlignment.start指底部对齐;而crossAxisAlignment.end和crossAxisAlignment.start正好相反。
Row的高度等于子组件中最高的子元素高度。

6. children :
  子组件数组。

Column(
  //测试Row对齐方式,排除Column默认居中对齐的干扰
  crossAxisAlignment: CrossAxisAlignment.start,
  children: <Widget>[
    Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Text(" hello world "),
        Text(" I am Jack "),
      ],
    ),
    Row(
      mainAxisSize: MainAxisSize.min,
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Text(" hello world "),
        Text(" I am Jack "),
      ],
    ),
    Row(
      mainAxisAlignment: MainAxisAlignment.end,
      textDirection: TextDirection.rtl,
      children: <Widget>[
        Text(" hello world "),
        Text(" I am Jack "),
      ],
    ),
    Row(
      crossAxisAlignment: CrossAxisAlignment.start,  
      verticalDirection: VerticalDirection.up,
      children: <Widget>[
        Text(" hello world ", style: TextStyle(fontSize: 30.0),),
        Text(" I am Jack "),
      ],
    ),
  ],
);

说明:
  第一个Row很简单,默认为居中对齐;
  第二个Row,由于mainAxisSize值为MainAxisSize.min,Row的宽度等于两个Text的宽度和,所以对齐是无意义的,所以会从左往右显示;
  第三个Row设置textDirection值为TextDirection.rtl,所以子组件会从右向左的顺序排列,而此时MainAxisAlignment.end表示左对齐,所以最终显示结果就是图中第三行的样子;
  第四个Row测试的是纵轴的对齐方式,由于两个子Text字体不一样,所以其高度也不同,我们指定了verticalDirection值为VerticalDirection.up,即从低向顶排列,而此时crossAxisAlignment值为CrossAxisAlignment.start表示底对齐。
  1. Column

在垂直方向排列其子组件。
参数和Row一样,不同的是布局方向为垂直,主轴纵轴正好相反。

import 'package:flutter/material.dart';

class CenterColumnRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.center,
      children: <Widget>[
        Text("hi"),
        Text("world"),
      ],
    );
  }
}

说明:
1. 由于没有指定Column的mainAxisSize,所以使用默认值MainAxisSize.max,则Column会在垂直方向占用尽可能多的空间,此例中为屏幕高度。
2. 由于指定了 crossAxisAlignment 属性为CrossAxisAlignment.center,那么子项在Column纵轴方向(此时为水平方向)会居中对齐。注意,在水平方向对齐是有边界的,总宽度为Column占用空间的实际宽度,而实际的宽度取决于子项中宽度最大的Widget。在本例中,Column有两个子Widget,而显示“world”的Text宽度最大,所以Column的实际宽度则为Text("world") 的宽度,所以居中对齐后Text("hi")会显示在Text("world")的中间部分。
实际上,Row和Column都只会在主轴方向占用尽可能大的空间,而纵轴的长度则取决于他们最大子元素的长度。如果想让本例中的两个文本控件在整个手机屏幕中间对齐,我们有两种方法:

1. 将Column的宽度指定为屏幕宽度;这很简单,可以通过ConstrainedBox或SizedBox来强制更改宽度限制
ConstrainedBox(
  constraints: BoxConstraints(minWidth: double.infinity), 
  child: Column(
    crossAxisAlignment: CrossAxisAlignment.center,
    children: <Widget>[
      Text("hi"),
      Text("world"),
    ],
  ),
);
将minWidth设为double.infinity,可以使宽度占用尽可能多的空间。


2. 使用Center Widget

特殊情况

如果Row里面嵌套Row,或者Column里面再嵌套Column,那么只有最外面的Row或Column会占用尽可能大的空间,里面Row或Column所占用的空间为实际大小。

如果要让里面的Column占满外部Column,可以使用Expanded 组件:

Expanded( 
  child: Container(
    color: Colors.red,
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center, //垂直方向居中对齐
      children: <Widget>[
        Text("hello world "),
        Text("I am Jack "),
      ],
    ),
  ),
)

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 "),
            ],
          ),
        )
      ],
    ),
  ),
);

2. 弹性布局(Flex)

子组件按照一定比例来分配父容器空间,通过Flex和Expanded来配合实现。

类似H5中的弹性盒子布局,Android中的FlexboxLayout等。

Flex

Flex组件可以沿着水平或垂直方向排列子组件。

如果知道主轴方向,使用Row或Column会方便一些,因为Row和Column都继承自Flex,参数基本相同,所以能使用Flex的地方基本上都可以使用Row或Column。

Flex继承自MultiChildRenderObjectWidget,对应的RenderObject为RenderFlex,RenderFlex中实现了其布局算法。
Flex可以和Expanded组件配合实现弹性布局。

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

Expanded 自动扩展

按比例“扩伸” Row、Column和Flex子组件所占用的空间。

const Expanded({
  int flex = 1, 
  @required Widget child,
})

说明:
1. flex参数为弹性系数,如果为0或null,则child是没有弹性的,即不会被扩伸占用的空间。如果大于0,所有的Expanded按照其flex的比例来分割主轴的全部空闲空间。

如果有SizeBox,会减去SizeBox的宽/高度,再按比例。

左侧hello文本固定宽度,右侧world文本(屏幕款 - hello文本宽)

Row(
  children:<Widget>[
    Text('hello'),
    Expanded(
      flex:1,
      child:Text('world'),
    ),
  ];
);

class FlexLayoutTestRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        // Flex的两个子widget按1:2来占据水平空间  
        Flex(
          direction: Axis.horizontal,
          children: <Widget>[
            Expanded(
              flex: 1,
              child: Container(
                height: 30.0,
                color: Colors.red,
              ),
            ),
            Expanded(
              flex: 2,
              child: Container(
                height: 30.0,
                color: Colors.green,
              ),
            ),
          ],
        ),
        Padding(
          padding: const EdgeInsets.only(top: 20.0),
          child: SizedBox(
            height: 100.0,
            // Flex的三个子widget,在垂直方向按2:1:1来占用100像素的空间  
            child: Flex(
              direction: Axis.vertical,
              children: <Widget>[
                Expanded(
                  flex: 2,
                  child: Container(
                    height: 30.0,
                    color: Colors.red,
                  ),
                ),
                Spacer(
                  flex: 1,
                ),
                Expanded(
                  flex: 1,
                  child: Container(
                    height: 30.0,
                    color: Colors.green,
                  ),
                ),
              ],
            ),
          ),
        ),
      ],
    );
  }
}


示例中的Spacer的功能是占用指定比例的空间,实际上它只是Expanded的一个包装类,Spacer的源码如下:

class Spacer extends StatelessWidget {
  const Spacer({Key key, this.flex = 1})
    : assert(flex != null),
      assert(flex > 0),
      super(key: key);
  final int flex;
  @override
  Widget build(BuildContext context) {
    return Expanded(
      flex: flex,
      child: const SizedBox.shrink(),
    );
  }
}

3. 流式布局

Row和Colum时,如果子widget超出屏幕范围,则会报溢出错误。

把超出屏幕显示范围会自动折行的布局称为流式布局。

如:
Row(
  children: <Widget>[
    Text("xxx"*100)
  ],
);
右边溢出部分报错。这是因为Row默认只有一行,如果超出屏幕不会折行。

通过Wrap和Flow来支持流式布局,将上例中的Row换成Wrap后溢出部分则会自动折行
溢出报错

Wrap

Wrap的很多属性在Row(包括Flex和Column)中也有,可以认为Wrap和Flex(包括Row和Column)除了超出显示范围后Wrap会折行外,其它行为基本相同。

Wrap({
  ...
  this.direction = Axis.horizontal,
  this.alignment = WrapAlignment.start,
  this.spacing = 0.0,    // 主轴方向子widget的间距
  this.runAlignment = WrapAlignment.start,  // 纵轴方向的对齐方式
  this.runSpacing = 0.0,  // 纵轴方向的间距
  this.crossAxisAlignment = WrapCrossAlignment.start,
  this.textDirection,
  this.verticalDirection = VerticalDirection.down,
  List<Widget> children = const <Widget>[],
})

Wrap(
  spacing: 8.0, // 主轴(水平)方向间距
  runSpacing: 4.0, // 纵轴(垂直)方向间距
  alignment: WrapAlignment.center, //沿主轴方向居中
  children: <Widget>[
    new Chip(
      avatar: new CircleAvatar(backgroundColor: Colors.blue, child: Text('A')),
      label: new Text('Hamilton'),
    ),
    new Chip(
      avatar: new CircleAvatar(backgroundColor: Colors.blue, child: Text('M')),
      label: new Text('Lafayette'),
    ),
    new Chip(
      avatar: new CircleAvatar(backgroundColor: Colors.blue, child: Text('H')),
      label: new Text('Mulligan'),
    ),
    new Chip(
      avatar: new CircleAvatar(backgroundColor: Colors.blue, child: Text('J')),
      label: new Text('Laurens'),
    ),
  ],
)

Flow

一般很少会使用Flow,因为其过于复杂,需要自己实现子widget的位置转换,在很多场景下首先要考虑的是Wrap是否满足需求。Flow主要用于一些需要自定义布局策略或性能要求较高(如动画中)的场景。

优点:
    1. 性能好;Flow是一个对子组件尺寸以及位置调整非常高效的控件,Flow用转换矩阵在对子组件进行位置调整的时候进行了优化:在Flow定位过后,如果子组件的尺寸或者位置发生了变化,在FlowDelegate中的paintChildren()方法中调用context.paintChild 进行重绘,而context.paintChild在重绘时使用了转换矩阵,并没有实际调整组件位置。
    2. 灵活;由于需要自己实现FlowDelegate的paintChildren()方法,所以要自己计算每一个组件的位置,因此,可以自定义布局策略。

缺点:
    1. 使用复杂。
    2. 不能自适应子组件大小,必须通过指定父容器大小或实现TestFlowDelegate的getSize返回固定大小。

Flow(
  delegate: TestFlowDelegate(margin: EdgeInsets.all(10.0)),
  children: <Widget>[
    new Container(width: 80.0, height:80.0, color: Colors.red,),
    new Container(width: 80.0, height:80.0, color: Colors.green,),
    new Container(width: 80.0, height:80.0, color: Colors.blue,),
    new Container(width: 80.0, height:80.0,  color: Colors.yellow,),
    new Container(width: 80.0, height:80.0, color: Colors.brown,),
    new Container(width: 80.0, height:80.0,  color: Colors.purple,),
  ],
)
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 w = context.getChildSize(i).width + x + margin.right;
      if (w < context.size.width) {
        context.paintChild(i,
            transform: new Matrix4.translationValues(
                x, y, 0.0));
        x = w + margin.left;
      } else {
        x = margin.left;
        y += context.getChildSize(i).height + margin.top + margin.bottom;
        //绘制子widget(有优化)  
        context.paintChild(i,
            transform: new Matrix4.translationValues(
                x, y, 0.0));
         x += context.getChildSize(i).width + margin.left + margin.right;
      }
    }
  }

  @override
  getSize(BoxConstraints constraints){
    //指定Flow的大小  
    return Size(double.infinity,200.0);
  }

  @override
  bool shouldRepaint(FlowDelegate oldDelegate) {
    return oldDelegate != this;
  }
}

主要的任务就是实现paintChildren,它的主要任务是确定每个子widget位置。由于Flow不能自适应子widget的大小,我们通过在getSize返回一个固定大小来指定Flow的大小。

4. 层叠布局 Stack、Positioned(绝对定位)、Align(相对定位)

Stack允许子组件堆叠。
Positioned用于根据Stack的四个角来确定子组件的位置,类似Web中的绝对定位(绝对定位允许子组件堆叠起来)、Android中的Frame布局。

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

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

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    var stack = new Stack(
      alignment: const Alignment(0.6, 0.6),
      children: [
        new CircleAvatar(
          backgroundImage: new AssetImage('images/pic.jpg'),
          radius: 100.0,
        ),
        new Container(
          decoration: new BoxDecoration(
            color: Colors.black45,
          ),
          child: new Text(
            'Mia B',
            style: new TextStyle(
              fontSize: 20.0,
              fontWeight: FontWeight.bold,
              color: Colors.white,
            ),
          ),
        ),
      ],
    );
    // ...
  }
}
  1. Positioned
const Positioned({
  Key key,
  this.left, 
  this.top,
  this.right,
  this.bottom,
  this.width,
  this.height,
  @required Widget child,
})

说明:
1. left、top 、right、 bottom
分别代表离Stack左、上、右、底四边的距离。
2. width和height
用于指定需要定位元素的宽度和高度。
注意,Positioned的width、height 和其它地方的意义稍微有点区别,此处用于配合left、top 、right、 bottom来定位组件,举个例子,在水平方向时,只能指定left、right、width三个属性中的两个,如指定left和width后,right会自动算出(left+width),如果同时指定三个属性则会报错,垂直方向同理。

//通过ConstrainedBox来确保Stack占满屏幕
ConstrainedBox(
  constraints: BoxConstraints.expand(),
  child: Stack(
    alignment:Alignment.center , //指定未定位或部分定位widget的对齐方式
    children: <Widget>[
      Container(child: Text("Hello world",style: TextStyle(color: Colors.white)),
        color: Colors.red,
      ),
      Positioned(
        left: 18.0,
        child: Text("I am Jack"),
      ),
      Positioned(
        top: 18.0,
        child: Text("Your friend"),
      )        
    ],
  ),
);

第一个子文本组件Text("Hello world")没有指定定位,并且alignment值为Alignment.center,所以它会居中显示。
第二个子文本组件Text("I am Jack")只指定了水平方向的定位(left),所以属于部分定位,即垂直方向上没有定位,那么它在垂直方向的对齐方式则会按照alignment指定的对齐方式对齐,即垂直方向居中。
第三个子文本组件Text("Your friend"),和第二个Text原理一样,只不过是水平方向没有定位,则水平方向居中。
给上例中的Stack指定一个fit属性,然后将三个子文本组件的顺序调整一下:

Stack(
  alignment:Alignment.center ,
  fit: StackFit.expand, //未定位widget占满Stack整个空间
  children: <Widget>[
    Positioned(
      left: 18.0,
      child: Text("I am Jack"),
    ),
    Container(child: Text("Hello world",style: TextStyle(color: Colors.white)),
      color: Colors.red,
    ),
    Positioned(
      top: 18.0,
      child: Text("Your friend"),
    )
  ],
),
由于第二个子文本组件没有定位,所以fit属性会对它起作用,就会占满Stack。由于Stack子元素是堆叠的,所以第一个子文本组件被第二个遮住了,而第三个在最上层,所以可以正常显示。
  1. Align

Align 组件可以调整子组件在父元素中的位置,并且可以根据子组件的宽高来确定自身的的宽高。

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

说明:
1. alignment :
  需要一个AlignmentGeometry类型的值,表示子组件在父组件中的起始位置。AlignmentGeometry 是一个抽象类,它有两个常用的子类:Alignment和 FractionalOffset。
  在需要制定一些精确的偏移时应优先使用FractionalOffset,因为它的坐标原点和布局系统相同,能更容易算出实际偏移。
2. widthFactor和heightFactor
  用于确定Align 组件本身宽高的属性;它们是两个缩放因子,会分别乘以子元素的宽、高,最终的结果就是Align 组件的宽高。如果值为null,则组件的宽高将会占用尽可能多的空间。

Container(
  height: 120.0,
  width: 120.0,
  color: Colors.blue[50],
  child: Stack(
    children: <Widget>[
      Align(
        alignment: Alignment.topRight,
        child: Text('hello'),
      ),
      Align(
        alignment: Alignment(1,-0.5),
        child: Text('hello'),
      ),
    ],
  ),
)

Container(
  height: 120.0,
  width: 120.0,
  color: Colors.blue[50],
  child: Align(
    alignment: Alignment.topRight,
    child: FlutterLogo(
      size: 60,
    ),
  ),
)

FlutterLogo 是Flutter SDK提供的一个组件,内容就是Flutter的商标。在上面的例子中,我们显式指定了Container的宽、高都为120。如果我们不显式指定宽高,而通过同时指定widthFactor和heightFactor 为2也是可以达到同样的效果:
Align(
  widthFactor: 2,
  heightFactor: 2,
  alignment: Alignment.topRight,
  child: FlutterLogo(
    size: 60,
  ),
),
因为FlutterLogo的宽高为60,则Align的最终宽高都为2*60=120。
运行结果
Align和Positioned都可以用于指定子元素相对于父元素的偏移,两个主要区别:
  1. 定位参考系统不同;Stack/Positioned定位的的参考系可以是父容器矩形的四个顶点;而Align则需要先通过alignment 参数来确定坐标原点,不同的alignment会对应不同原点,最终的偏移是需要通过alignment的转换公式来计算出。
  2. Stack可以有多个子元素,并且子元素可以堆叠,而Align只能有一个子元素,不存在堆叠。

Center组件

Center继承自Align,它比Align只少了一个alignment 参数;由于Align的构造函数中alignment 值为Alignment.center,所以,可以认为Center组件其实是对齐方式确定(Alignment.center)了的Align。

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

...//省略无关代码
DecoratedBox(
  decoration: BoxDecoration(color: Colors.red),
  child: Center(
    child: Text("xxx"),
  ),
),
DecoratedBox(
  decoration: BoxDecoration(color: Colors.red),
  child: Center(
    widthFactor: 1,
    heightFactor: 1,
    child: Text("xxx"),
  ),
)
运行结果

Alignment

Alignment(this.x, this.y)

Alignment继承自AlignmentGeometry,表示矩形内的一个点,有两个属性x、y,分别表示在水平和垂直方向的偏移。
Alignment Widget会以矩形的中心点作为坐标原点,即Alignment(0.0, 0.0) 。x、y的值从-1到1分别代表矩形左边到右边的距离和顶部到底边的距离,因此2个水平(或垂直)单位则等于矩形的宽(或高),如Alignment(-1.0, -1.0) 代表矩形的左侧顶点,而Alignment(1.0, 1.0)代表右侧底部终点,而Alignment(1.0, -1.0) 则正是右侧顶点,即Alignment.topRight。为了使用方便,矩形的原点、四个顶点,以及四条边的终点在Alignment类中都已经定义为了静态常量。

Alignment可以通过其坐标转换公式将其坐标转为子元素的具体偏移坐标:
(Alignment.x*childWidth/2+childWidth/2, Alignment.y*childHeight/2+childHeight/2)
其中childWidth为子元素的宽度,childHeight为子元素高度。

 Align(
  widthFactor: 2,
  heightFactor: 2,
  alignment: Alignment(2,0.0),
  child: FlutterLogo(
    size: 60,
  ),
)
运行结果

FractionalOffset

FractionalOffset 继承自 Alignment,它和 Alignment唯一的区别就是坐标原点不同!FractionalOffset 的坐标原点为矩形的左侧顶点,这和布局系统的一致,所以理解起来会比较容易。

FractionalOffset的坐标转换公式为:
实际偏移 = (FractionalOffse.x * childWidth, FractionalOffse.y * childHeight)

Container(
  height: 120.0,
  width: 120.0,
  color: Colors.blue[50],
  child: Align(
    alignment: FractionalOffset(0.2, 0.6),
    child: FlutterLogo(
      size: 60,
    ),
  ),
)
运行结果
上一篇 下一篇

猜你喜欢

热点阅读