Flutter布局指南之深入理解BoxConstraints

article/2025/10/7 6:08:49

1f4067ca66dbfabd35f32fd50bf35350.png

点击上方蓝字关注我,知识会给你力量

06161b8604cd8acff931172c43de273d.png

强烈建议先看下这篇文章——Flutter你竟是这样的布局

不管你是Android开发,还是Flutter开发,当你开始使用Flutter茫茫多的Widget时,可能会猜测Widget在屏幕上的尺寸和位置,但事实上,你会经历多次错误和失败,Flutter的Widget并不会总是像你想象的那样进行布局。

如果不了解Widget的约束条件是如何应用的,就很难预测Widget的尺寸。很多时候,你根本不知道为什么一个Widget的尺寸比你预期的要大,或者比你想象的要小。因此,在这篇文章中,让我们试着了解约束条件是如何工作的,以及对Widget尺寸的影响。

那么,Flutter中的约束究竟是什么?

Flutter中的约束是对一个Widget的宽度和高度的简单限制

这些限制是通过BoxConstraints对象指定的。在BoxConstraints对象中,尺寸限制被设置为minWidth、maxWidth、minHeight和maxHeight属性。

这4个宽度和高度属性可以有从0到double.infinity的任何数值。double.infinity这个值意味着Widget可以有无限的尺寸。

你可能会遇到有界和无界约束这两个术语。有界意味着有限的约束,即一些特定的尺寸,而无界约束意味着无限的尺寸,即无穷大。

为了设置你想要的约束,你可以使用BoxConstraints构造函数。

BoxConstraints( {double minWidth: 0.0, double maxWidth: double.infinity, double minHeight: 0.0, double maxHeight: double.infinity} )

现在,在我们开始讨论Flutter中的约束如何工作之前,让我们先理清一些关于约束的重要术语。

Tight constraints、Loose constraints和Unbounded constraints

假设我们有一个Widget,其BoxConstraints的maxWidth和maxHeight分别等于屏幕宽度和屏幕高度。而现在,如果我们想强迫这个Widget填满整个屏幕的宽度和高度,我们必须将Widget的BoxConstraints的minWidth等于屏幕宽度,minHeight等于屏幕高度。所以在这种情况下,当我们通过保持其minWidth、maxWidth等于目标填充宽度,保持其minHeight、maxHeight等于目标填充高度来强制一个Widget填充一个特定的尺寸时,我们说我们已经对该Widget设置了Tight约束。

另一方面,如果我们让Widget在其minWidth到maxWidth,minHeight到maxHeight的范围内拥有任何宽度和高度,那么我们就说我们对Widget设置了Loose约束。

所以现在这意味着为了应用Tight约束,你必须将BoxConstraints的minWidth设置为maxWidth,minHeight设置为maxHeight。你也可以单独为宽度或高度应用Tight约束。我们称其为应用tightWidth或tightHeight。在文章的其余部分,Tight约束一词将指Tight宽度、Tight高度或两者都是。

如果我们将maxWidth、maxHeight或两者都设置为double.infinity,那么我们就说我们在一个widget上设置了Unbounded约束。如果你把minWidth分别设置为double.infinity或0,那么一个Unbounded约束也可以同时是一个Tight束或Loose约束。

我们可以使用BoxConstraints构造函数来设置Tight约束、Loose约束和Unbounded约束。

BoxConstraints.tight( Size size )

这将把minWidth, maxWidth设置为size.width,minHeight, maxHeight设置为size.height。因此,现在任何应用了这些约束的Widget都将被强制填充到size.width和size.height的精确尺寸中。

BoxConstraints.tightFor( {double width, double height} )

你可以使用这个构造函数并传递宽度或高度来分别设置Tight宽度或Tight高度,或者同时传递宽度和高度来设置两者的Tight约束。这个构造函数有一个变种,叫做BoxConstraints.tightForFinite()。只有当你没有传递无限大的宽度或高度时,才会设置Tight约束。

BoxConstraints.loose( Size size )

这个构造函数设置了Loose约束,最小宽度和最小高度为0,最大宽度和最大高度为size对象所提供的,也就是说,一个Widget可以在0.0到size.width和0到size.height范围内自由选择任何尺寸。

BoxConstraints.expand()

对传递给它的宽度或高度设置Tight约束,并对未传递给构造函数的宽度或高度参数设置Unbounded约束,即double.infinity。

