Flutter之布局类Widget

栏目: IOS · Android · 发布时间: 5年前

内容简介:子表示Row纵轴(垂直)的对齐方向, 默认值表示水平方向子widget的布局顺序(是从左往右还是从右往左),默认为系统当前Locale环境的文本方向(如中文、英语都是从左往右,而阿拉伯语是从右往左)
Flutter之布局类Widget
  • 原文博客地址: Flutter之布局类Widget
  • 相关博客系列文章: Flutter和Dart系列文章
  • 相关 Demo 地址: GitHub地址
  • 布局类 Widget 都会包含一个或多个子 widget ,不同的布局类 Widget 对子 widget 排版( layout )方式不同
  • 上一篇文章中提到: Widget 实际上就是 Element 的配置数据, Widget 的功能是描述一个 UI 元素的一个配置数据, 而真正的 UI 渲染是由 Element 构成
  • Flutter 中,根据 Widget 是否需要包含子节点将 Widget 分为了三类,分别对应三种 Element ,如下表
Widget 对应的Element 用途
LeafRenderObjectWidget LeafRenderObjectElement Widget 树的叶子节点,用于没有子节点的 widget ,通常基础 widget 都属于这一类,如 TextImage
SingleChildRenderObjectWidget SingleChildRenderObjectElement 包含一个子 Widget ,如: ConstrainedBoxDecoratedBox
MultiChildRenderObjectWidget MultiChildRenderObjectElement 包含多个子 Widget ,一般都有一个 children 参数,接受一个 Widget 数组。如 RowColumnStack

布局类Widget

  • 布局类 Widget 就是指直接或间接继承(包含) MultiChildRenderObjectWidgetWidget ,它们一般都会有一个 children 属性用于接收子 Widget
  • Widget 的继承关系如下:
    • Widget > RenderObjectWidget > (Leaf/SingleChild/MultiChild)RenderObjectWidget
  • RenderObjectWidget 类中定义了创建、更新 RenderObject 的方法,子类必须实现他们
  • 对于布局类 Widget 来说,其布局算法都是通过对应的 RenderObject 对象来实现的
  • Flutter 中主要有以下几种布局类的 Widget
    • 线性布局 RowColumn
    • 弹性布局 Flex
    • 流式布局 WrapFlow
    • 层叠布局 StackPositioned

线性布局

  • RowColumn 是一种现行布局的 Widget , 都继承自 Flex
  • 所谓线性布局,即指沿水平或垂直方向排布 子Widget
  • 对于线性布局,有主轴和纵轴之分,如果布局是沿水平方,那么主轴就指是水平方向,而纵轴即垂直方向;如果布局沿垂直方向,那么主轴就是指垂直方向,而纵轴就是水平方向
  • Row 的主轴即为水平方向, Column 的主轴是垂直方向, 切两者的属性和使用都一样
  • 相关下定义的源码如下:
Row({
    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>[],
})

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>[],
})  
复制代码

相关属性如下

mainAxisAlignment

Widget 在主轴方向的排列方式, 为方便以下皆称 Widget 为组件

// 默认值
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start
复制代码
  • start : 子 widgets 向主轴起点对其, 依次排列
  • end : 子 widgets 向主轴终点对其, 依次排列
  • center : 所有子 widgets 居中排列
  • spaceBetween : 均匀分配,相邻 widgets 间距离相同。每行第一个 widgets 与行首对齐,每行最后一个 widgets 与行尾对齐
  • spaceAround : 均匀分配,相邻 widgets 间距离相同。每行第一个 widgets 到行首的距离和每行最后一个 widgets 到行尾的距离将会是相邻 widgets 之间距离的一半
  • spaceEvenly : 均匀分配,相邻 widgets 间距离相同。每行第一个 widgets 到行首的距离和每行最后一个 widgets 到行尾的距离和相邻 widgets 之间距离相同
