前言
增加一个TextView显示引发的血案….
首先简单来介绍下页面的布局:
描述:
-> 父RelativeLayout
->-> 子自定义View(A)
->-> 子View
。。。
->-> (某一天某小伙伴增加了一个TextView)
-> 父RelativeLayout
小伙伴增加的TextView会不定时更新内容,小伙伴想,这么简单,写完收工!
但是,问题发生了,自定义View A出现了问题,小伙伴急招同事来帮忙。
1、发现问题
在工作中遇到问题,自己搞不定能迅速找到一个人搞定,也是工作能力哈!
同事过来,发现自定义的View A调用invalidate方法时会再次执行onMeasure方法,而onMeasure方法中定义了一些初始化的操作。为什么View A会不定时的执行onMeasure方法?原来是小伙伴增加的TextView会不定时更新内容,To farce a view to draw,call invalidate()—摘自View类源码。
从上面这句话看出,invalidate方法会执行draw过程,重绘View树。在同一个父View(即RelativeLayout中),一个View刷新数据,会影响其他View。至此,根本原因已经找到。
1、Invalidate View更新和重绘
To farce a view to draw,call invalidate().——摘自View类源码
从上面这句话看出,invalidate方法会执行draw过程,重绘View树。
引申:当View的appearance发生改变,比如状态改变(enable,focus),背景改变,隐显改变等,这些都属于appearance范畴,都会引起invalidate操作。
所以当我们改变了View的appearance,需要更新界面显示,就可以直接调用invalidate方法。
View(非容器类)调用invalidate方法只会重绘自身,ViewGroup调用则会重绘整个View树。
首先,一个子View调用该方法,那么我们直接看View#invalidate方法:
public void invalidate() {
invalidate(true);
}
void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
if (mGhostView != null) {
mGhostView.invalidate(true);
return;
}
//这里判断该子View是否可见或者是否处于动画中
if (skipInvalidate()) {
return;
}
//根据View的标记位来判断该子View是否需要重绘,假如View没有任何变化,那么就不需要重绘
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
|| (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
|| (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
|| (fullInvalidate && isOpaque() != mLastIsOpaque)) {
if (fullInvalidate) {
mLastIsOpaque = isOpaque();
mPrivateFlags &= ~PFLAG_DRAWN;
}
//设置PFLAG_DIRTY标记位
mPrivateFlags |= PFLAG_DIRTY;
if (invalidateCache) {
mPrivateFlags |= PFLAG_INVALIDATED;
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
// Propagate the damage rectangle to the parent view.
//把需要重绘的区域传递给父容器
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
//调用父容器的方法,向上传递事件
p.invalidateChild(this, damage);
}
...
}
}
可以看出,invalidate有多个重载方法,但最终都会调用invalidateInternal方法,在这个方法内部,进行了一系列的判断,判断View是否需要重绘,接着为该View设置标记位,然后把需要重绘的区域传递给父容器,即调用父容器的invalidateChild方法。
接着我们看ViewGroup#invalidateChild:
/**
* Don't call or override this method. It is used for the implementation of
* the view hierarchy.
*/
public final void invalidateChild(View child, final Rect dirty) {
//设置 parent 等于自身
ViewParent parent = this;
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
// If the child is drawing an animation, we want to copy this flag onto
// ourselves and the parent to make sure the invalidate request goes
// through
final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION)
== PFLAG_DRAW_ANIMATION;
// Check whether the child that requests the invalidate is f