要设置Unbounded约束,你也可以使用默认的BoxConstraints构造函数,并将maxWidth或maxHeight或两者都设置为double.infinity。

约束的工作原理

让我们举个简单的例子,一个带有Container的MaterialApp,代码如下所示。

965433b6a353160f55f985a1defad5c4.png
img

runApp()方法将MyAppWidget设置为Root Widget。当framework渲染MyApp时,它在布局过程中被赋予约束,迫使它填满整个屏幕。换句话说,MyApp被赋予了与屏幕宽度和高度相等的尺寸的Tight约束。然后,MyApp在它的孩子MaterialAppWidget上设置约束,而后者又在它的孩子ContainerWidget上设置约束。

在这里,Container从它的父组件MaterialApp收到了关于屏幕尺寸的Tight约束。因此,即使Container被声明为具有100像素的特定宽度和高度,它也被强迫填满整个屏幕。

上面的示例代码是在一个宽度为392.7像素,高度为737.5像素的设备上运行的。(注意:这些是逻辑像素)。下图中突出显示的部分显示了ContainerWidget收到的严格约束,BoxConstraints(w=392.7, h=737.5)和Container的最终尺寸为392.7宽和737.5,同时忽略了它的额外约束w=100.0, h=100.0。你可以在Flutter Visual Inspector -> Widgets下查看这些内容。

cde7855e5876b5ac1375cdf29883b68a.png
img

现在让我们把Container包在一个Scaffold里面,如下面的代码所示。当我们运行这段代码时,我们会得到尺寸为w=100.0, h=100.0的Container。

28b226252799c3ef4b0e700593dda929.png
img

那么为什么Container现在改变了它的大小呢?

这是因为Scaffold对Container设置了Loose约束,即使Scaffold本身从它的父级接受了Tight约束。由于Container有Loose约束,它可以自由地选择最小和最大约束之间的任何尺寸,在这种情况下,它的尺寸是0到屏幕尺寸。但是Container本身有额外的约束,宽度为100,高度为100。所以Container选择了100x100,因为它是在Loose约束下。

当约束条件从父代传递到子代时会发生什么?

上面的例子表明,一个父Widget不可能简单地将它收到的约束传递给它的孩子。相反,父Widget可以将其子Widget的约束从Tight变为Loose,反之亦然。如果父Widget设置了Tight约束,那么子Widget别无选择,只能填补其约束中设置的精确尺寸。另一方面,如果父方设置了宽松的约束,那么子Widget就可以自由地选择自己的尺寸,直到最大宽度或最大高度。在Loose约束下,一些Widget占用了允许的最大尺寸,而另一些Widget只占用了它需要的最小尺寸。

这使得Widget的约束行为变得有些复杂,因为现在我们需要了解每个Widget在不同条件下的具体行为。

一个Widget最终可能具有的三种尺寸类型

一般来说,最终的Widget尺寸可能最终成为以下三种尺寸之一。

  • 在Loose约束条件下,它可能变得尽可能大。

  • 在Loose约束条件下,它可能会变得尽可能的小。

  • 在Tight约束下,它可能成为一个特定的尺寸。

那么,如何预测屏幕上最终的Widget尺寸?

好吧,首先,你应该知道在不同的条件下,如Tight约束、Loose约束、Unbounded约束、它有一个孩子或它没有更多的孩子或有多个孩子,特定的Widget会选择上述三个选择中的哪一个。

我们必须了解到每个布局Widget的具体行为。所以最好研究一下Flutter的常见布局组件,了解每个Widget在不同条件下的行为。

这里有一些问题可以帮助您预测Widget的大小。

  • 父Widget是否对其子Widget设置了Tight或Loose约束?

  • 子Widget是否有自己的额外约束。如果是这样,由父和子约束产生的综合约束是什么?

  • 子Widget是否覆盖了父Widget的约束?

  • 如果来自父代和子代的综合约束导致子代Widget有Loose约束,那么我们应该检查子Widget的具体行为,它是否会选择变得尽可能大或尽可能小。

  • 是否有来自父Widget的Unbounded约束,子Widget是否也有相同方向的Unbounded约束?

由于布局组件有自己的特定行为,为了正确预测一个Widget的最终尺寸,我们不仅要注意一般的规则,还要注意布局组件的特定约束规则。

最常用的布局Widget之一是Container。Container作为一个父Widget,对其子Widget传递相同的Loose或Tight约束。

下面是Container在不同条件下的最终尺寸:

案例:Container有无限制的父约束,没有孩子,没有对齐。

Container试图根据它给定的高度和宽度尽可能地缩小尺寸。

案例:有父约束、自我约束,如特定的高度、宽度,但没有孩子,没有对齐。

Container试图根据它的父约束和它自己的约束所产生的综合约束来确定尽可能小的尺寸。

案例:有边界的父约束,没有自我约束,没有孩子,没有对齐。

Container扩展以适应父代提供的约束,即Container试图尽可能大的尺寸。

案例:有无界的父约束,无自我约束,有孩子,有对齐。

Container试图将自己的大小围绕着孩子。

案例:有边界的父约束,没有自我约束,有孩子,有对齐。

Container试图扩大以适应父体,然后按照排列方式将子体置于自身之内。

案例:有父约束,无自约束,有子约束

Container将父方的约束传递给子方,并将自己的大小与子方相匹配。

正如你所看到的,如果不了解最常用的布局Widget的具体行为,要预测一个Widget的最终尺寸并不容易。

如何覆盖父约束并控制子Widget的尺寸

Flutter为我们提供了一些有用的小工具Widget,以覆盖父方对子方传递的约束。

案例:删除父Widget在其子Widget上设置的所有约束条件

用UnconstrainedBox包住子Widget。

案例:在父约束边界内为子Widget设置新的尺寸约束

用SizedBox包裹子Widget。我们还可以使用SizedBox的变体,如FractionallySizedBox来设置子Widget的尺寸为总可用空间的一部分,SizedOverflowBox来设置一个特定的尺寸,并允许子Widget溢出。

案例:在父Widget设置的约束条件的同时添加额外的约束条件

用ConstrainedBox包住子Widget

案例:在滚动的父Widget内限制一个子Widget的大小,在其滚动方向上有无限制的约束

用LimitedBox来包裹子Widget

案例:用新的约束覆盖父级约束,甚至允许孩子溢出父级而没有黑色和黄色的条纹警告

在一个OverflowBox中包裹子Widget

案例:缩放子Widget

在一个FittedBox中包裹子Widget

案例:控制行或列Widget内的子Widget尺寸

将每个子Widget包裹在一个Flexible或Expanded中

常见的约束问题和解决方案

Error: BoxConstraints forces an infinite width.

如果有任何来自父方的Unbounded约束,并且子方也有相同方向的Unbounded约束,即宽度或高度方向的Unbounded约束,那么它将导致上述的错误。这个错误是针对宽度的。这是因为Flutter不能渲染无限的尺寸。父方或子方都必须设置一个边界,以便框架知道它需要渲染的尺寸。

像ListView这样的滚动Widget在其滚动方向上有Unbounded约束。因此,如果你给它一个在滚动方向上也有Unbounded约束的子对象,那么同样的错误也会产生。为了解决这个错误,可以使用LimitedBox来包裹子Widget。

Error: RenderFlex children have non-zero flex but incoming height constraints are unbounded

这个错误可能发生在像column这样的Flex Widget中,例如,列的父Widget对它设置了Unbounded约束,而这个column中的一个子Widget的高度被设置为double.infinity,即无界高度约束,那么Flutter将出错,因为它无法确定子Widget的确切尺寸。可以通过使用Flexible或Expanded来包装每个子Widget来解决这个问题。

Black and yellow stripes shown on screen overflow

通常情况下,当文本大小或图像大小不适合在父约束中,它们就会溢出。在这种情况下,你可以使用一个FittedBox来解决这个问题。

Column或Row也可能在它们的子代不适合其主轴时溢出。你可以通过使用Flexible或Expanded来包裹每个子Widget来解决这个问题。或者把column或row改成一个Listview。

总结

一般来说,有三种类型的约束。Tight、Loose的和Unbounded约束。

屏幕将Tight约束传递给根Widget,使其与设备屏幕一样大。然后再往后,每个父Widget都会向其子Widget传递约束。

布局Widget有它们自己的特定行为:

  • 当把约束传递给子代时,父代可以把Tight约束改为Loose约束,或者不加改变地传递。

  • Widget的尺寸在不同的条件下可能是不同的。这取决于各种因素,如它的子尺寸、它的父尺寸、它自己的约束、父约束等。

  • 一般来说,一个Widget会尽可能的大,或者尽可能的小,或者一个特定的尺寸。

  • 使用BoxConstraints构造函数设置约束。

  • 我们也可以使用一些Box Widget来覆盖父级约束,如UnconstrainedBox, SizedBox, ConstrainedBox等。

  • 父约束和子约束中存在的无约束约束会导致渲染错误。Flutter不能渲染无限大的尺寸。

