44、Flutter之组件布局原理与约束(constraints)
尺寸限制类容器用于限制容器大小,Flutter中提供了多种这样的容器,如ConstrainedBox
、SizedBox
、UnconstrainedBox
、AspectRatio
等,本节将介绍一些常用的。
Flutter 中有两种布局模型:
- 基于 RenderBox 的盒模型布局。
- 基于 Sliver ( RenderSliver ) 按需加载列表布局。
两种布局方式在细节上略有差异,但大体流程相同,布局流程如下:
- 上层组件向下层组件传递约束(constraints)条件。
- 下层组件确定自己的大小,然后告诉上层组件。注意下层组件的大小必须符合父组件的约束。
- 上层组件确定下层组件相对于自身的偏移和确定自身的大小(大多数情况下会根据子组件的大小来确定自身的大小)。
比如,父组件传递给子组件的约束是“最大宽高不能超过100,最小宽高为0”,如果我们给子组件设置宽高都为200,则子组件最终的大小是100*100,因为任何时候子组件都必须先遵守父组件的约束,在此基础上再应用子组件约束(相当于父组件的约束和自身的大小求一个交集)。
本节我们主要看一下盒模型布局,然后会在可滚动组件一章中介绍 Sliver 布局模型。盒模型布局组件有两个特点:
- 组件对应的渲染对象都继承自 RenderBox 类。在本书后面文章中如果提到某个组件是 RenderBox,则指它是基于盒模型布局的,而不是说组件是 RenderBox 类的实例。
- 在布局过程中父级传递给子级的约束信息由 BoxConstraints 描述。
BoxConstraints
BoxConstraints 是盒模型布局过程中父渲染对象传递给子渲染对象的约束信息,包含最大宽高信息,子组件大小需要在约束的范围内,BoxConstraints 默认的构造函数如下:
const BoxConstraints({this.minWidth = 0.0, //最小宽度this.maxWidth = double.infinity, //最大宽度this.minHeight = 0.0, //最小高度this.maxHeight = double.infinity //最大高度
})
它包含 4 个属性,BoxConstraints还定义了一些便捷的构造函数,用于快速生成特定限制规则的BoxConstraints,如BoxConstraints.tight(Size size)
,它可以生成固定宽高的限制;BoxConstraints.expand()
可以生成一个尽可能大的用以填充另一个容器的BoxConstraints。除此之外还有一些其它的便捷函数,读者可以查看类定义。另外我们会在后面深入介绍布局原理时还会讨论 Constraints,在这里,读者只需知道父级组件是通过 BoxConstraints 来描述对子组件可用的空间范围即可。
约定:为了描述方便,如果我们说一个组件不约束其子组件或者取消对子组件约束时是指对子组件约束的最大宽高为无限大,而最小宽高为0,相当于子组件完全可以自己根据需要的空间来确定自己的大小。
下面我们介绍一些常用的通过约束限制子组件大小的组件。
ConstrainedBox
ConstrainedBox
用于对子组件添加额外的约束。例如,如果你想让子组件的最小高度是80像素,你可以使用const BoxConstraints(minHeight: 80.0)
作为子组件的约束。
示例
我们先定义一个redBox
,它是一个背景颜色为红色的盒子,不指定它的宽度和高度:
Widget redBox = DecoratedBox(decoration: BoxDecoration(color: Colors.red),
);
我们实现一个最小高度为50,宽度尽可能大的红色容器。
ConstrainedBox(constraints: BoxConstraints(minWidth: double.infinity, //宽度尽可能大minHeight: 50.0 //最小高度为50像素),child: Container(height: 5.0, child: redBox ,),
)
运行效果:
可以看到,我们虽然将Container的高度设置为5像素,但是最终却是50像素,这正是ConstrainedBox的最小高度限制生效了。如果将Container的高度设置为80像素,那么最终红色区域的高度也会是80像素,因为在此示例中,ConstrainedBox只限制了最小高度,并未限制最大高度。
SizedBox
SizedBox
用于给子元素指定固定的宽高,如:
SizedBox(width: 80.0,height: 80.0,child: redBox
)
运行效果所示:
实际上SizedBox
只是ConstrainedBox
的一个定制,上面代码等价于:
ConstrainedBox(constraints: BoxConstraints.tightFor(width: 80.0,height: 80.0),child: redBox,
)
而BoxConstraints.tightFor(width: 80.0,height: 80.0)
等价于:
BoxConstraints(minHeight: 80.0,maxHeight: 80.0,minWidth: 80.0,maxWidth: 80.0)
而实际上ConstrainedBox
和SizedBox
都是通过RenderConstrainedBox
来渲染的,我们可以看到ConstrainedBox
和SizedBox
的createRenderObject()
方法都返回的是一个RenderConstrainedBox
对象:
@override
RenderConstrainedBox createRenderObject(BuildContext context) {return RenderConstrainedBox(additionalConstraints: ...,);
}
多重限制
如果某一个组件有多个父级ConstrainedBox
限制,那么最终会是哪个生效?我们看一个例子:
ConstrainedBox(constraints: BoxConstraints(minWidth: 60.0, minHeight: 60.0), //父child: ConstrainedBox(constraints: BoxConstraints(minWidth: 90.0, minHeight: 20.0),//子child: redBox,),
)
上面我们有父子两个ConstrainedBox
,他们的约束条件不同,运行后效果如图5-4所示:
最终显示效果是宽90,高60,也就是说是子ConstrainedBox
的minWidth
生效,而minHeight
是父ConstrainedBox
生效。单凭这个例子,我们还总结不出什么规律,我们将上例中父子约束条件换一下:
ConstrainedBox(constraints: BoxConstraints(minWidth: 90.0, minHeight: 20.0),child: ConstrainedBox(constraints: BoxConstraints(minWidth: 60.0, minHeight: 60.0),child: redBox,)
)
运行效果如图5-5所示:
最终的显示效果仍然是90,高60,效果相同,但意义不同,因为此时minWidth
生效的是父ConstrainedBox
,而minHeight
是子ConstrainedBox
生效。
通过上面示例,我们发现有多重限制时,对于minWidth
和minHeight
来说,是取父子中相应数值较大的。实际上,只有这样才能保证父限制与子限制不冲突。
UnconstrainedBox
虽然任何时候子组件都必须遵守其父组件的约束,但前提条件是它们必须是父子关系,假如有一个组件 A,它的子组件是B,B 的子组件是 C,则 C 必须遵守 B 的约束,同时 B 必须遵守 A 的约束,但是 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的红色框。但是由于UnconstrainedBox
“去除”了父ConstrainedBox
的限制,则最终会按照子ConstrainedBox
的限制来绘制redBox
,即90×20:
但是,读者请注意,UnconstrainedBox
对父组件限制的“去除”并非是真正的去除:上面例子中虽然红色区域大小是90×20,但上方仍然有80的空白空间。也就是说父限制的minHeight
(100.0)仍然是生效的,只不过它不影响最终子元素redBox
的大小,但仍然还是占有相应的空间,可以认为此时的父ConstrainedBox
是作用于子UnconstrainedBox
上,而redBox
只受子ConstrainedBox
限制,这一点请读者务必注意。
那么有什么方法可以彻底去除父ConstrainedBox
的限制吗?答案是否定的!请牢记,任何时候子组件都必须遵守其父组件的约束,所以在此提示读者,在定义一个通用的组件时,如果要对子组件指定约束,那么一定要注意,因为一旦指定约束条件,子组件自身就不能违反约束。
在实际开发中,当我们发现已经使用 SizedBox
或 ConstrainedBox
给子元素指定了固定宽高,但是仍然没有效果时,几乎可以断定:已经有父组件指定了约束!举个例子,如 Material 组件库中的AppBar
(导航栏)的右侧菜单中,我们使用SizedBox
指定了 loading 按钮的大小,代码如下:
AppBar(title: Text(title),actions: <Widget>[SizedBox(width: 20, height: 20,child: CircularProgressIndicator(strokeWidth: 3,valueColor: AlwaysStoppedAnimation(Colors.white70),),)],
)
上面代码运行后,效果如图5-7所示:
我们会发现右侧loading按钮大小并没有发生变化!这正是因为AppBar
中已经指定了actions
按钮的约束条件,所以我们要自定义loading按钮大小,就必须通过UnconstrainedBox
来 “去除” 父元素的限制,代码如下:
AppBar(title: Text(title),actions: <Widget>[UnconstrainedBox(child: SizedBox(width: 20,height: 20,child: CircularProgressIndicator(strokeWidth: 3,valueColor: AlwaysStoppedAnimation(Colors.white70),),),)],
)
运行后效果如图所示:
生效了!实际上将 UnconstrainedBox 换成 Center 或者 Align 也是可以的,至于为什么,我们会在本书后面布局原理相关的章节中解释。
另外,需要注意,UnconstrainedBox 虽然在其子组件布局时可以取消约束(子组件可以为无限大),但是 UnconstrainedBox 自身是受其父组件约束的,所以当 UnconstrainedBox 随着其子组件变大后,如果UnconstrainedBox 的大小超过它父组件约束时,也会导致溢出报错,比如:
Column(children: <Widget>[UnconstrainedBox(alignment: Alignment.topLeft,child: Padding(padding: const EdgeInsets.all(16),child: Row(children: [Text('xx' * 30)]),),),]
运行效果如下:
![](/assets/blank.gif)
文本已经超过屏幕宽度,溢出了。
其他约束容器
除了上面介绍的这些常用的尺寸限制类容器外,还有一些其他的尺寸限制类容器,比如AspectRatio
,它可以指定子组件的长宽比、LimitedBox
用于指定最大宽高、FractionallySizedBox
可以根据父容器宽高的百分比来设置子组件宽高等,由于这些容器使用起来都比较简单。
44、Flutter之组件布局原理与约束(constraints)相关推荐
- 【Flutter】Flutter 布局组件 ( 布局组件简介 | Row 组件 | Column 组件 | SizedBox 组件 | ClipOval 组件 )
文章目录 一.Flutter 布局相关的组件简介 二.Row 和 Column 组件 三.SizedBox 组件 四.ClipOval 组件 五. 完整代码示例 六. 相关资源 一.Flutter 布 ...
- flutter listview 滚动到指定位置_Flutter 布局原理及实战
1. Flutter UI架构 Flutter将视图数据抽象成为三个部分,即Widget树.Element树和RenderObject树. Widget树:控件的配置信息,不涉及渲染,更新代价极低. ...
- 拆!对比详解 Flutter Widget 和 CSS,你关心的布局原理都在这儿了
简介: 这篇文章专门对比 Flutter Widget 的布局原理和 CSS 布局原理的差异,分享在对接过程中会遇到的问题和解决方案,帮大家理一理思路,内容可以分为这几部分:1.CSS 和 Widge ...
- Flutter 流式布局组件
Flutter 流式布局组件 简述 类似于Android中的FlexboxLayout布局. Wrap spacing: 主轴间距.runSpacing: 纵轴间距.direction: 布局方向.a ...
- 【约束布局】ConstraintLayout 约束布局 ( 简介 | 引入依赖 | 基本操作 | 垂直定位约束 | 角度定位约束 | 基线约束 )
文章目录 一. ConstraintLayout 简介 1. 引入 约束 布局 ( 1 ) 约束性布局 作用 和 简介 2. 约束 简介 ( 1 ) 约束个数要求 ( 2 ) 约束设置 与 显示位置 ...
- 官方文档——一篇文章弄懂Flutter中的布局
来自Flutter中文资源主页https://flutter.cn/ 原文:https://flutter.cn/docs/development/ui/layout Flutter 中的布局 要点 ...
- Android布局原理与优化
Android布局原理与优化 目录: 绘制原理 CPU与GPU Android 图形系统的整体架构 RenderThread 硬件加速和软件绘制 invalidate软件绘制流程 invalidate ...
- Flutter教程04——布局01
目录 1.约束 1.1BoxConstraints 1.2ConstrainedBox 1.3SizedBox 1.4多重限制 1.5UnconstrainedBox 2.线性布局 2.1Row 2. ...
- Android组件化原理
Android组件化原理 什么是组件化? 为什么使用组件化? 一步步搭建组件化 组件化开发要注意的几点问题 1.新建模块 2.统一Gradle版本号 3.创建基础库 4.组件模式和集成模式转换 5.A ...
最新文章
- 【c语言】测量最长字符串
- java方法生命周期_java – Servlet的生命周期及其方法
- 80后程序员月薪30K+感慨中年危机,面试必问!
- java面向对象之父类的引用指向子类的对象
- 白帽子讲web安全——访问控制
- Eclipse导出APK文件报错 android lint problem
- python threading.Thread
- 项目业务工作笔记001---发改委职责
- appium+Python真机运行测试demo的方法
- bzoj 3611: [Heoi2014]大工程(虚树+树形DP)
- mysql5.5 vsftpd_vsftpd-2.0.5+mysql-5.5+pam_mysql构建虚拟用户访问
- iOS原生地图开发进阶——使用导航和附近兴趣点检索
- 1078 Hashing (25 分) 解决冲突采用正向增加的二次探查法
- 单片机----数码管(138译码器)显示日期
- fclk if总线_技嘉B550手把手超频指南,光威血影为例
- 如何在CAD图纸中添加文字
- 三种视觉自动化检测的解决方案
- 是否有唯一的 Android 设备 ID?
- FMG首席执行官被控误导股市
- 基于MATLAB图像处理