总结了30个例子之后,我悟到了Flutter的布局原理

这样的

还有这样的

不得不说确实覆盖了很多场景!可是对于我这种记性不好的懒鬼来说,看完30个例子真的是太!费!劲!了!而且看完就忘!!实际中大概率不会出现一模一样的情况。所以我就在寻思,这背后究竟是啥原理可以我下次不用反复复习这30个例子呢?这就引出了今天的主题:Flutter的布局原理

正片开始:先宏观看看Flutter组件的分类


在原生上我们知道一个View控件的渲染过程大致分为onMeasure()[知道有多大],onLayout()[知道该放那],onDraw()[知道长啥样]三个过程。但Flutter的UI体系思路和这个不太一样,首先在Flutter的组件体系中,并非所有的Widget都会渲染到最后的页面上,整个Widget大概可以分为三类组合类代理类绘制类 -[这点面试必问!!]-

平时我们使用到最多的StatelessWidget和StatefulWidget其实只是组合类的控件,实际上他并不负责绘制,所有我们在屏幕上看到的UI最终几乎都会通过RenderObjectWidget实现。而RenderObjectWidget中有个createRenderObject()方法生成RenderObject对象,RenderObject实际负责实际的layout()和paint()。例如我们最常使用的Container组件其实只是一个组合类的控件,在其中封装了多个负责绘制的原子组件。想详细了解RenderObjectWidget可以看看深入研究Flutter布局原理写得非常好


开胃小菜:RenderObject的的绘制过程


RenderObject是如何完成渲染的呢,在原来我一直在错误的使用 setState()?中分析过,Flutter的渲染流程关键在于drawFrame()方法中

void drawFrame() {

//在这之前已经完成了build()

pipelineOwner.flushLayout();

pipelineOwner.flushCompositingBits();

pipelineOwner.flushPaint();

renderView.compositeFrame(); // this sends the bits to the GPU

pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.

}

整个过程和原生分为三个阶段build(),layout(),paint()。build()方法由组合类和代理类Widget实现,layout()和paint()由RenderObject实现。这里设计思路和原生不太一样,在原生中layout()方法一般由ViewGroup实现,他需要规定child控件的位置。这样他的子节点就只用关心绘制即可。

而Flutter中的layout()更接近理解为Measure(!!理解这点非常重要,不能用原生的思路去学习),它的职能主要是计算控件_自身的尺寸_和_位置偏移_。这里的计算是一个从最顶级的节点开始传递约束,从下开始返回测量结果的过程

测量结果我们很好理解,就是一个控件实际的宽高。而约束是个啥玩意儿??


硬菜来了: What’s Constraints


约束Constraints 在Flutter中是一种_布局协议_,Flutter中有两大布局协议BoxConstraintsSliverConstraints。对于非滑动的控件例如Padding,Flex等一般都使用_BoxConstraints盒约束_。

BoxConstraints({

this.minWidth,

this.maxWidth,

this.minHeight,

this.maxHeight,

});

看起来非常好理解,在盒约束中,只会限制子控件的最大最小宽高。经过我搜刮了网上几乎所有的布局原理文章之后,对于这个约束这个约束可以这样总结。 首先这个约束可以根据最大最小值分为两大类

  • 1、 tight(紧约束):当max和min值相等时,这时传递给子类的是一个确定的宽高值。

const BoxConstraints.expand({

double width,

double height,

}) : minWidth = width ?? double.infinity,

maxWidth = width ?? double.infinity,

minHeight = height ?? double.infinity,

maxHeight = height ?? double.infinity;

这个约束的使用的地方主要有两个

一个在_Container_中,当Container的 child==null&&||(constraints == null || !constraints.isTight))时。 另一个_ModalBarrier_,这个组件我们不太熟悉,但查看调用发现被嵌套在了Route中,所以每次我们push一个新Route的时候,默认新的页面就是撑满屏幕的模式。

  • 2、loose(松约束):当max和min不相等的时候,这种时候对子类的约束是一个范围,称为松约束。
BoxConstraints.loose(Size size)
minWidth = 0.0,

maxWidth = size.width,

minHeight = 0.0,

maxHeight = size.height;

在我们最常使用的Scaffold组件中就采用了这种布局,所以Scaffold对于子布局传递的是一个松的约束。


酣畅过瘾: 举几个栗子


了解了上面的基础概念之后,我们先来看看如何对一些场景进行分析。当我们想知道一个控件的布局过程是怎样的,可以参考:

在你的代码中找到一个 Column 并跟进到它的源代码。为此,请在 (Android Studio/IntelliJ) 中使用 command+B(macOS)或 control+B(Windows/Linux)。你将跳到 basic.dart 文件中。由于 Column 扩展了 Flex,请导航至 Flex 源代码(也位于 basic.dart 中)。 向下滚动直到找到一个名为 createRenderObject() 的方法。如你所见,此方法返回一个 RenderFlex。它是 Column 的渲染对象,现在导航到 flex.dart 文件中的 RenderFlex 的源代码。 向下滚动,直到找到 performLayout() 方法,由该方法执行列布局。

根据这个方法,我们试着分析几个有意思的栗子。

案例1(来自样例一)


如图,如果我们直接返回一个红色Container,这个时候他会撑满整个屏幕。首先Container是一个组合类的Widget,并不负责渲染。查看他的build方法,在这种情况下返回了三层RenderObject RenderDecoratedBox,RenderLimitedBox,RenderConstrainedBox

这三个类都继承自RenderProxyBox,这个类混入了RenderProxyBoxMixin,布局方法就在里面:

@override

void performLayout() {

if (child != null) {

child.layout(constraints, parentUsesSize: true);

size = child.size;

} else {

performResize();

}

}

其实从这个类的名称我们可知一二,这是个代理类的渲染对象,如果他有子节点的的时候他会把自己父节点的约束constraints传递给节点,然后使用子节点的尺寸作为自己的。 那么最外层的RenderDecoratedBox的约束是什么呢。前面其实也提到了在紧约束中,_BoxConstraints.expand_被用在了Route上,所以每个页面默认是撑满屏幕的。这个约束就一直向下传递

但在最下面的_RenderConstrainedBox_重写了_performLayout_方法

@override

void performLayout() {

if (child != null) {

child.layout(_additionalConstraints.enforce(constraints), parentUsesSize: true);

size = child.size;

} else {

size = _additionalConstraints.enforce(constraints).constrain(Size.zero);

}

}

其实和上面差不多,不过对_RenderConstrainedBox_我们可以添加约束信息_additionalConstraints属性,查看Container的build方法可知这个约束在这种情况下为_BoxConstraints.expand_ ,在layout的时候也会被考虑。由于_RenderConstrainedBox_下没有child了所以走

size = _additionalConstraints.enforce(constraints).constrain(Size.zero);

///返回新的框约束,它尊重给定的约束,同时与原始约束尽可能接近

BoxConstraints enforce(BoxConstraints constraints) {

return BoxConstraints(

minWidth: minWidth.clamp(constraints.minWidth, constraints.maxWidth),

maxWidth: maxWidth.clamp(constraints.minWidth, constraints.maxWidth),

minHeight: minHeight.clamp(constraints.minHeight, constraints.maxHeight),

maxHeight: maxHeight.clamp(constraints.minHeight, constraints.maxHeight),

);

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

题外话

不管怎么样,不论是什么样的大小面试,要想不被面试官虐的不要不要的,只有刷爆面试题题做好全面的准备,当然除了这个还需要在平时把自己的基础打扎实,这样不论面试官怎么样一个知识点里往死里凿,你也能应付如流啊

这里我为大家准备了一些我工作以来以及参与过的大大小小的面试收集总结出来的一套进阶学习的视频及面试专题资料包,主要还是希望大家在如今大环境不好的情况下面试能够顺利一点,希望可以帮助到大家~

欢迎评论区讨论。

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

什么样的大小面试,要想不被面试官虐的不要不要的,只有刷爆面试题题做好全面的准备,当然除了这个还需要在平时把自己的基础打扎实,这样不论面试官怎么样一个知识点里往死里凿,你也能应付如流啊**

这里我为大家准备了一些我工作以来以及参与过的大大小小的面试收集总结出来的一套进阶学习的视频及面试专题资料包,主要还是希望大家在如今大环境不好的情况下面试能够顺利一点,希望可以帮助到大家~

[外链图片转存中…(img-uffTMVch-1712412511396)]

欢迎评论区讨论。

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值