0
点赞
收藏
分享

微信扫一扫

Flutter 学习 布局类Widget


文章目录

  • ​​1. 布局类组件介绍​​
  • ​​2. 布局原理与约束布局​​
  • ​​2.1 BoxConstraints​​
  • ​​2.2 ConstrainedBox​​
  • ​​2.3 SizedBox​​
  • ​​2.4 多重限制​​
  • ​​2.5 UnconstrainedBox​​
  • ​​3. 线性布局​​
  • ​​3.1 主轴和纵轴​​
  • ​​3.2 Row​​
  • ​​3.3 Column​​
  • ​​3.4 特殊情况​​
  • ​​4. 弹性布局​​
  • ​​4.1 Flex​​
  • ​​4.2 Expanded​​
  • ​​5. 流式布局​​
  • ​​5.1 Wrap​​
  • ​​5.2 Flow​​
  • ​​6. 层叠布局​​
  • ​​6.1 Stack​​
  • ​​6.2 Positiened​​
  • ​​6.3 官方示例​​
  • ​​7. 对齐与相对定位​​
  • ​​7.1 Align​​
  • ​​7.2 Alignment​​
  • ​​7.3 FractionalOffset​​
  • ​​7.4 Center​​
  • ​​8. LayoutBuilder、AfterLayouter​​
  • ​​8.1 LayoutBuilder​​
  • ​​8.2 Flutter 的build 和 layout​​
  • ​​参考​​

1. 布局类组件介绍

布局类组件可以容纳一个或多个子组件,Widget根据容纳子树,大致分为三种,如下:

Widget

说明

用途

​LeafRenderObjectWidget​

非容器类组件基类

Widget树的叶子结点,用于没有子节点的Widget

​SingleChildRenderObjectWidget​

单子组件基类

只能包含一个子Widget,例如 ConstrainedBox 等

​MultiChildRenderObjectWidget​

多子组件基类

包含多个子Widget,一般有一个children参数,接收一个Widget数组,例如 Row、Column、Stack等

它们都继承自 RenderObjectWidget

布局类组件就是直接或间接的继承 SingleChildRenderObjectWidget 或者 ​MultiChildRenderObjectWidget​ 的 Widget,一般它们用 child 或者 children 来接收子Widget

2. 布局原理与约束布局

Flutter 有两种布局模型:

  • 基于 RenderBox的盒模型布局
  • 基于 Sliver(RenderSliver)按需加载列表布局

两者的实现上有差异,但是大体流程相同:

  1. 上层组件向下层组件传递约束(constraints)条件
  2. 下层组件确定自己大小,然后通知上层组件
  3. 上层组件确定下层组件相对自身的偏移和确定自身的大小(大多数情况下会根据子组件的大小来确定自身的大小)

本节主要来学习 盒模型布局,在之后的可滚动组件里学习 Sliver 模型, 盒模型布局组件有两个特点:

  1. 组件对应的渲染对象都继承​​RenderBox​​,如果某个组件是 RenderBox,则指它是盒模型布局
  2. 在布局过程中父级传递给子级的约束信息由​​BoxConstraints​​ 描述

2.1 BoxConstraints

约束信息, 包含参数如下:

class BoxConstraints extends Constraints {
const BoxConstraints({
this.minWidth = 0.0, // 最小宽度
this.maxWidth = double.infinity, // 最大宽度,默认为无限大
this.minHeight = 0.0, // 最小高度
this.maxHeight = double.infinity // 最大高度,默认为无限大
})
...

它包含了四个属性,除此之外,还定义了便捷的构造函数,例如

