5

Flutter布局指南之约束和尺寸

 1 year ago
source link: https://blog.csdn.net/eclipsexys/article/details/129360814
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

2d5620d278eb288e3de114600b7a846e.png

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

f5885681651de80ce851938466313bec.png

Flutter布局总纲——向下传递约束,向上传递尺寸。

Box约束

约束是Flutter布局的核心,在Flutter中,约束的表现形式是通过Constraints类来实现的,所有的非滚动布局模型,都通过BoxConstraints来进行约束,它的代码如下。

8c3dc6eb16a5bcaaae4082f8635fd6c0.png

从上面的代码可以看出,约束本质上就是「宽」「高」上的「最大」「最小」范围。

BoxConstraints具有传递性,约束会在组件树上传递,当前Widget会受到来自父级的约束,同时也会将约束传递给它的子Widget。

通过一个例子,我们来理解下约束是如何进行传递的。



newCodeMoreWhite.png

我们先提出这样几个问题:

  • 第一个Container的10x10能否生效

  • 第二个Container的300x300能否生效

  • FlutterLogo的1000x1000能否生效

运行结果如下。

8b6d05a5d11cb774dc0321242a1815c1.png

从运行效果来看,第一个Container的尺寸被无视了,第二个Container的尺寸生效了,FlutterLogo的尺寸也被无视了。那么为什么会这样呢?一图胜千言,随着下面这张图的线路,我们可以好好理解下约束是如何进行传递的。

0aebef36a2d656965e308b5a7dcf5306.png

在Flutter中,每个组件都有自己的布局行为:

  • Root,传递紧约束,即它的子元素,必须是设备的尺寸,不然Root根本不知道未被撑满的内容该如何显示

  • Container,在有Child的时候,传递紧约束,即子元素必须和它一样大,否则Container也不知道该怎么放置Child

  • Center,将紧约束转换为松约束,Center可以将父级的紧约束,变松,这样它的子元素可以选择放置在居中的位置,而子元素具体有多大?只要不超过父容器大小都可以

这就是Flutter布局的核心思想。

父容器一层层向下先传递约束,即最大最小宽高,子元素根据父元素的约束,修改自己的约束,并继续向下传递,到根子节点之后,将根据约束修正后得到的尺寸,返回给父级,直到根节点。

这也是为什么有些元素设置的尺寸,会被约束吃掉的原因。在Flutter中,元素的尺寸,在不同的父级组件下,会展示出不同的约束效果,从而展示出不同的样式,这是和Android View非常不同的一点。

更多的示例,大家可以参考下面几篇文章。
Flutter布局综述
Flutter布局指南之深入理解BoxConstraints
Flutter布局指南之Box套盒子

不同组件的约束行为不一样,我们平时可以通过下面两种方法来调试,获取组件当前的约束。

LayoutBuilder

首先我们可以通过LayoutBuilder来打印当前Widget的约束,示例代码如下。



newCodeMoreWhite.png

借助它,我们就可以很方便的查到当前约束具体是什么约束,以及到底是多少约束。但是这样做还是有点麻烦,所以我们可以借助Flutter的调试工具。

Flutter Inspector

在Flutter Inspector中,我们可以查看当前Widget Tree的约束情况,在Layout Explorer中,可以看到约束的具体数值,如下所示。

3a7337a05403f74331b262aad8e7b2d9.png

在Widget Detail Tree中,我们还可以看到具体的BoxConstraints对象,如下所示。

533956f67a7b4fa1fe7044331d7b51b3.png

这种方式比前面打log的方式更加直观方便。

约束的松与紧

BoxConstraints定义了最大最新范围之后,还定义了两个语义名词——「松约束」「紧约束」。

  • 松约束:minWidth和minHeight都为0的约束

  • 紧约束:minWidth和maxWidth相等,而且minHeight和maxHeight相等的约束

松约束和紧约束并不是相对的,它们是可以同时存在的。

对于Child来说,它无法违法父级的布局约束,就像下面这个例子。

Container虽然对Child施加了紧约束,但由于Root对Container施加的也是紧约束,所以Container的约束失效了。

不同的Widget,在不同的场景下所产生的约束是不一样的,对于Container来说:

  • 有Child就选择Child的尺寸(有设置alignment时会将约束放松)

  • 没有Child就撑满父级空间(父级空间为Unbound时,尺寸为0)

打破约束限制

由于父组件的紧约束会强制子组件也施加紧约束,这种限制在某些场景下不太灵活,所以Flutter提供了UnconstrainedBox来解除这种限制,还是上面的例子,我们加上UnconstrainedBox。

这个时候,Container施加的新的紧约束(10,10)就可以生效了。
除此之外,还有一些组件,例如——Align。

这些类似的组件,也会将父Widget的紧约束放松为松约束,从它们的使用方式上就可以理解它们的行为,因为如果不能去除紧约束的话,类似对齐的需求就没有办法实现了。

Flex约束

前面看的都是单个Child的容器布局,这类布局方式,我们称之为Box布局,相对而言,类似Column和Row这样的布局方式,我们称之为Flex布局

Row本质上是direction: Axis.horizontal的Flex Widget,Column本质上是direction: vertical的Flex Widget。

在Column和Row中,有两类约束组件,一种是明确知道自身尺寸的Widget,例如Text、Button,有约束的Container等,还有一种是弹性组件,例如Expanded和Flexible等组件。

