跳到主要内容

三、常用布局控件

线性布局(Row和Column)

所谓线性布局,即指沿水平或垂直方向排列子组件。Row和Column就像css中flex布局的flex-direction属性:row(横向) 和 column (纵向)排列子元素。

Row

属性描述
mainAxisAlignmentMainAxisAlignment.start控制子widget在主轴方向上的对齐方式(对于Row是水平方向,对于Column是垂直方向)
crossAxisAlignmentCrossAxisAlignment.center控制子widget在交叉轴上的对齐方式(对于Row是垂直方向,对于Column是水平方向)
children[Text('123')]子元素
mainAxisSizeMainAxisSize.max用于确定RowColumn在主轴方向上应占用的空间大小
默认是MainAxisSize.max,表示尽可能多的占用水平方向的空间,此时无论子 widgets 实际占用多少水平空间,Row的宽度始终等于水平方向的最大宽度;而MainAxisSize.min表示尽可能少的占用水平空间,当子组件没有占满水平剩余空间,则Row的实际宽度等于所有子组件占用的水平空间;
verticalDirectionVerticalDirection.up它决定了子widget在垂直方向上的起始排列点,是从上往下还是从下往上排列,默认是VerticalDirection.down,表示从上到下。

verticalDirection

  • 对于 ColumnVerticalDirection.down是默认值,表示子widget从上往下排列,第一个子widget位于顶部,后续的子widget依次向下排列;VerticalDirection.up表示子widget从下往上排列,第一个子widget位于底部,后续的子widget依次向上排列。
  • Row 或其他水平布局widget:Row纵轴(垂直)的对齐方向,默认是VerticalDirection.down,表示从上到下。虽然 verticalDirection 属性影响的是垂直方向,但它决定了在主轴(水平方向)对齐方式为 startend 时,如何处理交叉轴(垂直方向)上的对齐:
Column(
crossAxisAlignment: CrossAxisAlignment.start, //测试Row对齐方式,排除Column默认居中对齐的干扰
children: <Widget>[
Row( // 1
mainAxisAlignment: MainAxisAlignment.center, // 主轴居中
children: <Widget>[
Text(" 主轴居中1 "),
Text(" 主轴居中2 "),
],
),
Row( // 2
mainAxisSize: MainAxisSize.min, // 主轴尽可能小,导致两个文本都紧靠左边
mainAxisAlignment: MainAxisAlignment.center, // 主轴居中
children: <Widget>[
Text("主轴尽可能小&主轴居中1"),
Text("主轴尽可能小&主轴居中2"),
],
),
Row( // 3
mainAxisAlignment: MainAxisAlignment.end, // TextDirection.rtl从右向左的顺序排列,导致MainAxisAlignment.end表示左对齐
textDirection: TextDirection.rtl, // 文本方向右对齐
children: <Widget>[
Text("主轴居右文本方向1"),
Text("主轴居右文本方向2"),
],
),
Row( // 4
crossAxisAlignment: CrossAxisAlignment.start, // 交叉轴居上
verticalDirection: VerticalDirection.up, // 竖直方向导致文本靠下
children: <Widget>[
Text(" 交居上竖直1", style: TextStyle(fontSize: 30.0),),
Text(" 交居上竖直2"),
],
),
],
)

  1. 第一个Row很简单,默认为居中对齐;
  2. 第二个Row,由于mainAxisSize值为MainAxisSize.minRow的宽度等于两个Text的宽度和,所以对齐是无意义的,所以会从左往右显示;
  3. 第三个Row设置textDirection值为TextDirection.rtl,所以子组件会从右向左的顺序排列,而此时MainAxisAlignment.end表示左对齐,所以最终显示结果就是图中第三行的样子;
  4. 第四个 Row 测试的是纵轴的对齐方式,由于两个子 Text 字体不一样,所以其高度也不同,我们指定了verticalDirection值为VerticalDirection.up,即从低向顶排列,而此时crossAxisAlignment值为CrossAxisAlignment.start表示底对齐。

