Android View相关-View的常用方法及使用区别

经过上一章的摸索,我们已经了解了Android中View的绘制流程分别是measure、layout和draw,那么对Android有一些了解的话,一定知道View中有这样几个方法invalidate、postInvalidate以及requestLayout,我们知道这些方法调用后会触发View的重绘(不一定正确的说法),那么它们的用法是什么,有什么区别以及使用时候有哪些注意事项,这就是我们这一篇文章力求弄明白的东西,好了,废话不多说,开始今天的分析。

invalidate

我们从View源码中追本溯源,可以看到在View中方法的调用情况是这样的:

/**
 * 使当前View失效,若View是可见的那么onDraw方法会在未来的某个时间点被调用
 * 该方法必须在UI线程被调用,若需要在非UI线程调用,调用postInvalidate
 */
public void invalidate() {
    invalidate(true);
}
/**
 * 这个方法是invalidate整整产生作用的地方,一个invalidate请求会将绘制缓存设为失效
 * 不过这个方式可以设置参数为false来禁止绘制缓存失效
 */
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 ((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)) {
        // 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);
            //调用父控件的invalidateChild方法
            p.invalidateChild(this, damage);
        }
        ...
    }
}

从这里可以看出invalidate方法的作用是令当前View的绘制缓存失效,并且会调用onDraw方法,不过我们并没有在上述代码中看到onDraw(注意invalidate方法只能在UI线程中调用,工作线程调用该方法会报错),我们知道该方法最终调用了父控件的invalidateChild方法,那么我们来查看下ViewGroup中的invalidateChild:

/**
 * 不要主动调用该方法
 */
public final void invalidateChild(View child, final Rect dirty) {
    ViewParent parent = this;
    ...
        do {
            ...
            if (view != null) {
                if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&
                    view.getSolidColor() == 0) {
                    opaqueFlag = PFLAG_DIRTY;
                }
                if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
                    view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag;
                }
            }

            parent = parent.invalidateChildInParent(location, dirty);
        } while (parent != null);
    }
}

该方法执行了一段循环代码,这里层层调用父控件的invalidateChildInParent方法,知道返回值为空,我们知道绘制的起点是在ViewRootImpl中,那么打开ViewRootImpl,查看其invalidateChildInParent代码:

@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    invalidateRectOnScreen(dirty);
    return null;
}
private void invalidateRectOnScreen(Rect dirty) {
    if (!mWillDrawSoon && (intersected || mIsAnimating)) {
        scheduleTraversals();
    }
}

这里在返回null之后上述循环终止,我们注意到在循环终止前ViewRootImpl调用了scheduleTraversals方法,这个方法跟我们之前讲过的performTraversals方法很像,我们来看看里面有什么:

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        //这行代码在Choreographer类内部使用Handler机制最终调用了doTraversal方法
        mChoreographer.postCallback(
            Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }
        //调用此方法判断是否需要重新测量放置以及绘制
        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

可以看到在绕了一圈之后,ViewRootImpl最终会调用我们上篇讲到的performTraversals方法,重复判断流程。至此,View的invalidate方法执行流程完毕。

postInvalidate

我们在前面的注释里可以看出,invalidate方法是不可以在非UI线程执行的,而postInvalidate则可以,结合我们之前学习的Handler知识,我们猜想可能是使用了handler机制,最终仍然是调用了invalidate,那我们来源码中验证一下我们的猜想:

public void postInvalidate() {
    postInvalidateDelayed(0);
}
public void postInvalidateDelayed(long delayMilliseconds) {
    // We try only with the AttachInfo because there's no point in invalidating
    // if we are not attached to our window
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
    }
}
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
    Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
    mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
@Override
public void handleMessage(Message msg) {
    switch (msg.what) {
        case MSG_INVALIDATE:
            ((View) msg.obj).invalidate();
            break;
    }
}

可以看到,跟我们猜想的一样,经过一系列列调用,最后使用Handler机制,调用了invalidate方法,也就是说这两个方法区别基本只在线程之间。

requestLayout

requestLayout也是我么View中一个很重要的方法,我们在之前讲绘制流程的时候也有看到这个方法,那么我们来探究一下它究竟做了什么:

public void requestLayout() {
    ...
    if (mParent != null && !mParent.isLayoutRequested()) {
        mParent.requestLayout();
    }
    ...
}

这方法与之前invalidateInternal方法类似,也是一层层调用父控件的requestLayout方法,直到ViewRootImpl(ViewGroup没有复写该方法,这里不再关注):

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

又看到了熟悉的scheduleTraversals方法,这里跟之前invalidate的区别仅仅只是标志位可能有所改变,后续流程一致。

小结

一般引起invalidate调用的情景如下:

  • 调用invalidate()方法,请求重新draw(),但只会绘制调用者本身。
  • 调用setSelection()方法 :请求重新draw(),但只会绘制调用者本身。
  • 调用setVisibility()方法 : 当View可视状态在INVISIBLE转换VISIBLE时会间接调用invalidate方法,继而绘制该View。当View的可视状态在INVISIBLE\VISIBLE 转换为GONE状态时会间接调用requestLayout和invalidate方法,同时由于View树大小发生了变化,所以会请求measure过程以及draw过程,同样只绘制需要“重新绘制”的视图。
  • 调用setEnabled()方法 : 请求重新draw(),但不会重新绘制任何视图包括该调用者本身。
  • 调用requestFocus方法。请求View树的draw过程,只绘制“需要重绘”的View。
  • invalidate方法只能在UI线程调用,而postInvalidate可以在工作线程调用,不过invalidate效率更高(显然)
  • requestLayout()方法会调用measure过程和layout过程,不会调用draw过程,也不会重新绘制任何View包括该调用者本身

好了,这就是本篇的全部内容了,这里还是偏理论的东西较多,掌握脉络即可。下一篇文章我们会来聊一聊Android中的事件分发机制,敬请期待~
如果您觉得文章不错,可以来我的个人博客查看更多内容~

猜你喜欢

转载自blog.csdn.net/xiaomi987/article/details/80103179