| 导语 我们知道当一个View进行布局重计算时(即requestLayout,最终会触发onMeasure和onLayout进行大小和位置计算),此View也会触发其所有子View进行布局重计算,那如果相反过来呢,一个子View进行布局重计算时,会触发其父View也进行布局重计算吗?
一. 一个功能引发的思考
首先从一个真实项目中的例子说起,假设我们需要做一个定时器之类的功能,就是每隔一秒会刷新TextView(显示时间用)的内容,同时也会更改另一个View的background。
布局比较简单,如下:
代码逻辑也比较简单,就是每隔一秒更新text的内容,以及container的background,如下:
运行后一切都符合预期,good!
然而,有一天因为新需求在这个布局里加了一个ListView,运行后,奇怪的现象出现了:ListView右侧的scrollbar一直在闪烁,而自己并没有滚动ListView。我们知道,scrollbar在用户没有操作时也出现的话,只能说明此时ListView触发了布局计算,而一直在闪烁,则说明一直在触发布局计算。。。
为了证明这点,继承一个RelativeLayout,然后覆盖onMeasure和onLayout,把log打出来,再把xml里的root View替换成我们的Layout,如下:
运行后,发现onMeasure和onLayout的log确实一直在打印,说明一直在触发布局计算。
接下来进行问题排查。
首先排除代码里有没有一直在手动调用root view或ListView的requestLayout之类操作,找了下,没有。
说明可能是某个逻辑在导致整个布局进行重绘,而恰巧我们就有个定时器在一直更新view,看来极大可能是他导致。
先把runnable里设置text和background的地方注释掉,重新运行,果然ListView的scrollbar不闪烁了,log也没有不停在打印了。
那为什么子view更新了自己的内容,会导致父布局进行布局重计算呢?
我们知道调用一个View的requestLayout方法,则可以强制其重新计算大小和位置信息,先找一下requestLayout的源码看一下,如下:
1和2为两处关键代码。
1处的作用是将View的flags标记为需要重新layout,当下次View刷新周期到时,会触发其onMeasure和onLayout等方法进行布局计算;
2处的作用是调用其parent的requestLayout方法,即触发其父View也进行布局重计算。
到这里已经可以回答开头提的那个问题了,如果调用子view的requestLayout进行布局重计算,其也会调用父View的requestLayout,一层一层传上去,直到root View。
再回到前面那个例子,我们并没有直接调用requestLayout,而是调用了setText,setBackgroundDrawable这些方法,看来这些方法里面可能也调用了requestLayout从而导致其parent也进行了布局重计算。
下面对这两个方法简单分析一下。
1)setText
setText是TextView的方法,源码的逻辑比较多,一直跟下去,找到一个和布局比较相关的代码,如下:
再看一下checkForRelayout这个方法,如下:
可以看到里面就是一个if else逻辑,if的判断条件主要是看TextView的宽度是否是非Wrap_Content(即设置了固定大小或match_parent等确定的尺寸),这里省略了if分支里面的代码,主要是进一步判断高度等属性是否已经发生了变化,进而决定是否触发requestLayout;而else分支则很直接,就是直接调用requestLayout触发布局重计算。
而我们前面例子里的TextView宽度正是设置为Wrap_Content,同时也没设置mMaxWidth这些影响大小的属性,换一句话说,即我们的TextView大小是内容自适应的,所以每次setText都会走else分支,进而触发了requestLayout方法。
看来要避免requestLayout被触发,解决方案就是让TextView的大小固定。
2)setBackgroundDrawable
先看一下View的setBackgroundDrawable方法,如下:
2处又是我们熟悉的requestLayout调用,而是否触发requestLayout,主要取决于1处的条件判断,主要是判断新的backgroundDrawable大小是否和旧的backgroundDrawable有差异,如果不一样,则requestLayout为true,如下:
看到这里也清晰了,不想触发requestLayout,只需要让每次更新的backgroundDrawable大小一样就可以了。
上面只介绍了setText,setBackgroundDrawable两个方法的实现,其实View其他设置方法都大同小异,代码的实现者考虑到性能问题,在更改View的内容时,如果发现其大小等属性没变化,则一般不会触发requestLayout进行布局重计算,只会调用invalidate进行内容绘制。
通过上述的分析后,回到前面那个例子,我们主要做两个改动。
第一个是将TextView的宽高都设置为固定大小;
第二个是每次更新background的时候,确保Drawable都是同样尺寸。
重新运行后,scrollbar一直闪烁的问题果然就解决了。
通过上述的例子和分析,我们知道View的requestLayout也会触发parent的requestLayout,进而触发整个布局树都requestLayout,是存在一定的性能开销的,所以对于一些需要频繁更新View内容的场景(比如定时器),一方面需要谨慎调用requestLayout,另一方面也需要通过log等方法来排查整个布局是否一直在measure和layout,因为只是通过界面的显示,很多时候并不会暴露出这个问题。而对于需要频繁更新内容的View来说,则可以通过固定宽高等方式来避免一直触发requestLayout。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有