  • ​BoxContraints.tight(Size size)​​ 用于生成固定宽高限制
  • ​BoxConstraints.expand()​​ 可以生成一个尽可能大的填充另一个容器的 BoxConstranits

如果 BoxConstraints 不指定,就可以认为父组件不约束其子组件的宽高。

2.2 ConstrainedBox

ConstrainedBox 用于对子组件添加额外的约束。例如,如果你想让子组件的最小高度是50,可以使用 BoxConstraintes(minHeight: 50) 做为子组件的约束。

例子:我们定义一个 blueBox, 作为背景为红色的盒子,并且不指定它的宽高:

Widget rexBox = const DecoratedBox(decoration: BoxDecoration(color: Colors.blue));

接下来实现一个最小高度为50,宽度尽可能大的容器来包裹上面这个盒子:

ConstrainedBox(
constraints:
BoxConstraints(minWidth: double.infinity, minHeight: 50),
child: Container(
height: 5.0,
child: blueBox,
),
)

效果:

Flutter 学习 布局类Widget_宽高


可以看到, 虽然设置了 Container 的高度为5像素,但是最终却是 50像素,这正是 ConstrainedBox 的最小高度限制生效了。

如果将 Container 的高度设置为 80像素,那么最终蓝色区域的高度也会为80像素,因为在此示例中, ConstrainedBox 只限制了最小高度,并未限制最大高度

2.3 SizedBox

用于子元素指定固定的宽高,如:

SizedBox(
width: 50,
height: 50,
child: yellowBox,
)

效果:

Flutter 学习 布局类Widget_flutter_02

2.4 多重限制

如果某一个子组件有多个父级 ​​ConstrainedBox​​​ 限制,那么最终会是哪个生效呢?
例如:

ConstrainedBox(
constraints: BoxConstraints(minWidth: 60.0, minHeight: 60.0), //父
child: ConstrainedBox(
constraints: BoxConstraints(minWidth: 90.0, minHeight: 20.0),//子
child: redBox,
),
)

的效果和下面的效果:

ConstrainedBox(
constraints: BoxConstraints(minWidth: 90.0, minHeight: 20.0),
child: ConstrainedBox(
constraints: BoxConstraints(minWidth: 60.0, minHeight: 60.0),
child: redBox,
)
)

是相同的,也就是说:

当一个子 Widget 有多重限制的时候, 对于 minWidth​minHeight​ 来说,是取多重限制中较大的的那个

2.5 UnconstrainedBox

假如 A 组件的子组件是 B, B 的子组件是 C,那么 A可以约束 B组件,B组件可以约束 C组件, 但是 A组件不会直接约束到C组件,除非 B 将 A 对它的约束透传给了 C。 那么根据该原理,可以实现一个 B组件:

  • B组件在布局 C时 不约束 C
  • C根据自身的空间占用来确定自身大小
  • B在遵守A 的约束前提小结合子组件大小来确定自身大小

这个 B组件就是 ​​UnconstrainedBox​​​ 组件,也就是说 ​​UnconstrainedBox​​​ 的子组件将不再受到约束,大小完全取决于自己。
一般情况下我们不怎么使用这个组件,但在 去掉 多重限制的时,也许会有帮助,例如下面代码:

ConstrainedBox(
constraints: BoxConstraints(minWidth: 60.0, minHeight: 100.0), //父
child: UnconstrainedBox( //“去除”父级限制
child: ConstrainedBox(
constraints: BoxConstraints(minWidth: 90.0, minHeight: 20.0),//子
child: redBox,
),
)
)

如果没有使用 UnconstrainedBox, 那么将会产生 90 * 100 的框,但是加了这个后,最终会生成 90 * 20 的框。

Flutter 学习 布局类Widget_宽高_03

但是这里请注意, 这个 90*20 并非是真正的大小, 它上面还有 80 像素的空白空间,所以并不是真正的屏蔽了父组件的限制呢。

3. 线性布局

线性布局就是水平或垂直方向排列的子组件。 Flutter 中使用 ​​Row​​​ 和 ​​Column​​​ 来实现线性布局,它们都继承自 ​​Flex​​​, ​​Flex​​ 是弹性布局。

3.1 主轴和纵轴

  • 如果当前布局是沿水平方向,那么主轴就是水平方向, 而纵轴就是垂直方向
  • 如果当前布局是沿垂直方向,那么主轴就是垂直方向,纵轴就是水平方向

在线性布局中,有两个定义对齐方式的枚举类 ​​MainAxisAlignment​​​ 和 ​​CrossAxisAlignment​​,分别代表主轴对齐和纵轴对齐

3.2 Row

Row可以沿着水平方向排列子 Widget,构造函数如下:

Row({
...
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>[],
})

  • ​textDirection​​ 表示水平方向子组件的布局顺序是从左往右还是右往左, 默认是系统Locale中的方向
  • ​mainAxisSize​​​ 表示 ​​Row​​ 在主轴(就是水平)方向占用的控件,默认是 ​​MainAxisSize.max​​ ,表示尽可能多的占用水平方向的空间,此时无论子 widgets 实际占用多少水平空间, ​​Row​​ 的宽度始终等于水平方向的最大宽度; 而 ​​MainAxisSize.min​​ 表示尽可能少的占用水平空间,当子组件没有占满水平剩余空间,则 ​​Row​​ 的实际宽度等于所有子组件占用的水平空间
  • ​mainAxisAlignment​​​ 表示子组件主轴的对齐方式, 当 ​​mainAxisSzie == MainAxisSize.min​​ 时是无意义的,因为子组件宽度就是父组件宽度。 只有等于 ​​MainAxisSize.max​​ 时才有意义, ​​MainAxisAligment.start​​ 指从textDirection设置的值开始布局。 也就是说 textDirection 是参考系,这里就不再赘述
  • ​verticalDirection​​​ 表示纵轴的对齐方向。 默认 ​​VerticalDirection.down​​ 是从上到下
  • ​crossAxisAlignment​​​ 和 ​​mainAxisAlignment​​ 一样,表示纵轴的对齐方式
  • ​children​​ 定义子组件数组

示例代码:

Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("hello "),
Text("i am Rikka"),
],
)