属性 效果
start
Flutter之布局类Widget
end
Flutter之布局类Widget
center
Flutter之布局类Widget
spaceBetween
Flutter之布局类Widget
spaceAround
Flutter之布局类Widget
spaceEvenly
Flutter之布局类Widget

mainAxisSize

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

verticalDirection

表示Row纵轴(垂直)的对齐方向, 默认值 down ,表示从上到下; up 表示从下到上

// 默认值
VerticalDirection verticalDirection = VerticalDirection.down
复制代码

crossAxisAlignment

  • 表示子 Widgets 在纵轴方向的对齐方式, Row 的高度等于子 Widgets 中最高的子元素高度
  • crossAxisAlignment 的参考系是 verticalDirection
// 默认值
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center

/**
 * VerticalDirection.down时, crossAxisAlignment.start指顶部对齐
 * VerticalDirection.up时,crossAxisAlignment.start指底部对齐
 * crossAxisAlignment.end和crossAxisAlignment.start正好相反
 */
复制代码
  • VerticalDirection.down 时, crossAxisAlignment 个枚举值如下
  • start : 顶部对其
  • end : 底部对其
  • center : 居中对其
  • stretch : 侧轴方向上, 子 Widget 的高度拉伸至和 Row 的高度相同
  • baseline : 不论 VerticalDirection 取值如何, 子 Widget 的顶部和 Row 的顶部对其

textDirection

表示水平方向子widget的布局顺序(是从左往右还是从右往左),默认为系统当前Locale环境的文本方向(如中文、英语都是从左往右,而阿拉伯语是从右往左)

TextDirection textDirection
/**
 * ltr: 从左往右
 * rtl: 从右往左
 */
复制代码

textBaseline

用于对其文本的水平线,详情可参考

TextBaseline textBaseline
/**
 * alphabetic: 用于对齐普通的字母基线
 * ideographic: 用于对齐表意基线
 */
复制代码

使用代码

Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.center,
        textDirection: TextDirection.ltr,
        verticalDirection: VerticalDirection.down,
        textBaseline: TextBaseline.ideographic,
        children: <Widget>[
          new Container(width: 80.0, height:80.0, color: Colors.red,),
          new Container(width: 80.0, height:90.0, color: Colors.green,),
          new Container(width: 80.0, height:100.0, color: Colors.blue,),
        ],
    )
复制代码

特别注意

RowColumn 中, 如果子 widget 超出屏幕范围,则会报溢出错误

Flutter之布局类Widget

弹性布局

  • 弹性布局允许子 widget 按照一定比例来分配父容器空间
  • Flutter 中的弹性布局主要通过 FlexExpanded 来配合实现
  • Flex 可以沿着水平或垂直方向排列子 widget
  • 如果已知主轴方向,建议使用 RowColumn ,因为 RowColumn 都继承自 Flex ,参数基本相同,所以能使用 Flex 的地方一定可以使用 RowColumn
  • Flex 本身功能是很强大的,它也可以和 Expanded 配合实现弹性布局,接下来我们只讨论 Flex 和弹性布局相关的属性(其它属性已经在介绍 RowColumn 时介绍过了)

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

// direction
// 水平方向
Axis direction = Axis.horizontal
// 垂直方向, 默认为垂直方向
Axis direction = Axis.vertical
复制代码

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

Expanded

可以按比例缩放 RowColumnFlexwidget 所占用的空间

class Expanded extends Flexible {
  
  const Expanded({
    Key key,
    int flex = 1,
    @required Widget child,
  }) : super(key: key, flex: flex, fit: FlexFit.tight, child: child);
}
复制代码

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

Row(
        children: <Widget>[
          Container(width: 80.0, height:80.0, color: Colors.red,),
          Expanded(
            flex: 1,
            child: Container(width: 80.0, height:80.0, color: Colors.blue,),
          ),
          Expanded(
            flex: 1,
            child: Container(width: 80.0, height:80.0, color: Colors.yellow,),
          )
        ],
      ),