本文部分翻译自https://medium.com/@naresh.idiga/a-deep-dive-into-flutter-constraints-abd3d4c93a6

向大家推荐下我的网站 https://xuyisheng.top/  点击原文一键直达

专注 Android-Kotlin-Flutter 欢迎大家访问

往期推荐

  • flutter与compose的爱恨情仇

  • Flutter混编工程之Engine复用

  • 不懂设计的产品不是好开发

  • Flutter混编工程之轻量化改造

本文原创公众号:群英传,授权转载请联系微信(Tomcat_xu),授权后,请在原创发表24小时后转载。

< END >

作者:徐宜生

更文不易,点个“三连”支持一下👇


http://chatgpt.dhexx.cn/article/r8tL4twQ.shtml

相关文章

vue element-ui el-table表格二次封装 自定义el-table表格组件 vue封装表格组件

CommTable.vue table组件 <template><div><el-table:data"tableData"border:class"tabClass ? tabClass : null":showHeader"showHeader ? showHeader : true":spanMethod"spanMethod ? spanMethod : null"element…

Stage的MinWidth和MinHeight的疑问

设置了Stage的MinWidth和MinHeight,但是显示的时候不是这个高度和宽度&#xff0c;最小化之后再次显示的时候就可以了&#xff0c;奇怪 package stage;import javafx.application.Application; import javafx.event.ActionEvent; import javafx.event.EventHandler; import jav…

php的width是什么意思,minwidth什么意思?min-width怎么设置

很多人刚刚入门css的新手&#xff0c;不知道minwidth什么意思&#xff1f;min-width怎么设置&#xff0c;下面php中文网就带领大家来学习一下min-width。 一&#xff1a;minwidth什么意思 在css中&#xff0c;minwidth是设置段落的最小宽度&#xff0c;使用该属性是设置一个最小…

width和min-width的区别和差异性比较

1、正常情况下&#xff1a; width :给块级元素/行内块 元素设置固定的宽度&#xff0c;或者固定百分比的宽度。 min-width: 当盒子内部元素宽度小于 min-width的值时&#xff0c;盒子宽度为 min-width的值&#xff0c;当盒子内容宽度大于 min-width的值时&#xff0c;盒子随着…

【NOIP2013提高组】华容道

题目背景 NOIP2013 提高组 Day2 试题。 题目描述 小 B 最近迷上了华容道&#xff0c;可是他总是要花很长的时间才能完成一次。于是&#xff0c;他想到用编程来完成华容道&#xff1a;给定一种局面&#xff0c;华容道是否根本就无法完成&#xff0c;如果能完成&#xff0c;最…

【NOIP2013提高组】花匠

题目背景 NOIP2013 提高组 Day2 试题。 题目描述 花匠栋栋种了一排花&#xff0c;每株花都有自己的高度。花儿越长越大&#xff0c;也越来越挤。栋栋决定把这排中的一部分花移走&#xff0c;将剩下的留在原地&#xff0c;使得剩下的花能有空间长大&#xff0c;同时&#xff…

【NOIP2013提高组】积木大赛

题目背景 NOIP2013 提高组 Day2 试题 题目描述 春春幼儿园举办了一年一度的“积木大赛”。今年比赛的内容是搭建一座宽度为 n 的大厦&#xff0c;大厦可以看成由 n 块宽度为 1 的积木组成&#xff0c;第 i 块积木的最终高度需要是 hi。 在搭建开始之前&#xff0c;没有任何…

7.15 NOIP 2013

NOIP 2013 DAY 1 DAY1 T1 转圈游戏 快速幂模板 #include<bits/stdc.h> using namespace std; int n,m,k,x; long long ans; long long cmd(long long a,long long b){long long sum1;for(;a;a>>1){if(a&1){sumsum*b%n;}bb*b%n; } return sum; } int main(…

noip2013 day2

一道纯模拟就可以过&#xff08;水水水&#xff09;。 考试时本蒟蒻甚至写了个线段树&#xff0c;然后发现其实不如直接模拟。 模拟思路&#xff1a; 从1到n枚举每个最长的不为0的序列&#xff0c;每次每个数减去其中剩余的最小值&#xff0c;答案加上这个最小值&#xff0c…

noip2013