Flutter 学习 布局类Widget_宽高_04

3.3 Column

垂直线性布局。 参数和 ​​Row​​ 是一样的,就是主轴和纵轴和 Row 反着来。 所以可以触类旁通,这里就不再赘述

3.4 特殊情况

如果 ​​Row​​​ 里嵌套 ​​Row​​​, 或者 ​​Column​​​ 里嵌套 ​​Column​​​ ,那么只有最外面的 ​​Row​​​ 或 ​​Column​​​ 会占用尽可能大的空间,里面 ​​Row 或 Column​​​ 所占用的空间为实际大小,下面以 ​​Column​​ 为例说明:

Container(
color: Colors.yellow,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max, // 有效,外层 Column 主轴占满整个屏幕
children: [
Container(
color: Colors.red,
child: Column(
mainAxisSize: MainAxisSize.max, // 无效,内层 Column 高度为实际占用高度
children: [
Text("hello "),
Text("I am Rikka")
],
),
)
],
),
),
),

效果如下:

Flutter 学习 布局类Widget_构造函数_05


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

Expanded(
child: Container(
color: Colors.red,
child: Column(
mainAxisSize: MainAxisSize.max, // 无效,内层 Column 高度为实际占用高度
children: [Text("hello "), Text("I am Rikka")],
),
))

Flutter 学习 布局类Widget_flutter_06

4. 弹性布局

弹性布局允许组件按一定比例来分配父容器空间, Flutter中主要通过 ​​Flex​​​ 和 ​​Expanded​​ 来实现

4.1 Flex

​Flex​​​ 可以沿着水平或者垂直方向排列子组件,如果你知道主轴和纵轴的方向,那么直接使用 ​​Row​​​ 或者 ​​Column​​​ 会更好,因为它们两个就是继承自 ​​Flex​​, 参数也基本相同,所以可以被这两个来替代。

Flex 主要是和 ​​Expanded​​ 配合实现弹性布局, 下面是Flex的构造函数, 和 Row、Column 相同的都用省略号概括了:

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

4.2 Expanded

​Expanded​​​ 只能作为 ​​Flex​​ 的孩子(否则会报错),它可以按比例扩展 Flex 子组件所占用的空间。

class Expanded extends Flexible {
const Expanded({
Key? key,
int flex = 1,
required Widget child,
})...

  • ​flex​​​ 弹性系数, 如果为0 或者 null,则 child 是没有弹性的,即不会扩展占用空间。如果大于0,所有的 Expand 按照其 flex 的比例来分割主轴的全部空闲位置。 这玩意就和 Android 原生控件 LinearLayout 中的子控件的 ​​weight​​ 一样的。这里就不贴代码

这里再多介绍一个 ​​Spacer​​​, 它是 ​​Expanded​​ 的一个包装类,也可以按比例占用空间, 源码如下,可以用来做分割线什么的:

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(),
);
}
}

5. 流式布局

在使用 ​​Row​​​ 或者 ​​Column​​ 时,如果子 Widget 超出屏幕,就会有溢出的错误,如:

Row(
children: <Widget>[
Text("xxx"*100)
],
);

Flutter 学习 布局类Widget_构造函数_07


这是因为 Row 默认只有一行,如果超出屏幕不会折行,我们把超出屏幕显示范围会自动折行的布局称为流式布局。 Flutter中通过 ​​Wrap​​​ 和 ​​Flow​​ 来支持流式布局,将上例中的 Row 换成 Wrap 将会自动折行。

5.1 Wrap

来看看 Wrap 的参数:

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 的很多属性在 ​​Flex​​ 中也有,这些属性的意义都是相同的。

额外需要了解的属性:

  • ​spacing​​ 主轴方向子 Widget 的间距
  • ​runSpacing​​ 纵轴方向子 Widget 的间距
  • ​runAlignment​​ 纵轴方向的对齐方式

Flutter 学习 布局类Widget_flutter_08

5.2 Flow

