在真实的产品环境中使用的自定义 View 与平时写 Demo 时的自定义 View 有没有区别呢?
答案是肯定的,有区别。在产品环境中由于产品的使用者人数庞大因此很多在 Demo 中发现不了的问题也会一一暴露出来,甚至一些平时所认知的正常的情况,在用户看来也是不合常理的。
这次我司有个展示公告的需求,需求是当有公告时,公告内容从左到右匀速划过屏幕,每隔一分钟出现一次。刚拿到需求,心中还是有点窃喜的,一次自定 View 的实战机会来了。
需求上线之后没有出现 bug,但是客服那边却接到用户反馈。用户反馈在他正在看公告时只是返回桌面了几秒钟,再进入 App 时发现公告已经滑到左边接近尾声了,导致没有及时看见公告内容,因此反馈给客服,然后客服就直接反馈到了产品 Bug 群里。
这就尴尬了,进入后台 App 没有被杀,滑动动画肯定在运行呀,在我们看来一切都是完美的。然而产品最终还是决定,改!在该页面不可见时暂停公告滚动,等再次可见时再恢复滚动!
没办法,最后通过该 View 的可见性变化进行监听,通过对动画的暂停和继续操作进行了修改。
通过该次事情我也意识到,对自定 View 的掌握仅仅只是会写 onMeasure() / onLayout() / onDraw() 这个三个方法是不够的,而我们在平时的 Demo 练习中也是着重于该三个函数上,忽略了其它方面的认识,下面我将会以如下的顺序展示我对这个问题的思考:
- 写好 onMeasure() / onLayout() / onDraw(),做好变量的初始化工作避免 OOM 产生。
- 对于需要依赖 View 自身尺寸的变量,需要做好初始尺寸的获取以及 onSizeChanged() 函数的处理。
- 做好生命周期处理,完美断后。
onMeasure() / onLayout() / onDraw() ,变量初始化
关于如何写好自定义的三大方法这里不做详细介绍,网上有很多相应的教程,这里推荐 HenCoder 的自定View教程,全面、清晰。
对于 View 变量的初始化工作,我认为始终不应该在 onLayout()
/ onMeasure()
/ onDraw()
三个函数中处理。
onLayout()
和 onMeasure()
方法的调用和父 View 息息相关,可能被调用多次也可能只被调用一次我们无法确保自己的 View 被添加到何种 ViewGroup 中。
onDraw()
会被调用多次,每次 View 发生重绘都会被调用,如果在该方法中创建对象会出现频繁的创建对象,从而不断引发 GC ,造成所谓的内存抖动现象拉低 App 性能。提高发生 OOM 的几率。
变量分类
这里我将自定 View 的变量按照使用者分为两类,一类是不管外界是否调用暴露的方法自身都将使用,二类是只在外界调用对外暴露的函数时才会使用。
第一类变量,我认为应该在自定义 View 的初始化函数中进行初始化。
第二类变量,则可以在当外界进行函数调用时进行初始化,一来减少内存使用,二来逻辑上也会清晰许多。
依赖 View 的尺寸?做好初始尺寸获取和 onSizeChanged() 尺寸改变处理
区分 getWidth() / getMeasureWidth()
在许多文章和 Demo 中我们常看到直接以 getMeasureWidth()
作为 View 的宽度,在绝大多数情况下也是正确的,但是 getMeasureWidth()
确实不是 View 最终的宽度。从源码来看 getMesaurWidth()
获取的是 setMeasuredDimensionRaw()
即 etMeasuredDimension()
函数的值,而 getWidth()
获取的是 mRight - mLeft
的值,mRight 与 mLeft 是在 layout()
时由父 View 传入的值。
因此,在认识清楚两者的区别之后,我们应该在产品项目中需要获取 View 的尺寸时应该使用 getWidth()
系函数,避免和 Demo 一样使用 getMeasureWidth()
系函数。
具体的源码截图如下:
在自定义 View 中获取 View 的尺寸是非常常见的,onDraw()
中绘制位置的确定都依赖于 View 的尺寸,而在 onDraw()
中通过 getWidth()
等函数进行获取虽然可取,但会使得 onDraw()
中的代码较为难看显得不优雅,其次,getWidth()
方法带有数学操作,频率过大时,还是显得不太优雅。
那么应该在那里对 View 尺寸进行获取呢?
对于初始尺寸的获取
首先明确一点,需要在 onLayout()
函数执行之后才能正确获取 View 的尺寸,那么将可用的 api 列出来,然后列举出其调用的时机和意义。
onLaout()
getViewTreeObserver.addGloblLayout()
在 onLayout() 之后执行,需要注意该方法会调用多次。onDraw()
第一次肯定在 onLayout() 之后。onWindowFocusChanged()
当 View 所有在的 Window 焦点发生变化时调用,肯定调用在 onLayout() 之后,但是要注意焦点的获得和失去都会调用。
我的建议
那么我的建议是利用 getViewTreeObserver.addGloblLayout()
初始获 View 尺寸,获取完成之后即移除该监听,防止后续重复调用。我的理由呢,有二:
- 利用该方法肯定能获取到 View 的时机尺寸。
- 初始获取 View 尺寸的方法是独立的,仅仅会调用一次。
是否需要处理 onSizeChanged() ?
对于响应 View 的尺寸发生改变在目前 Android 环境来讲大部分情况时不需要的,但在可见的将来折叠屏、伸缩屏的出现对于尺寸变化的适配也是一大需求。
onSizeChanged()
只会在 View 的尺寸发生改变时触发,即 View 的 width / height 发生改变。onSizeChanged()
触发后续必定会调用 onDraw()
方法重绘 View。
那么什么时候需要处理 onSizeChanged() 方法呢? 我认为只有当 View 所展示的内容需要随着 View 尺寸的改变而变化时需要处理,如:TextView 尺寸变化,展示的文字需要重新布局、ImageView 显示的图片需要更改大小。
注意:使用属性动画操作 View 的 top / left / right 等值时亦有可能触发
onSizeChanged()
方法。
做好生命周期的处理
View 的生命周期的处理思想和 Activity 的处理思想相似,做好 View 中使用的资源处理,如:Handler / 动画等,总结下来的方式如下:
- 做好资源的使用及释放工作。
这里主要涉及
onAttachedToWindow()
/onDetachedFromWindow()
两个函数的处理,这里需要注意onDeatachedFromWindow()
的调用仅仅表示该 View 从 Window 上移除,后续是可以重新添加的。 - 处理
View
可见性变化处理。 这里就看onVisbilityChanged()
函数。 - 处理数据保存于重建工作
onSaveInstanceState()
和onRestoreInstanceState()
,该方法和 Activity 中的方法进行对应。
小结
我们平时的 Demo 练习呢,其实就相当于试验,是为了快速验证某种问题或者实现特定的工作,为了快速且简洁的完成不会关注其他的问题。在我们将 Demo 搬上线上产品时需要有一个产品化过程,全面的思考可能将出现的问题并进行处理。
参考
blog.csdn.net/asd7364645/… measureWidth和width 区别