Column

Column可以在垂直方向排列其子组件。参数和Row一样,不同的是布局方向为垂直,主轴纵轴正好相反

  Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text("hi"),
Text("world"),
Container(
color: Colors.green,
child: Text("hello world", style: TextStyle(fontSize: 30.0),),
),
],
)

  • 由于我们没有指定ColumnmainAxisSize,所以使用默认值MainAxisSize.max,则Column会在垂直方向占用尽可能多的空间,此例中会占满整个屏幕高度。
  • 由于我们指定了 crossAxisAlignment 属性为CrossAxisAlignment.center,那么子项在Column纵轴方向(此时为水平方向)会居中对齐。注意,在水平方向对齐是有边界的,总宽度为Column占用空间的实际宽度,而实际的宽度取决于子项中宽度最大的Widget。在本例中,Column有两个子Widget,而显示“hello world”的Container宽度最大,所以Column的实际宽度则为绿色的宽度,所以居中对齐后前面的两个元素会居中对齐

与css的Flex不同, RowColumn都只会在主轴方向占用尽可能大的空间,而纵轴的长度则取决于他们最大子元素的长度。如果我们想让本例中的文本控件在整个手机屏幕中间对齐:

  • Column的宽度指定为屏幕宽度;这很简单,我们可以通过ConstrainedBoxSizedBox(我们将在后面章节中专门介绍这两个Widget)来强制更改宽度限制,将minWidth设为double.infinity,可以使宽度占用尽可能多的空间:
ConstrainedBox(
constraints: BoxConstraints(minWidth: double.infinity),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text("hi"),
Text("world"),
],
),
);
  • Center 组件
  • 修改crossAxisAlignment
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, // 使子widget填满水平空间
children: <Widget>[
Text("hi", textAlign: TextAlign.center,), // 需要调整文本
Text("world", textAlign: TextAlign.center,),
Container(
color: Colors.green,
child: Text("hello world", style: TextStyle(fontSize: 30.0),),
),
],
),

特殊情况

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

Container(
color: Colors.green,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max, //有效,外层Colum高度为整个屏幕
children: <Widget>[
Container(
color: Colors.red,
child: Column(
mainAxisSize: MainAxisSize.max,//无效,内层Colum高度为实际高度
children: <Widget>[
Text("hello world "),
Text("I am Jack "),
],
),
)
],
),
),
);

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

  Container(
color: Colors.green,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max, //有效,外层Colum高度为整个屏幕
children: <Widget>[
Expanded(
child: Container(
color: Colors.red,
child: Column(
mainAxisAlignment: MainAxisAlignment.center, //垂直方向居中对齐
children: <Widget>[
Text("hello world "),
Text("I am Jack "),
],
),
),
)
],
),
),
),

弹性布局(Flex)

弹性布局允许子组件按照一定比例来分配父容器空间。弹性布局的概念在其他UI系统中也都存在,如 H5 中的弹性盒子布局。Flutter 中的弹性布局主要通过Flex和Expanded来配合实现。

Flex

Flex是一个抽象类,是RowColumn的基类,用于定义一个灵活的布局模型。Flex布局允许其子widget沿着主轴(main axis)线性排列,子widget的大小可以按比例分配。 Flex的主要属性包括:

  • direction:确定主轴的方向,水平或垂直
  • mainAxisAlignmentcrossAxisAlignment:控制子widget在主轴和交叉轴上的对齐方式
  • children:包含一系列子widget

    在实际使用中,通常不会直接使用Flex,而是使用其具体实现Row(水平布局)或Column(垂直布局)

Expanded

Expanded是一个widget,它可以包裹一个子widget,并按照其在Flex(如RowColumn)中的flex因子来分配空间。使用Expanded可以让子widget填充未被其他子widget占用的空间,或按比例分配空间。 Expanded的主要属性包括:

  • flex:一个整数,表示子widget的弹性系数,用于确定它占据的空间比例。默认值为1
  • child: widget

例子

基于ColumnRow:

  body: Column(
children: <Widget>[
Expanded(
flex: 1, // 占用剩余空间的1/3
child: Container(color: Colors.red),
),
Expanded(
flex: 2, // 占用剩余空间的2/3
child: Container(color: Colors.green),
),
Row(
children: <Widget>[
Expanded(
child: Container(color: Colors.blue, height: 100),
),
Container(color: Colors.yellow, width: 50, height: 100),
Expanded(
child: Container(color: Colors.blue, height: 100),
),
],
),
],
),

基于Flex:

示例中的Spacer的功能是占用指定比例的空间,实际上它只是Expanded的一个包装类

body: Column(
children: <Widget>[
//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,
),
),
],
),
Padding(
padding: const EdgeInsets.only(top: 20.0),
child: SizedBox(
height: 100.0,
//Flex的三个子widget,在垂直方向按2:1:1来占用100像素的空间
child: Flex(
direction: Axis.vertical,
children: <Widget>[
Expanded(
flex: 2,
child: Container(
height: 30.0,
color: Colors.red,
),
),
Spacer(
flex: 1,
),
Expanded(
flex: 1,
child: Container(
height: 30.0,
color: Colors.green,
),
),
],
),
),
),
],
)

流式布局(Wrap、Flow)

WrapFlow是Flutter中用于布局的两个高级widget,它们提供比传统的RowColumn更多的灵活性和复杂性。

Wrap

Wrap布局可以在水平或垂直方向上排列其子widget,并且可以自动换行或换列。这使得Wrap非常适合创建一个需要自适应空间的布局,比如标签组或者有多个子项且空间受限的情况。

可以认为WrapFlex(包括RowColumn)除了超出显示范围后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的很多属性在Row(包括FlexColumn)中也有,如directioncrossAxisAlignmenttextDirectionverticalDirection等,这些参数意义是相同的。

主要属性:

  • direction:主轴方向,可以是水平或垂直
  • alignmentrunAlignment:分别控制主轴和交叉轴上的对齐方式
  • spacing:子widget在主轴方向上的间距
  • runSpacing:子widget在纵轴(交叉轴)方向上的间距,即行与行之间的间距

示例:

body: Wrap(
spacing: 8.0, // 主轴(水平)方向间距
runSpacing: 4.0, // 纵轴(垂直)方向间距
alignment: WrapAlignment.center, // 沿主轴方向居中
children: <Widget>[
Chip(
avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text('A')),
label: Text('Hamilton'),
),
Chip(
avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text('M')),
label: Text('Lafayette'),
),
Chip(
avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text('M')),
label: Text('Lafayette'),
),
Chip(
avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text('M')),
label: Text('Lafayette'),
),
// 更多Chip...
],
)

Flow

Flow是一个提供高性能自定义布局策略的widget。与Wrap相比,Flow更加复杂和灵活,但也更难以使用。Flow需要开发者重写FlowDelegate来自定义布局策略。

我们一般很少会使用Flow,因为其过于复杂,需要自己实现子 widget 的位置转换,在很多场景下首先要考虑的是Wrap是否满足需求。Flow主要用于一些需要自定义布局策略或性能要求较高(如动画中)的场景。

看到这里,就没空实践了~ 4.5 流式布局(Wrap、Flow) | 《Flutter实战·第二版》

Flow有如下优点:

  • 性能好;Flow是一个对子组件尺寸以及位置调整非常高效的控件,Flow用转换矩阵在对子组件进行位置调整的时候进行了优化:在Flow定位过后,如果子组件的尺寸或者位置发生了变化,在FlowDelegate中的paintChildren()方法中调用context.paintChild 进行重绘,而context.paintChild在重绘时使用了转换矩阵,并没有实际调整组件位置。
  • 灵活;由于我们需要自己实现FlowDelegatepaintChildren()方法,所以我们需要自己计算每一个组件的位置,因此,可以自定义布局策略。 缺点:
  • 使用复杂。
  • Flow 不能自适应子组件大小,必须通过指定父容器大小或实现TestFlowDelegategetSize返回固定大小。

层叠布局(Stack、Positioned)

层叠布局和 Web 中的绝对定位、Android 中的 Frame 布局是相似的,子组件可以根据距父容器四个角的位置来确定自身的位置。层叠布局允许子组件按照代码中声明的顺序堆叠起来。 Flutter中使用StackPositioned这两个组件来配合实现绝对定位。Stack允许子组件堆叠,而Positioned用于根据Stack的四个角来确定子组件的位置。

先看一个小例子: Stack包含了三个子widget:一个大的红色Container作为背景,一个较小的绿色Container通过Positioned定位在红色Container的左上角,最上层是一个Textwidget,同样通过Positioned定位。

body: Stack(
alignment: Alignment.center, // 未定位widget对齐方式
children: <Widget>[
Container(
width: 300.0,
height: 300.0,
color: Colors.red,
),
Positioned(
left: 50.0,
top: 50.0,
child: Container(
width: 200.0,
height: 200.0,
color: Colors.green,
),
),
Positioned(
left: 100.0,
top: 100.0,
child: Text(
'Stack布局',
style: TextStyle(fontSize: 20.0, color: Colors.white),
),
),
],
),

Stack

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

Stack允许其子widget堆叠在一起。默认情况下,Stack中的第一个子widget会位于底部,后面的子widget依次堆叠在上面。可以使用Stackalignment属性来指定如何对齐这些子widget。

属性描述
alignment决定如何去对齐没有定位(没有使用Positioned)或部分定位的子组件。所谓部分定位,在这里特指没有在某一个轴上定位:leftright为横轴,topbottom为纵轴,只要包含某个轴上的一个定位属性就算在该轴上有定位
textDirectionRowWraptextDirection功能一样,都用于确定alignment对齐的参考系,即:textDirection的值为TextDirection.ltr,则alignmentstart代表左,end代表右,即从左往右的顺序;textDirection的值为TextDirection.rtl,则alignment的start代表右,end代表左,即从右往左的顺序
fit用于确定没有定位的子组件如何去适应Stack的大小。StackFit.loose表示使用子组件的大小,StackFit.expand表示扩伸到Stack的大小
clipBehavior决定对超出Stack显示空间的部分如何剪裁,Clip枚举类中定义了剪裁的方式,Clip.hardEdge 表示直接剪裁,不应用抗锯齿,更多信息可以查看源码注释

Stack本身并不直接接受宽度和高度参数来设置其大小,将Stack包裹在一个ContainerSizedBox中,并在这个外部widget上设置宽度和高度。这是设置Stack尺寸最直接的方法。

Positioned

Positioned 只能嵌套在Stack中,如果在不想调整结构的情况下可以使用Align布局

const Positioned({
Key? key,
this.left,
this.top,
this.right,
this.bottom,
this.width,
this.height,
required Widget child,
})

Positioned用于给Stack内的子widget指定具体的位置。通过Positioned,可以控制子widget的左、顶、右、底边距,实现精确布局。 主要属性:

  • lefttoprightbottom:用于确定widget距离Stack的四边的距离。
  • widthheight:可以指定Positioned widget的宽度和高度。

示例: Stack指定一个fit属性

body: Stack(
alignment:Alignment.center ,
fit: StackFit.expand, //未定位widget占满Stack整个空间
children: <Widget>[
Positioned(
left: 18.0,
child: Text("被遮住了"),
),
Container(child: Text("Hello world",style: TextStyle(color: Colors.white)),
color: Colors.red,
),
Positioned(
top: 18.0,
child: Text("Your friend"),
)
],
),

由于第二个子文本组件没有定位,所以fit属性会对它起作用,就会占满Stack。由于Stack子元素是堆叠的,所以第一个子文本组件被第二个遮住了,而第三个在最上层,所以可以正常显示

对齐与相对定位(Align、Center)

Align widget 在 Flutter 中用于对齐和定位其子 widget。通过 Align,你可以控制子 widget 在父 widget 中的具体位置,无论父 widget 的大小如何变化,Align 都能确保子 widget 根据设定的对齐方式定位。

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

Align

alignmentAlign widget 的主要属性,用于确定子 widget 的对齐方式。alignment 属性接受一个 Alignment 对象,如 Alignment.centerAlignment.topRightAlignment.bottomLeft 等。Alignment 类中预定义了一系列常用的对齐方式,同时你也可以通过 Alignment(x, y) 来自定义对齐方式,其中 xy 是从 -1.01.0 的值,分别代表水平和垂直方向
widthFactor、heightFactor是用于确定Align 组件本身宽高的属性
它们是两个缩放因子,会分别乘以子元素的宽、高,最终的结果就是Align 组件的宽高。如果值为null,则组件的宽高将会占用尽可能多的空间。

如果我们不显式指定宽高,而通过同时指定widthFactorheightFactor 为 2 也是可以达到同样的效果:

Align(
widthFactor: 2, // 因为`FlutterLogo`的宽高为 60,则`Align`的最终宽高都为`2*60=120`
heightFactor: 2,
alignment: Alignment.topRight,
child: FlutterLogo(
size: 60,
),
),

Align和Stack对比

可以看到,AlignStack/Positioned都可以用于指定子元素相对于父元素的偏移,但它们还是有两个主要区别:

  1. 定位参考系统不同;Stack/Positioned定位的参考系可以是父容器矩形的四个顶点;而Align则需要先通过alignment 参数来确定坐标原点,不同的alignment会对应不同原点,最终的偏移是需要通过alignment的转换公式来计算出。
  2. Stack可以有多个子元素,并且子元素可以堆叠,而Align只能有一个子元素,不存在堆叠。

Alignment

与css中的clip-path: polygon(0 0, 0 100%)不同,css中的基点为左上角,而Alignment为元素正中心。所以优先用FractionalOffset进行偏移布局更容易理解。

Alignment Widget会以矩形的中心点作为坐标原点,即Alignment(0.0, 0.0) 。xy的值从-1到1分别代表矩形左边到右边的距离和顶部到底边的距离,因此2个水平(或垂直)单位则等于矩形的宽(或高),如Alignment(-1.0, -1.0) 代表矩形的左侧顶点,而Alignment(1.0, 1.0)代表右侧底部终点,而Alignment(1.0, -1.0) 则正是右侧顶点,即Alignment.topRight。为了使用方便,矩形的原点、四个顶点,以及四条边的终点在Alignment类中都已经定义为了静态常量。

Alignment可以通过其坐标转换公式将其坐标转为子元素的具体偏移坐标:

实际偏移 = (Alignment.x * (parentWidth - childWidth) / 2 + (parentWidth - childWidth) / 2,
Alignment.y * (parentHeight - childHeight) / 2 + (parentHeight - childHeight) / 2)

示例: 最终计算的偏移结果为:(0, 30)

body: Container(
color: Colors.red,
child: Align(
widthFactor: 2,
heightFactor: 2,
alignment: Alignment(-1,0.0),
child: FlutterLogo(
size: 60,
),
),)

FractionalOffset

FractionalOffset 继承自 Alignment,它和 Alignment唯一的区别就是坐标原点不同!FractionalOffset 的坐标原点为矩形的左侧顶点,这和Css布局系统一致

FractionalOffset的坐标转换公式为:

实际偏移 = (FractionalOffse.x * (parentWidth - childWidth), FractionalOffse.y * (parentHeight - childHeight))

Center

将其单个子 widget 居中对齐在其父 widget 中。Center widget 可以在垂直方向、水平方向或者两者上同时居中子 widget。它非常适合用于需要简单居中布局的场景,如将文本、按钮、图标或自定义子 widget 居中显示。

Center继承自Align,它比Align只少了一个alignment 参数;由于Align的构造函数中alignment 值为Alignment.center。 所以,我们可以认为Center组件其实是对齐方式确定(Alignment.center)了的Align

主要属性:

  • childCenter widget 的唯一子 widget。这个子 widget 将被居中对齐。
  • widthFactorheightFactor:这些是可选属性,用于确定 Center widget 自身的大小相对于其子 widget 的大小。如果设置了 widthFactorheightFactor,则 Center widget 的大小将是其子 widget 大小的倍数;如果未设置,Center widget 将尽可能大地扩展以填满父 widget 的可用空间
Center( child: Text('这是一个居中的文本'), ),

LayoutBuilder、AfterLayout

LayoutBuilder

LayoutBuilder是一个widget,它可以构建一个依赖于其父widget的尺寸的widget树。LayoutBuilder会将父widget的约束传递给它的builder函数,你可以根据这些约束来决定如何构建子widget。这对于创建响应父widget大小变化的布局非常有用。 LayoutBuilder的builder函数提供了一个BuildContext和一个BoxConstraints对象,后者描述了父widget对子widget的约束(例如,最大/最小宽度和高度)

示例

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


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

final List<Widget> children;


Widget build(BuildContext context) {
// 通过 LayoutBuilder 拿到父组件传递的约束,然后判断 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);
}
},
);
}
}


class LayoutBuilderRoute extends StatelessWidget {
const LayoutBuilderRoute({Key? key}) : super(key: key);


Widget build(BuildContext context) {
var _children = List.filled(6, Text("A"));
// Column在本示例中在水平方向的最大宽度为屏幕的宽度
return Column(
children: [
// 限制宽度为190,小于 200
Container(
width: 190,
color: Colors.green,
child: ResponsiveColumn(children: _children),
),
ResponsiveColumn(children: _children),
// LayoutLogPrint(child:Text("xx")) // 下面介绍
],
);
}
}

它非常实用且重要,它主要有两个使用场景:

  1. 可以使用 LayoutBuilder 来根据设备的尺寸来实现响应式布局。
  2. LayoutBuilder 可以帮我们高效排查问题。比如我们在遇到布局问题或者想调试组件树中某一个节点布局的约束时 LayoutBuilder 就很有用。

打印布局时的约束信息

注意!我们的大前提是盒模型布局,如果是Sliver 布局,可以使用 SliverLayoutBuiler 来打印。

class LayoutLogPrint<T> extends StatelessWidget {
const LayoutLogPrint({
Key? key,
this.tag,
required this.child,
}) : super(key: key);

final Widget child; final T? tag; //指定日志tag

@override Widget build(BuildContext context) { return LayoutBuilder(builder: (_, constraints) { // assert在编译release版本时会被去除 assert(() { print('${tag ?? key ?? child}: $constraints'); return true; }()); return child; }); } }

LayoutLogPrint(child:Text("xx"))

![](../../static/docs/Pasted%20image%2020240131180041.jpeg)
可以看到 Text("xx") 的显示空间最大宽度为 393,最大高度未做限制 。

### AfterLayout
`AfterLayout`是一个Dart mixin,不是一个widget。它提供了一种在widget布局完成后执行代码的方式。
> Flutter响应式UI框架,而命令式UI框架最大的不同就是:大多数情况下开发者只需要关注数据的变化,因此如果我们想在 Flutter 中获取某个组件的大小和位置就会很困难。个人理解`就像jQuery和Vue`的区别。

使用`AfterLayout` mixin时,你的widget状态类还需要扩展`State<T>`,其中`T`是你的widget类。`afterFirstLayout`方法在widget第一次布局完成后被调用,这对于执行依赖于widget大小的初始化任务非常有用。
简单示例:
```dart
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> with AfterLayoutMixin<MyWidget> {
@override
void afterFirstLayout(BuildContext context) {
// 布局完成后的操作
}

@override
Widget build(BuildContext context) {
return Container(); // 你的widget布局
}
}

获取组件相对于某个父组件的坐标

4.8 LayoutBuilder、AfterLayout | 《Flutter实战·第二版》