D1&#xff1a; T1&#xff1a;快速幂 #include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #include<cstdlib> #include<cmath> #define LL long long using namespace std; LL n,m,k,x; inline LL quickpow(LL…

解题报告:NOIP2013 车站分级(拓扑序递推求解差分约束、建图优化O(n+m)) 超详细讲解

本题是2013年NOIP普及组的压轴题 差分约束裸题。 计算当前线路中最小的级别&#xff08;比较始发站和终点站&#xff09;。 整条线路中所有大于这个级别的都必须停靠 所有未停靠的站点的级别一定小于这个级别 也就是说所有未停靠的即为级别低&#xff0c;记为A 所有停靠的站点…

[NOIP2013]记数问题

[NOIP2013]记数问题 1.题目2.分析3.代码方法1&#xff1a;将每个数字的每一位单独算出方法2&#xff1a;转换为字符串再进行遍历 4.反思总结5.更新日志 1.题目 题目链接 题号&#xff1a;NC16538 时间限制&#xff1a;C/C 1秒&#xff0c;其他语言2秒 空间限制&#xff1a;C/C…

ARMv8体系结构基础04:算术和移位指令

目录 1 数据处理指令概述 2 加法指令详解 2.1 ADD指令 2.1.1 ADD&#xff08;extended register&#xff09;指令编码分析 2.1.2 ADD&#xff08;extended register&#xff09;指令编码验证 2.1.3 ADD&#xff08;immediate&#xff09;指令编码分析 2.1.4 ADD&#xf…

8086CPU指令系统--汇编语言逻辑运算和移位操作指令

文章目录 一、逻辑运算指令1、逻辑‘与’指令 AND2、逻辑‘或’指令 OR3、逻辑“非”指令 NOT4、逻辑“异或” XOR5、测试指令TEST 二、移位指令1&#xff09;非循环移位1、算数左移SAL和逻辑左移SHL2、逻辑右移SHR3、算术右移SAR 2&#xff09;循环移位1、带CF的循环左移 RCL2…

arm64汇编学习-(3)算术与移位指令

arm64汇编学习-&#xff08;3&#xff09;算术与移位指令 1 数据处理指令1.1 check the C condition of adds, adc,cmp1.1.1 测试示例程序1.1.2 执行之前1.1.3 执行之后1.1.3.1 ldr和mov指令之后1.1.3.2 ads和adc指令之后1.1.3.3 cmp和adc指令之后 1.2 cmp和sbc指令的综合运用1…

汇编语言——逻辑运算和移位指令

逻辑运算和移位指令 逻辑运算指令 逻辑与AND 格式 AND reg, imm/reg/mem ;reg←reg^imm/reg/mem AND mem, imm/reg ; mem←-mem ^ imm/reg功能:对两个操作数执行按位的逻辑与运算&#xff0c;结果送到目的操作数说明: (1)按位的逻辑与运算; (2)操作数不能同时为存储器操作数…

汇编语言基础之 移位指令

原文: http://bdxnote.blog.163.com/blog/static/ 移位指令是一组经常使用的指令,包括:算数移位、逻辑移位、双精度移位、循环移位、带进位的循环移位; 移位指令都有一个指定需要移动的二进制位数的操作数,该操作数可以是立即数,也可以是CL的值;在8086中,该操作数只能是1,但是在…

x86汇编_移位和循环移位指令简介_笔记46

移位指令与前面介绍的按位操作指令一起形成了汇编语言最显著的特点之一。位移动 (bit shifting) 意味着在操作数内向左或向右移动。x86 处理器在这方面提供了相当丰富的指令集如下表所示&#xff0c;这些指令都会影响溢出标志位和进位标志位。 英文全称汇编指令中文翻译说明意…

PLC移位循环指令

PLC移位循环指令 一、移位指令 移位指令包括无符号数移位和有符号数移位。 其中无符号数移位包含字左移指令、字右移指令、 双字左移指令和双字右移指令&#xff1b;有符号数移位包含整数右移指令和双整数右移指令。 1、无符号数移位指令 &#xff08;1&#xff09;字左移指…

ARM64体系结构编程3-算数和移位指令

条件操作码 条件标志位描述N负数标志&#xff08;上一次运算结果为负值&#xff09;Z零结果标志&#xff08;上一次运算结果为零&#xff09;C进位标志&#xff08;上一次运算结果发生了无符号溢出&#xff09;V溢出标志&#xff08;上一次运算结果发生了有符号溢出&#xff0…