复制代码

流式布局

  • 上面提到在 RowColumn 中, 如果子 widget 超出屏幕范围,则会报溢出错误
  • 这是因为 Row 默认只有一行,如果超出屏幕不会折行
  • 我们把超出屏幕显示范围会自动折行的布局称为流式布局
  • Flutter 中通过 WrapFlow 来支持流式布局

Wrap

Wrap({
    Key key,
    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 中的很多属性和 Row 中相同, 这里就不在赘述了, 这里主要看一下 Wrap 中特有的属性

alignment

Widget 在主轴上的对其方式

// 默认值
this.alignment = WrapAlignment.start
// 取值: start, end, center, spaceBetween, spaceAround, spaceEvenly
复制代码

runAlignment

Widget 在纵轴上的对其方式

// 默认值
this.runAlignment = WrapAlignment.start
// 取值: start, end, center, spaceBetween, spaceAround, spaceEvenly
复制代码

spacing

主轴方向子 widget 的间距: spacing: 10

runSpacing

纵轴方向子 widget 的间距: runSpacing: 10

Flow

  • 一般很少会使用 Flow ,因为其过于复杂,需要自己实现子 widget 的位置转换,在很多场景下首先要考虑的是 Wrap 是否满足需求
  • Flow 主要用于一些需要自定义布局的UI或性能要求较高(如动画中)的场景
  • Flow 有如下优点:
    • 性能好: Flow 是一个对 child 尺寸以及位置调整非常高效的控件, Flow 用转换矩阵对 child 进行位置调整的时候进行了优化
    • Flow 定位过后,如果 child 的尺寸或者位置发生了变化,在 FlowDelegate 中的 paintChildren() 方法中调用 context.paintChild 进行重绘,而 context.paintChild 在重绘时使用了转换矩阵,并没有实际调整 Widget 位置。
    • 灵活: 由于我们需要自己实现 FlowDelegatepaintChildren() 方法,所以我们需要自己计算每一个 widget 的位置,因此,可以实现自定义布局。
  • 缺点:
    • 使用复杂.
    • 不能自适应子 widget 大小,必须通过指定父容器大小或重写 FlowDelegategetSize 返回固定大小
  • 下面是一个简单的示例代码:
class FlowWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Container(
      color: Colors.orange,
      child: Flow(
        delegate: ShowFlowDelegate(margin: EdgeInsets.all(10)),
        children: <Widget>[
          Container(width: 100.0, height:100.0, color: Colors.red),
          Container(width: 100.0, height:100.0, color: Colors.yellow),
          Container(width: 100.0, height:100.0, color: Colors.blue),
          Container(width: 100.0, height:100.0, color: Colors.cyan),
          Container(width: 100.0, height:100.0, color: Colors.pink)
        ],
      ),
    );
  }
}
复制代码

实现一个继承自 FlowDelegate 的类, 并重写响应的方法

class ShowFlowDelegate extends FlowDelegate {
  EdgeInsets margin =EdgeInsets.zero;
  ShowFlowDelegate({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
  Size getSize(BoxConstraints constraints) {
    // 设置Flow的大小
    return Size(double.infinity, 300);
  }

  @override
  bool shouldRepaint(FlowDelegate oldDelegate) {
    return oldDelegate !=this;
  }
}
复制代码

层叠布局

  • 层叠布局和 Web 中的绝对定位、 iOS 中的 Frame 布局是相似的,子 widget 可以根据到父容器四个角的位置来确定本身的位置
  • 绝对定位允许子 widget 堆叠(按照代码中声明的顺序)
  • Flutter 中使用 StackPositioned 来实现绝对定位, Stack 允许子 widget 堆叠,而 Positioned 可以给子 widget 定位

Stack

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

alignment

决定子 WidgetStack 中的定位

// 默认值
this.alignment = AlignmentDirectional.topStart

// 取值如下, start和end为水平方向, top和bottom是垂直方向
static const AlignmentDirectional topStart = AlignmentDirectional(-1.0, -1.0);
static const AlignmentDirectional topCenter = AlignmentDirectional(0.0, -1.0);
static const AlignmentDirectional topEnd = AlignmentDirectional(1.0, -1.0);

static const AlignmentDirectional centerStart = AlignmentDirectional(-1.0, 0.0);
static const AlignmentDirectional center = AlignmentDirectional(0.0, 0.0);
static const AlignmentDirectional centerEnd = AlignmentDirectional(1.0, 0.0);

static const AlignmentDirectional bottomStart = AlignmentDirectional(-1.0, 1.0);
static const AlignmentDirectional bottomCenter = AlignmentDirectional(0.0, 1.0);
static const AlignmentDirectional bottomEnd = AlignmentDirectional(1.0, 1.0);

// 还可以使用具体数值比例定位, 设置值在0~1之间
AlignmentDirectional(0.8, 0.9)
复制代码

textDirection

决定 alignment 对齐的参考系

// 默认ltr
textDirection: TextDirection.ltr

// textDirection的值为TextDirection.ltr,则alignment的start代表左,end代表右
// textDirection的值为TextDirection.rtl,则alignment的start代表右,end代表左
复制代码

fit

用于决定没有定位的子 widget 如何去适应 Stack 的大小

// 默认值
this.fit = StackFit.loose

// StackFit.loose表示使用子widget的大小
// StackFit.expand表示扩伸到Stack的大小
复制代码

overflow

决定如何显示超出 Stack 显示空间的子 widget

// 默认值
this.overflow = Overflow.clip

// Overflow.clip时,超出部分会被剪裁(隐藏)
// Overflow.visible时,时则不会被剪裁
复制代码

使用示例

Stack(
  // alignment: AlignmentDirectional.center,
  alignment: AlignmentDirectional(0.8, 0.8),
  textDirection: TextDirection.ltr,
  fit: StackFit.loose,
  overflow: Overflow.visible,
  children: <Widget>[
    Container(width: 100.0, height:100.0, color: Colors.red),
    Container(width: 100.0, height:100.0, color: Colors.yellow),
  ],
)
复制代码

Positioned

PositionediOS 中的 Frame 设置位置和大小一样, 根据上下左右和宽高设置 Widget 的定位和大小

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

left、top 、right、 bottom 分别代表离Stack左、上、右、底四边的距离, widthheight 用于指定定位元素的宽度和高度

注意,此处的width、height 和其它地方的意义稍微有点区别,此处用于配合left、top 、right、 bottom来定位widget,举个例子,在水平方向时,你只能指定left、right、width三个属性中的两个,如指定left和width后,right会自动算出(left+width),如果同时指定三个属性则会报错,垂直方向同理

child: Stack(
  alignment: AlignmentDirectional.center,
  children: <Widget>[
    // 这个widget会根据alignment的设置展示
    Container(child: Text('https', style: TextStyle(color: Colors.red)), color: Colors.yellow,),
    // 这个widget会根据left和top和width的设置显示和alignment无关了, 实际width为80
    Positioned(
      left: 10,
      top: 30,
      width: 80,
      child: Container(width: 100.0, height:100.0, color: Colors.red),
    ),
    Positioned(
      right: 10,
      bottom: 50,
      child: Container(width: 100.0, height:100.0, color: Colors.blue),
    )
  ],
),
复制代码

至此, Flutter 中布局相关的 Widget 也都学习完了......接下来就是容器类 Widget


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

HTML Dog

HTML Dog

Patrick Griffiths / New Riders Press / 2006-11-22 / USD 49.99

For readers who want to design Web pages that load quickly, are easy to update, accessible to all, work on all browsers and can be quickly adapted to different media, this comprehensive guide represen......一起来看看 《HTML Dog》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器