前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android View之requestLayout排坑

Android View之requestLayout排坑

原创
作者头像
帅气的程序员
发布2020-08-23 00:04:21
6K0
发布2020-08-23 00:04:21
举报
文章被收录于专栏:Android开发之路Android开发之路

| 导语 我们知道当一个View进行布局重计算时(即requestLayout,最终会触发onMeasure和onLayout进行大小和位置计算),此View也会触发其所有子View进行布局重计算,那如果相反过来呢,一个子View进行布局重计算时,会触发其父View也进行布局重计算吗?

一. 一个功能引发的思考

首先从一个真实项目中的例子说起,假设我们需要做一个定时器之类的功能,就是每隔一秒会刷新TextView(显示时间用)的内容,同时也会更改另一个View的background

       布局比较简单,如下:

       代码逻辑也比较简单,就是每隔一秒更新text的内容,以及containerbackground,如下:

       运行后一切都符合预期,good!

       然而,有一天因为新需求在这个布局里加了一个ListView,运行后,奇怪的现象出现了:ListView右侧的scrollbar一直在闪烁,而自己并没有滚动ListView。我们知道,scrollbar在用户没有操作时也出现的话,只能说明此时ListView触发了布局计算,而一直在闪烁,则说明一直在触发布局计算。。。

       为了证明这点,继承一个RelativeLayout,然后覆盖onMeasureonLayout,把log打出来,再把xml里的root View替换成我们的Layout,如下:

       运行后,发现onMeasureonLayout的log确实一直在打印,说明一直在触发布局计算。

       接下来进行问题排查。

       首先排除代码里有没有一直在手动调用root view或ListView的requestLayout之类操作,找了下,没有。

       说明可能是某个逻辑在导致整个布局进行重绘,而恰巧我们就有个定时器在一直更新view,看来极大可能是他导致。

       先把runnable里设置textbackground的地方注释掉,重新运行,果然ListViewscrollbar不闪烁了,log也没有不停在打印了。

       那为什么子view更新了自己的内容,会导致父布局进行布局重计算呢?

二. requestLayout机制

       我们知道调用一个View的requestLayout方法,则可以强制其重新计算大小和位置信息,先找一下requestLayout的源码看一下,如下:

       1和2为两处关键代码。

       1处的作用是将Viewflags标记为需要重新layout,当下次View刷新周期到时,会触发其onMeasureonLayout等方法进行布局计算;

       2处的作用是调用其parentrequestLayout方法,即触发其父View也进行布局重计算。

       到这里已经可以回答开头提的那个问题了,如果调用子view的requestLayout进行布局重计算,其也会调用父View的requestLayout,一层一层传上去,直到root View。

       再回到前面那个例子,我们并没有直接调用requestLayout,而是调用了setTextsetBackgroundDrawable这些方法,看来这些方法里面可能也调用了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大小一样就可以了。

       上面只介绍了setTextsetBackgroundDrawable两个方法的实现,其实View其他设置方法都大同小异,代码的实现者考虑到性能问题,在更改View的内容时,如果发现其大小等属性没变化,则一般不会触发requestLayout进行布局重计算,只会调用invalidate进行内容绘制。

三. 解决方案

通过上述的分析后,回到前面那个例子,我们主要做两个改动。

第一个是将TextView的宽高都设置为固定大小;

第二个是每次更新background的时候,确保Drawable都是同样尺寸。

重新运行后,scrollbar一直闪烁的问题果然就解决了。

四. 总结

通过上述的例子和分析,我们知道ViewrequestLayout也会触发parentrequestLayout,进而触发整个布局树都requestLayout,是存在一定的性能开销的,所以对于一些需要频繁更新View内容的场景(比如定时器),一方面需要谨慎调用requestLayout,另一方面也需要通过log等方法来排查整个布局是否一直在measurelayout,因为只是通过界面的显示,很多时候并不会暴露出这个问题。而对于需要频繁更新内容的View来说,则可以通过固定宽高等方式来避免一直触发requestLayout

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 二. requestLayout机制
  • 三. 解决方案
  • 四. 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档