内容简介:子表示Row纵轴(垂直)的对齐方向, 默认值表示水平方向子widget的布局顺序(是从左往右还是从右往左),默认为系统当前Locale环境的文本方向(如中文、英语都是从左往右,而阿拉伯语是从右往左)
- 原文博客地址: 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 都属于这一类,如 Text 、 Image |
SingleChildRenderObjectWidget |
SingleChildRenderObjectElement |
包含一个子 Widget ,如: ConstrainedBox 、 DecoratedBox 等 |
MultiChildRenderObjectWidget |
MultiChildRenderObjectElement |
包含多个子 Widget ,一般都有一个 children 参数,接受一个 Widget 数组。如 Row 、 Column 、 Stack 等 |
布局类Widget
- 布局类
Widget就是指直接或间接继承(包含)MultiChildRenderObjectWidget的Widget,它们一般都会有一个children属性用于接收子Widget -
Widget的继承关系如下:-
Widget>RenderObjectWidget>(Leaf/SingleChild/MultiChild)RenderObjectWidget
-
-
RenderObjectWidget类中定义了创建、更新RenderObject的方法,子类必须实现他们 - 对于布局类
Widget来说,其布局算法都是通过对应的RenderObject对象来实现的 -
Flutter中主要有以下几种布局类的Widget:- 线性布局
Row和Column - 弹性布局
Flex - 流式布局
Wrap、Flow - 层叠布局
Stack、Positioned
- 线性布局
线性布局
-
Row和Column是一种现行布局的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 |
|
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: <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,),
],
)
复制代码
特别注意
在 Row 和 Column 中, 如果子 widget 超出屏幕范围,则会报溢出错误
弹性布局
- 弹性布局允许子
widget按照一定比例来分配父容器空间 -
Flutter中的弹性布局主要通过Flex和Expanded来配合实现 -
Flex可以沿着水平或垂直方向排列子widget - 如果已知主轴方向,建议使用
Row或Column,因为Row和Column都继承自Flex,参数基本相同,所以能使用Flex的地方一定可以使用Row或Column -
Flex本身功能是很强大的,它也可以和Expanded配合实现弹性布局,接下来我们只讨论Flex和弹性布局相关的属性(其它属性已经在介绍Row和Column时介绍过了)
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 ,对应的 RenderObject 为 RenderFlex , RenderFlex 中实现了其布局算法
Expanded
可以按比例缩放 Row 、 Column 和 Flex 子 widget 所占用的空间
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,),
)
],
),
复制代码
流式布局
- 上面提到在
Row和Column中, 如果子widget超出屏幕范围,则会报溢出错误 - 这是因为
Row默认只有一行,如果超出屏幕不会折行 - 我们把超出屏幕显示范围会自动折行的布局称为流式布局
-
Flutter中通过Wrap和Flow来支持流式布局
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位置。 - 灵活: 由于我们需要自己实现
FlowDelegate的paintChildren()方法,所以我们需要自己计算每一个widget的位置,因此,可以实现自定义布局。
- 性能好:
- 缺点:
- 使用复杂.
- 不能自适应子
widget大小,必须通过指定父容器大小或重写FlowDelegate的getSize返回固定大小
- 下面是一个简单的示例代码:
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中使用Stack和Positioned来实现绝对定位,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
决定子 Widget 在 Stack 中的定位
// 默认值 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
Positioned 和 iOS 中的 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左、上、右、底四边的距离, width 和 height 用于指定定位元素的宽度和高度
注意,此处的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 了
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- css经典布局系列三——三列布局(圣杯布局、双飞翼布局)
- 四种方法实现──三栏布局(圣杯布局、双飞翼布局)
- 浅谈CSS三栏布局(包括双飞翼布局和圣杯布局)
- css经典布局——圣杯布局
- CSS布局基础——(三栏布局)
- Grid布局 - 一键布局尝试总结~
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
无线:网络文化中激进的经验主义
[英] 阿德里安·麦肯齐 / 张帆 / 上海译文出版社 / 2018-9
本书研究了无线是如何成为当代人类经验的主角的。从路由器、智能电话、电子书、城市到在线工作、服务协议、玩具以及国家等各个方面,人们已经感觉到了无线技术所引发的变革。本书作者援引一个世纪之前的哲学技术来分析当代最前沿的后网络时代的人类状况。基于威廉•詹姆斯的实用主义哲学相关的彻底经验主义,作者提出了把失序的无线网络世界与人们的感知匹配起来的新方式。一起来看看 《无线:网络文化中激进的经验主义》 这本书的介绍吧!