为了账号安全,请及时绑定邮箱和手机立即绑定

Flutter——布局类组件

标签:
Android

布局类组件简介

布局类组件都会包含一个或多个子组件,不同的布局类组件对子组件排版(layout)方式不同,简单讲,布局类组件会按照一定的排列方式来对其子Widget进行排列。
在Flutter中,根据Widget是否需要包含子节点将Widget分为了三类,分别对应三种Element,如下表:

Widget 对应的Element 用途
LeafRenderObjectWidget LeafRenderObjectElement Widget树的叶子节点,用于没有子节点的widget,通常基础组件都属于这一类,如Image。
SingleChildRenderObjectWidget SingleChildRenderObjectElement 包含一个子Widget,如:ConstrainedBox、DecoratedBox等
MultiChildRenderObjectWidget MultiChildRenderObjectElement 包含多个子Widget,一般都有一个children参数,接受一个Widget数组。如Row、Column、Stack等

Flutter中的很多Widget是直接继承自StatelessWidget或StatefulWidget,然后在build()方法中构建真正的RenderObjectWidget.布局类组件就是指直接或间接继承(包含)Leaf/SingleChild/MultiChild)RenderObjectWidget的Widget,它们一般都会有一个children属性用于接收子Widget。我们看一下继承关系(Leaf/SingleChild/MultiChild)RenderObjectWidget > RenderObjectWidget > Widget 。

举个简单的例子,Text,它其实是继承自StatelessWidget,然后在build()方法中通过RichText来构建其子树,而RichText才是继承自LeafRenderObjectWidget。所以为了方便叙述,我们也可以直接说Text属于LeafRenderObjectWidget(其它widget也可以这么描述),这才是本质。

  @override
  Widget build(BuildContext context) {
    ...;
    Widget result = RichText(
      ...;
    );
    ...;
    return result;
  }

RenderObjectWidget

RenderObjectWidget类中定义了创建、更新RenderObject的方法,子类必须实现他们,关于RenderObject我们现在只需要知道它是最终布局、渲染UI界面的对象即可,也就是说,对于布局类组件来说,其布局算法都是通过对应的RenderObject对象来实现的。

abstract class RenderObjectWidget extends Widget {
  const RenderObjectWidget({ Key key }) : super(key: key);

  @override
  RenderObjectElement createElement();
  
  @protected
  RenderObject createRenderObject(BuildContext context);

  @protected
  void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }

  @protected
  void didUnmountRenderObject(covariant RenderObject renderObject) { }
}

举个例子,RichText对应的RenderObject对象就是RenderParagraph,而RichText的实现就在RenderParagraph中。

class RichText extends LeafRenderObjectWidget {
  @override
  RenderParagraph createRenderObject(BuildContext context) {
    assert(textDirection != null || debugCheckHasDirectionality(context));
    return RenderParagraph(
      ...;
    );
  }

  @override
  void updateRenderObject(BuildContext context, RenderParagraph renderObject) {
    ...;
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    ...;
  }
}

线性布局(Row和Column)

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

线性布局,即指沿水平或垂直方向排布子组件。对于线性布局,需要注意两点:

  • 主轴和纵轴
  • 对齐时的参考方向

弹性布局(Flex)

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

允许子组件按照一定比例来分配父容器空间。弹性布局的参数含义基本和线性布局相同。弹性布局一般和Expanded配合使用来更好发挥Flex的作用。

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

flex可以指定child在Flex所占的比例

//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,
              ),
            ),
          ],
        ),

流式布局(Wrap和Flow)

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>[],
})

Wrap的属性基本和线性布局相同,需要注意Wrap特有的几个属性:

  • spacing:主轴方向子widget的间距
  • runSpacing:纵轴方向的间距
  • runAlignment:纵轴方向的对齐方式
Wrap(
  spacing: 8.0, // 主轴(水平)方向间距
  runSpacing: 4.0, // 纵轴(垂直)方向间距
  alignment: WrapAlignment.center, //沿主轴方向居中
  children: <Widget>[
    ...;
  ]
 )

Flow的属性略显复杂,以后再说。

层叠布局 Stack

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

子组件可以根据距父容器四个角的位置来确定自身的位置。绝对定位允许子组件堆叠起来(按照代码中声明的顺序)。
Stack需要特别注意两个属性:

  • alignment: 此参数决定如何去对齐没有定位(没有使用Positioned)或部分定位的子组件
  • fit: 用于确定没有定位的子组件如何去适应Stack的大小。StackFit.loose表示使用子组件的大小,StackFit.expand表示扩伸到Stack的大小。

Flutter中使用Stack和Positioned这两个组件来配合实现绝对定位。Stack允许子组件堆叠,而Positioned用于根据Stack的四个角来确定子组件的位置。

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

对齐与相对定位(Align)

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

Align只有一个child,如果只想简单的调整一个子元素在父元素中的位置的话,使用Align组件会更简单一些。
对于Align,要特别注意其alignment属性:

  • alignment : 需要一个AlignmentGeometry类型的值,表示子组件在父组件中的起始位置。AlignmentGeometry 是一个抽象类,它有两个常用的子类:Alignment和 FractionalOffset。

Alignment和 FractionalOffset的区别

唯一的区别在于坐标原点:Alignment以矩形中心点为坐标原点,而FractionalOffset的坐标原点为矩形的左侧顶点,这和布局系统的一致。

              Container(
                height: 120.0,
                width: 120.0,
                color: Colors.blue[50],
                child: Align(
                  //Alignment(x,y)的坐标原点为矩形的中心点
                  //Alignment(x,y)最终得到实际坐标点的计算公式:
                  // (Alignment.x*childWidth/2+childWidth/2, Alignment.x*childHeight+childHeight/2)
                  alignment: Alignment(1, -1), //topRight = Alignment(1.0, -1.0),即(60, 0)
                  widthFactor: 2,
                  heightFactor: 2,
                  child: FlutterLogo(
                    size: 60,
                  ),
                ),
              ),
点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消