所以Column和Row在布局时,采用的是Flex约束进行布局,布局按照下面的规则进行。

  • 先按照unbound约束,计算所有非Flex布局的组件的尺寸

  • 再对Flex组件进行布局,布局根据flex属性来分配剩余空间(Flex组件向下传递紧约束)

上面的布局规则是针对主轴来说的,Flex的主轴约束为unbound,Flex约束在交叉轴上会设置为松约束(如果crossAxisAlignment设置为stretch,那么会变成紧约束)。

以Row为例,Row对child的约束会修改为松约束,从而不会限制child在主轴方向上的尺寸,所以当Row内的Child宽度大于屏幕宽度时,就会产生内容溢出的警告。

所以我们通常会在Flex组件中使用Expanded组件来避免内容的溢出。Expanded组件会将主轴方向上的Child施加紧约束,从而避免溢出。我们查看下Expanded的源码。

ab03e560b3471d10fac80a6cba15698a.png

可以发现,Expanded其实就是Flexible的封装,只是将fit设置为了FlexFit.tight。所以,下面的代码是等价的。

我们再来看下面的这个例子。

在上面的代码中,我们得到了下面的结果。

e3a23185f620fea969032f9fe60978dd.png

如果把Flexible中的fit改为FlexFit.tight,那么效果如下。

2af3f1d9c81535a1a3247a1881354f22.png

结合这个例子,我们可以更好的理解Flex的布局模型(先计算非Flex Widget的尺寸,再将剩余空间按照Flex进行拆分,所以不论fit如何,其尺寸是固定的)。

Expanded组件就是基于FlexFit.tight的封装,它用于撑满剩余空间,FlexFit.loose虽然没有封装的组件,但它的使用也很常见,我们可以很容易的实现Android约束布局中的ConstraintedWidth这样的效果。

987262ceb93af9e47e652ae7245d2dd8.png

当内容变长时,会限制其最大宽度,如下所示。

bdd3a154c95945445c554294b6c57d14.png

Wrap约束

Wrap组件与Flex组件有些类似,但又有些不同,拿前面的例子来说,Row中的child组件如果超过了屏幕宽度,就会导致内容溢出,因为Flex组件其主轴上的约束为unbound,而Wrap组件,其主轴上的约束会被修改为松约束,交叉轴上的约束会被改为unbound,这样就可以实现流式的布局效果。

所以Wrap组件和Flex组件在本质上是相反的两种布局行为。

Stack布局约束

Stack是一类比较特殊的层叠组件,它的约束方式和Column、Row相似,但又不完全一样,在Stack中,同样也分为两类组件,一类是Positioned组件,一类是非Positioned组件,然后Stack会按照下面的布局方式进行。

  • 先按照非Positioned组件的尺寸进行计算,将自身尺寸设置为非Positioned组件的最大值

  • 再对Positioned组件进行布局,按照位置约束进行布局,但不能再改变Stack的尺寸

特殊场景下:如果全部是Positioned组件,那么Stack将获得父容器的最大约束,如果全部是非Positioned组件,那么Stack将获得子元素的最大尺寸

从约束上来说,Stack同样会放松父布局的紧约束,其行为和Align是类似的。

Stack的Fit属性

Stack有个Fit属性,需要特别注意,它可以设置为:

  • StackFit.loose:向下传递送约束(默认行为)

  • StackFit.expand:向下传递紧约束

  • StackFit.passthrough:将父级的约束向下传递

Stack设置Fit属性后,并不会对自身尺寸有影响,它改变的是Child的尺寸,通过修改约束是紧约束还是松约束,来影响Child的尺寸,从而改变自己的尺寸。所以Stack的Fit属性默认是loose,即松约束。如果设置为expand,那么Stack将向Child传递一个紧约束。

IntrinsicHeight与IntrinsicWidth

这两个组件在Flutter中的使用非常少,但在某些极端的场景下,却是非常有用的,它的主要功能,就是为了实现类似Android约束布局中的Barrier的功能。

我们以IntrinsicWidth为例,来看看它的作用。

这个布局效果如下所示。

7392923d0478128d338ef26e44e3b8c2.png

由于蓝色的Container没有width约束,所以它在交叉轴方向上的大小是父布局最大尺寸,这时候,我们给它加上IntrinsicWidth。

这时候效果如下所示。

b781b2cb72587d0a9efe401ed46fc4ec.png

可以发现,蓝色Container被强制加上了红色Container的尺寸约束,这就是IntrinsicWidth的作用——在宽度或者高度上施加紧约束来限制Child的尺寸,其约束来自于Child的固有宽度或者高度。

借助这个特性,我们可以很方便的实现一些效果。

这个例子,展示了3个不同长度的Button,通过stretch属性将其交叉轴的长度设置为父布局的最大尺寸,通过增加IntrinsicWidth,我们可以实现下面的效果。

这样的效果如下所示。

91a4e0d9a5eecee43d0a6c5726805f26.png

再例如这个例子。

效果如下。

b383944fb65a772b4cf2bc7829112c78.png

就是因为红色Container没有宽度限制,所以撑满了,我们现在想让红色Container跟随蓝色Container的宽度而变化,那就可以使用IntrinsicWidth。

93526989b175542fdf8332766a73b64a.png

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

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

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

< END >

作者:徐宜生

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK