博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Flutter之布局类Widget
阅读量:6903 次
发布时间:2019-06-27

本文共 12152 字,大约阅读时间需要 40 分钟。

  • 原文博客地址:
  • 相关博客系列文章:
  • 相关Demo地址:
  • 布局类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
children = const
[],})Column({ Key key, MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start, MainAxisSize mainAxisSize = MainAxisSize.max, CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center, TextDirection textDirection, VerticalDirection verticalDirection = VerticalDirection.down, TextBaseline textBaseline, List
children = const
[],}) 复制代码

相关属性如下

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
end
center
spaceBetween
spaceAround
spaceEvenly

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: 
[ 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超出屏幕范围,则会报溢出错误

弹性布局

  • 弹性布局允许子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
children = const
[],})// 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: 
[ 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
children = const
[],})复制代码

可以看到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: 
[ 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
children = const
[],})复制代码

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对齐的参考系

// 默认ltrtextDirection: 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: 
[ 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会根据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

参考文献


转载于:https://juejin.im/post/5cc19f8f5188252acb429ec8

你可能感兴趣的文章
java 任务栏_java 如何将当前程序隐藏到任务栏(类似windows上的其他程序)
查看>>
java编写一个小学辅助软件_高分求用Java编写的一个小软件
查看>>
java 现场_面试遇到现场(我哥是李刚 java模型现场)
查看>>
java asm 书_使用ASM来书写Java代码-2(zt)续2
查看>>
java 堆栈的声明_Java 堆栈
查看>>
linux 内存管理 代码,《LINUX3.0内核源代码分析》第四章:内存管理(3)
查看>>
linux 一句话运行多,Linux运维工程师常用一句话脚本,你会几句?
查看>>
linux锁住终端会话命令,Linux终端会话实时共享(kibitz)
查看>>
夏普linux电视软件,夏普电视第三方软件安装方法,使用教程和攻略
查看>>
华为手机linux终端,华为云IoT如何让“哑”终端进化为智能终端?看完这场直播你就明白了...
查看>>
linux特殊权限管理,linux文件访问特殊权限的设置
查看>>
linux 网络编程 51cto,Linux服务器开发,2小时搞定高并发网络编程
查看>>
fork是linux函数吗,关于LINUX中的FORK函数
查看>>
红旗linux 进不去图形界面,进不了红旗Linux6.0的图形界面请高手帮忙
查看>>
linux安装中文语言命令,Centos使用yum命令安装中文语言包(fonts-chinese.noarch,m17n-db-common-cjk)...
查看>>
linux添加Qt的fontconfig,QT__qt SDK------Fontconfig-2.4.2安装 (二)
查看>>
linux 查看io调度策略,磁盘IO调度策略
查看>>
高通笔记本装linux,在华硕畅370(TP370QL)骁龙笔记本上安装Ubuntu 18.04 ARM64的方法...
查看>>
linux sysfs属性,linux-sysfs
查看>>
c语言int speed=1,求大神帮忙看看这个弹弹球消砖块的游戏代码,为什么speed只能15...
查看>>