因为 Flow 较为复杂,一般会很少的场景使用 Flow,需要实现子 Widget 的位置转换,所以它可以做一些动画。它有一下的优点:

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

缺点:

  • 使用比较复杂
  • Flow 不能自适应子组件大小,必须要通过指定父容器大小或实现​​TestFlowDelegate.getSize​​ 返回固定大小

示例:
我们对六个色块进行自定义流式布局:

Flow(
delegate: TestFlowDelegate(margin: EdgeInsets.all(10.0)),
children: <Widget>[
Container(width: 80.0, height:80.0, color: Colors.green,),
Container(width: 80.0, height:80.0, color: Colors.red,),
Container(width: 80.0, height:80.0, color: Colors.yellow,),
Container(width: 80.0, height:80.0, color: Colors.blue,),
Container(width: 80.0, height:80.0, color: Colors.brown,),
Container(width: 80.0, height:80.0, color: Colors.purple,),
],
)

同时继承 FlowDelegate 实现一个 ​​TestFlowDelegate​​:

class TestFlowDelegate extends FlowDelegate {
EdgeInsets margin;

TestFlowDelegate({this.margin = EdgeInsets.zero});

double width = 0;
double height = 0;

@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: 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: Matrix4.translationValues(x, y, 0.0));
x += context.getChildSize(i)!.width + margin.left + margin.right;
}
}
}

@override
Size getSize(BoxConstraints constraints) {
// 指定Flow的大小,简单起见我们让宽度竟可能大,但高度指定为200,
// 实际开发中我们需要根据子元素所占用的具体宽高来设置Flow大小
return Size(double.infinity, 200.0);
}

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

效果如下:

Flutter 学习 布局类Widget_构造函数_09

6. 层叠布局

层叠布局和 Android 中的 ​​FrameLayout​​ 类似,子组件可以根据父容器的四个角的位置来确定自身位置,且该布局允许子组件按照声明顺序堆叠起来。

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

6.1 Stack

来看看 Stack 的构造函数:

Stack({
Key? key,
this.alignment = AlignmentDirectional.topStart,
this.textDirection,
this.fit = StackFit.loose,
@Deprecated(
'Use clipBehavior instead. See the migration guide in flutter.dev/go/clip-behavior. '
'This feature was deprecated after v1.22.0-12.0.pre.',
)
this.overflow = Overflow.clip,
this.clipBehavior = Clip.hardEdge,
List<Widget> children = const <Widget>[],
})

  • ​alignment​​ 对齐方式。 left、right 为横轴, top、bottom为纵轴
  • ​textDirection​​ 和 Row、Column 的 textDirection 功能一样
  • ​fit​​​ 此参数用于确定没有定位的子组件如何去适应 ​​Stack​​ 的大小。 ​​StackFit.loose​​ 表示使用子组件的大小,​​StackFit.expand​​ 表示拉伸到 Stack 的大小
  • ​overflow​​​ 此属性决定如何显示超出 Stack 显示空间的子组件, ​​Overflow.clip​​ 时,超出部分会被剪裁,该属性被废弃,使用 clipBehavior,两者作用是一样的

6.2 Positiened

来看下构造函数:

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​​ 宽高。 但是水平方向上 left、right、width 只能三选二,因为任意两点就能确定水平的位置, 垂直方向同理

6.3 官方示例

我么能通过对几个 ​​Text​​​ 的定位来演示 ​​Stack​​​ 和 ​​Positioned​​ 的特性:

ConstrainedBox(
constraints: const BoxConstraints.expand(),
child: Stack(
alignment: Alignment.center,
children: [
Container(
child: const Text(
"hello rikka",
style: TextStyle(color: Colors.white),
),
color: Colors.red,
),
const Positioned(left: 18.0, child: Text("I am Rikka")),
const Positioned(
top: 18.0, child: Text("Your friendly neighborhood"))
],
),
)

效果如下:

Flutter 学习 布局类Widget_flutter_10

7. 对齐与相对定位

如果我们只想简单调整一个子元素在父元素中的位置的话, 可以使用 Align 会更加简单一点

7.1 Align

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

  • ​alignment​​​ 表示子组件在父组件中的其实位置,接收一个​​AliginmentGemotry​​​,有两个常用子类:​​Alignment​​​ 和​​FractionalOffset​
  • ​widthFactor​​​ 、​​heightFactor​​​ 用于确定 ​​Align​​ 组件本身宽高属性,实际上会用这两个因子分别乘以 宽、高得出最终 Align 的宽高,如果为null,则宽高将会占用尽可能多的空间

示例, 定义一个靠父组件右上的子组件:

Container(
height: 120,
width: 120,
color: Colors.greenAccent,
child: const Align(
alignment: Alignment.topRight,
child: FlutterLogo(
size: 60,
),
),
)

Flutter 学习 布局类Widget_flutter_11


​FlutterLogo​​​ 是一个用于展示 Flutter logo的组件,在上面例子中,我们指定父组件Container为120、120,如果我们不显示指定宽高,而是通过指定 ​​widthFactor​​​ 和 ​​heightFactor​​ 为 2 也可以达到同样的效果:

...
widthFactor: 2,
heightFactor: 2,
child: FlutterLog(
...

而我们指定了 ​​Alignment.topRight​​​ 将子组件定位在右上角,那么 ​​Alignment.topRight​​ 是什么呢?点进去可以看到它:

static const Alignment topRight = Alignment(1.0, -1.0);

下面我们来学习一下这个 ​​Alignment​

7.2 Alignment

Alignement 继承自 ​​AlignmentGeometry​​, 表示矩形内的一个点,构造函数:

const Alignment(this.x, this.y)

​x​​​ 、​​y​​ 两个属性,方别表示在水平方向和垂直方向的偏移, 所以说到偏移,就要了解 Widget 的坐标系。

对于 ​​Alignment​​, Widget会以矩形的中心点坐标原点,即当 Alignment(0,0) 时,组件是水平垂直居中的, [-1 , 1] 表示从左到右、 从顶到底的距离。
如 (-1, -1) 表示左上角、 (1,-1)表示右上角, (-1,1)表示左上角,(1, 1) 表示右下角,为了简便使用,定义了 topRight、topLeft等静态变量分别表示它们。

Alignment 是通过坐标转换公式,获得子元素的具体偏移坐标:

(Alignment.x*childWidth/2 + childWidth/2, Alignment.y*childheight/2 + childHeight/2)

所以在上面例子中, 将 (1.0, -1.0) 代入,可以得到 ​​FlutterLogo​​ 的实际偏移坐标为 (60, 0)

7.3 FractionalOffset

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

FractionalOffset 的坐标转换公式:

实际偏移 = (FractionalOffset.x*childWidth, FactionalOffset.y*childHeight)

这里不做例子赘述,和 Alignment 差不多

7.4 Center

我们前面有用到过 Center 组件, 它其实也是对齐组件的一种,并且继承自 ​​Alignment​​​,相当于定义了 ​​alignemt = center​​ 源码定义为:

class Center extends Align {
const Center({ Key? key, double? widthFactor, double? heightFactor, Widget? child })
..

它只少了一个 alignemnt, 而 alignemnt 默认是 center,所以作用就不言而喻了。

8. LayoutBuilder、AfterLayouter

8.1 LayoutBuilder

通过 LayouBuilder,我们可以拿到布局过程中父组件传递的约束信息,然后就可以根据约束信息动态构建不同的布局,这是一个响应式的交互。

下面来实现一个响应式的 Column 组件 ​​ResponsiveColumn​​, 它的功能是当前可用宽度小于200时,将子组件显示为一列,否则两列,代码如下:

class ResponsiveColumn extends StatelessWidget {
const ResponsiveColumn({Key? key, required this.children}) : super(key: key);

final List<Widget> children;

@override
Widget build(BuildContext context) {
// 通过 LayoutBuild 拿到父组件传递的约束,然后判断 maxWidth 是否小于200
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
if (constraints.maxWidth < 200) {
// 最大宽度小于 200 ,则显示单列
return Column(children: children, mainAxisSize: MainAxisSize.min);
} else {
// 大于200 则显示双列
var _children = <Widget>[];
for (var i = 0; i < children.length; i += 2) {
if (i + 1 < children.length) {
_children.add(Row(
children: [children[i], children[i + 1]],
mainAxisSize: MainAxisSize.min,
));
} else {
_children.add(children[i]);
}
}
return Column(children: _children, mainAxisSize: MainAxisSize.min);
}
});
}
}

// 使用:
...
body: Column(
children: [
// 限制宽度为 190
SizedBox(width: 190, child: ResponsiveColumn(children: _children)),
ResponsiveColumn(children: _children),
],
),
...

效果如下:

Flutter 学习 布局类Widget_宽高_12

8.2 Flutter 的build 和 layout

通过观察 LayoutBuilder 示例,我们可以发现: **Flutter 的 build 和 layout是可以交错进行的。**并不是严格的按照先 build 再 layout 的顺序,这里的原理可以后面再学习。

参考

​​官方文档​​


举报

相关推荐

0 条评论