前言
我们在自定义View时可能需要更新View的显示,比如为View添加动画等等,有两个方法是我们经常会用到的:invalidate()
和requestLayout()
,本文就来具体分析一下这两个方法的区别和使用场景。
在进入正文之前还是要强调一下,本文的分析基于Android 9.0(API Level 28)的源码,不同版本的源码可能会有不同,但是基本思路不会变化太多,可以进行参考。
invalidate()
我们直接来看View的invalidate()
方法:
public void invalidate() {
invalidate(true);
}
public void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
可以看出View的invalidate()
方法最终会调用invalidateInternal()
方法:
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
// ...
// 判断是否跳过重绘
if (skipInvalidate()) {
return;
}
// 判断是否需要重绘
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;
}
mPrivateFlags |= PFLAG_DIRTY;
if (invalidateCache) {
// 设置PFLAG_INVALIDATED标志位
mPrivateFlags |= PFLAG_INVALIDATED;
// 移除PFLAG_DRAWING_CACHE_VALID标志位
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
// damage表示要重绘的区域
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
// 将要重绘的区域传给父View
p.invalidateChild(this, damage);
}
// ...
}
}
invalidateInternal()
方法中首先会根据skipInvalidate()
方法判断是否跳过绘制,如果同时满足以下三个条件就直接return,跳过重绘。
- View是不可见的
- 当前没有设置动画
- 父View的类型不是ViewGroup或者父ViewGoup不处于过渡态
接下来根据一系列条件判断是否需要重绘,如果满足以下任意一条就进行重绘。
- View已经被绘制完成并且具有边界
- invalidateCache为true并且设置了PFLAG_DRAWING_CACHE_VALID标志位,即绘制缓存可用
- 没有设置PFLAG_INVALIDATED标志位,即没有被重绘过
- fullInvalidate为true并且透明度发生了变化
接下来判断如果invalidateCache为true,就给View设置PFLAG_INVALIDATED标志位,这一步很重要,后面还会提到,通过上面的调用也能看出这里的invalidateCache传入的值为true,因此会设置这个标志位。方法的最后会调用mParent即父View的invalidateChild()
方法,将要重绘的区域damage传递给父View。下面我们来看ViewGroup的invalidateChild()
方法:
@Override
public final void invalidateChild(View child, final Rect dirty) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null && attachInfo.mHardwareAccelerated) {
// 开启了硬件加速
onDescendantInvalidated(child, child);
return;
}
ViewParent parent = this;
if (attachInfo != null) {
// ...
do {
View view = null;
if (parent instanceof View) {
view = (View) parent;
}
// ...
parent = parent.invalidateChildInParent(location, dirty);
// ...
} while (parent != null);
}
}
方法内部首先会判断是否开启了硬件加速,接下来我们分别看一下关闭和开启硬件加速情况下的重绘流程。
- 关闭硬件加速
关闭硬件加速的情况下会循环调用invalidateChildInParent()
方法,将返回值赋给parent,当parent为null时退出循环,我们来看ViewGroup的invalidateChildInParent()
方法。
@Override
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
// ...
// 移除PFLAG_DRAWING_CACHE_VALID标志位
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
// ...
return mParent;
}
return null;
}
这里省略了大量代码,主要是对子View传递过来的重绘区域进行运算处理,方法最后会返回mParent。因此在invalidateChild()
方法中会通过循环逐层调用父View的invalidateChildInParent()
方法,那么当调用到最顶层ViewGroup——DecorView的invalidateChild()
方法时,它的mParent是谁呢?我们可以回顾一下ViewRootImpl的setView()
方法:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
// ...
requestLayout();
// ...
view.assignParent(this);
// ...
}
}
}
可以发现方法执行了view.assignParent(this)
,这里的view其实就是DecorView(ActivityThread的handleResumeActivity()
方法中调用wm.addView()
传过来的),我们来看一下assignParent()
方法,它定义在View中:
void assignParent(ViewParent parent) {
if (mParent == null) {
mParent = parent;
} else if (parent == null) {
mParent = null;
} else {
throw new RuntimeException("view " + this + " being added, but"
+ " it already has a parent");
}
}
很明显assignParent()
方法完成了View的成员变量mParent的赋值,因此DecorView的mParent就是上面传入的this,也就是ViewRootImpl。既然清楚了DecorView的mParent,接下来我们就来看一下ViewRootImpl的invalidateChildInParent()
方法:
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
// ...
invalidateRectOnScreen(dirty);
return null;
}
方法内部首先会调用checkThread()
方法:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
checkThread()
方法会判断当前线程是否为主线程,如果不是主线程就直接抛出异常,因此我们需要特别注意,invalidate()
方法必须在主线程中调用。回到ViewRootImpl的invalidateChildInParent()
方法,最后调用了invalidateRectOnScreen()
方法,同时由于返回值为null,因此执行完invalidateChildInParent()
方法后parent被赋值为null,退出do-while循环。接下来我们就来看一下invalidateRectOnScreen()
方法:
private void invalidateRectOnScreen(Rect dirty) {
// ...
if (!mWillDrawSoon && (intersected || mIsAnimating)) {
scheduleTraversals();
}
}
invalidateRectOnScreen()
方法内部会调用scheduleTraversals()
方法,这个方法我们很熟悉了,接下来会调用performTraversals()
方法,开始View的三大流程,这里再来回顾一下:
private void performTraversals() {
// ...
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
if (layoutRequested) {
// ...
// 调用performMeasure()方法开始measure流程
measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
}
// ...
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
if (didLayout) {
// ...
// 开始layout流程
performLayout(lp, mWidth, mHeight);
}
// ...
// 开始draw流程
performDraw();
// ...
}
由于此时没有给mLayoutRequested赋值,它的默认值为false,因此不会调用measureHierarchy()
和performLayout()
方法,只调用performDraw()
方法,换句话说就是不会执行measure和layout流程,只执行draw流程,接下来就是调用DecorView的draw()
方法,遍历DecorView的子View,逐层完成子View的绘制。
- 开启硬件加速
开启硬件加速时在ViewGroup的invalidateChild()
方法中会调用onDescendantInvalidated()
方法并直接返回,不会执行后面的invalidateChildInParent()
方法,我们来看一下onDescendantInvalidated()
方法:
@Override
public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
// ...
if (mParent != null) {
mParent.onDescendantInvalidated(this, target);
}
}
方法内部会调用mParent的onDescendantInvalidated()
方法,和invalidateChildInParent()
类似,接下来会逐级调用父View的onDescendantInvalidated()
方法,最后来到ViewRootImpl的onDescendantInvalidated()
方法。
@Override
public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) {
if ((descendant.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {
mIsAnimating = true;
}
invalidate();
}
接下来会调用ViewRootImpl的invalidate
方法:
void invalidate() {
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon) {
scheduleTraversals();
}
}
可以看到这里同样调用了scheduleTraversals()
方法,之后的流程和关闭硬件加速的情况类似,同样是调用performDraw()
方法,不同的是开启硬件加速的情况下会执行mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this, callback)
,上一篇文章Android自定义View的基石——View工作原理总结中分析View的draw流程时也介绍过了,这里就不再分析了,之后会依次调用ThreadedRenderer的updateRootDisplayList()
、updateViewTreeDisplayList()
方法。
private void updateViewTreeDisplayList(View view) {
view.mPrivateFlags |= View.PFLAG_DRAWN;
view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
== View.PFLAG_INVALIDATED;
view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
view.updateDisplayListIfDirty();
view.mRecreateDisplayList = false;
}
这里需要注意,根据此前的分析,在调用View的invalidate()
方法后,会给当前View设置PFLAG_INVALIDATED标志位,因此它的mRecreateDisplayList变量值为true,而其他的父级View由于没有设置PFLAG_INVALIDATED标志位,mRecreateDisplayList值为false。接下来会调用view的updateDisplayListIfDirty()
方法,这里的view是DecorView。
public RenderNode updateDisplayListIfDirty() {
final RenderNode renderNode = mRenderNode;
// ...
if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
|| !renderNode.isValid()
|| (mRecreateDisplayList)) {
if (renderNode.isValid()
&& !mRecreateDisplayList) {
// 不需要重新进行绘制
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchGetDisplayList();
return renderNode;
}
// 需要重新进行绘制
mRecreateDisplayList = true;
try {
// ...
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
// 如果设置了PFLAG_SKIP_DRAW标志位,执行dispatchDraw()方法
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().draw(canvas);
}
if (debugDraw()) {
debugDrawFocus(canvas);
}
} else {
// 没有设置PFLAG_SKIP_DRAW标志位,执行draw()方法
draw(canvas);
}
// ...
} finally {
renderNode.end(canvas);
setDisplayListProperties(renderNode);
}
} else {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
}
return renderNode;
}
由于页面首次绘制完成后执行了renderNode.end(canvas)
,因此这里renderNode.isValid()
返回值为true,而DecorView的mRecreateDisplayList值为false,因不会执行后面的重新绘制逻辑,取而代之的是调用dispatchGetDisplayList()
方法,我们来看一下这个方法:
@Override
protected void dispatchGetDisplayList() {
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
final View child = children[i];
if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)) {
recreateChildDisplayList(child);
}
}
// ...
}
private void recreateChildDisplayList(View child) {
child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) != 0;
child.mPrivateFlags &= ~PFLAG_INVALIDATED;
child.updateDisplayListIfDirty();
child.mRecreateDisplayList = false;
}
dispatchGetDisplayList()
方法内部会遍历子View,依次调用recreateChildDisplayList()
方法,不难看出recreateChildDisplayList()
方法和updateViewTreeDisplayList()
方法很像,接下来同样会调用updateDisplayListIfDirty()
方法,对于没有设置PFLAG_INVALIDATED标志位的View,它的mRecreateDisplayList值为false,会重复上面的过程,即调用dispatchGetDisplayList()
方法;而对于调用了invalidate()
方法的View,由于设置了PFLAG_INVALIDATED标志位,它的mRecreateDisplayList值为true,会执行updateDisplayListIfDirty()
方法最后的重绘逻辑,即调用dispatchDraw()
方法或者draw()
方法完成自身及子View的绘制。
最后总结一下invalidate()
方法,调用View的invalidate()
方法后会逐级调用父View的方法,最终导致ViewRootImpl的scheduleTraversals()
方法被调用,进而调用performTraversals()
方法。由于mLayoutRequested的值为false,因此不会执行measure和layout流程,只执行draw流程。draw流程的执行过程和是否开启硬件加速有关:
- 如果关闭了硬件加速,从DecorView开始的所有View都会重新完成绘制
- 如果开启了硬件加速,只有调用
invalidate()
方法的View(包括它的子View)会完成重新绘制
由此也可以看出,开启硬件加速确实可以提高重绘的效率。
此外,由于invalidate()
方法必须在主线程中调用,那么如果我们想要在子线程中刷新视图要怎么做呢?不用担心,官方还为我们提供了一个postInvalidate()
方法,其实从名称上我们也能猜到它的作用了,就是用于在子线程中刷新视图,简单看一下它的定义:
public void postInvalidate() {
postInvalidateDelayed(0);
}
public void postInvalidateDelayed(long delayMilliseconds) {
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);
}
哈哈,果然还是用到了Handler,mHandler是ViewRootImpl中的一个成员变量,类型为ViewRootHandler,我们来看一下ViewRootHandler对MSG_INVALIDATE消息的处理:
final class ViewRootHandler extends Handler {
// ...
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INVALIDATE:
((View) msg.obj).invalidate();
break;
// ...
}
}
}
可以看出最后还是调用了invalidate()
方法,因此postInvalidate()
方法其实就是通过Handler完成了线程的切换,使得invalidate()
方法在主线程中被调用。
requestLayout()
我们来看View的requestLayout()
方法:
public void requestLayout() {
// ...
// 给View添加两个标志位
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
// 调用父类的requestLayout()方法
mParent.requestLayout();
}
// ...
}
requestLayout()
方法内部会调用mParent即父View的requestLayout()
方法,最终会来到ViewRootImpl的requestLayout()
方法:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
首先还是会进行线程的检查,因此requestLayout()
方法同样只能在主线程中调用。接着会把mLayoutRequested赋值为true并调用scheduleTraversals()
方法。后面的流程相信也不用我多说了,调用performTraversals()
方法,由于将mLayoutRequested赋值为true,因此会依次执行measureHierarchy()
、performLayout()
和performDraw()
方法,开始View的三大流程。
到这里还没完,我们需要探究一下View调用requrestLayout()
是否会导致View树中的所有View都进行重新测量、布局和绘制。我们注意到调用requestLayout()
方法后,会为当前View及所有父级View添加PFLAG_FORCE_LAYOUT和PFLAG_INVALIDATED标志位。首先来回顾一下View的measure()
方法:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
// ...
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
if (forceLayout || needsLayout) {
// ...
onMeasure(widthMeasureSpec, heightMeasureSpec);
// ...
// 设置PFLAG_LAYOUT_REQUIRED标志位
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
// ...
}
可以看出,当View设置了PFLAG_FORCE_LAYOUT标志位后,forceLayout的值为true,因此会执行onMeasure()
方法,而对于没有设置PFLAG_FORCE_LAYOUT标志位的View,需要判断测量尺寸是否发生了改变,如果改变了才会调用onMeasure()
方法。在调用onMeasure()
方法后会给View设置PFLAG_LAYOUT_REQUIRED标志位,我们再来看View的layout()
方法:
public void layout(int l, int t, int r, int b) {
// ...
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
// ...
}
// ...
}
对于设置了PFLAG_LAYOUT_REQUIRED标志位的View,onLayout()
方法肯定会执行,另一种情况就是View的四个顶点坐标发生改变,也会执行onLayout()
方法。
结合上面的分析可以得出结论,当View调用了requestLayout()
方法后,自身及父级View的onMeasure()
和onLayout()
方法会被调用,对于它的子View,onMeasure()
和onLayout()
方法不一定被调用。
对于draw流程,performDraw()
方法会调用ViewRootImpl中的draw()
方法:
private boolean draw(boolean fullRedrawNeeded) {
// ...
final Rect dirty = mDirty;
// ...
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
// ...
// 开启了硬件加速
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this, callback);
} else {
// ...
// 关闭了硬件加速
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
}
}
// ...
return useAsyncReport;
}
dirty指向ViewRootImpl中的一个成员变量mDirty,类型为Rect,在ViewRootImpl的invalidate()
方法中会调用set()
方法为其设置四个边界值,由于此时没有调用invalidate()
方法,因此mDirty.isEmpty()
返回true,不会执行后面的绘制方法,因此整个View树不会进行重新绘制。不过也有这样一种情况,我们知道在执行VIew的layout流程时会调用setFrame()
方法,在setFrame()
方法中有这样的逻辑:
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
invalidate(sizeChanged);
可以看出当View的宽或高发生改变时会调用invalidate()
方法,导致View的重新绘制。
总结
我们已经分析过了invalidate()
和requestLayout()
的具体实现,现在就来总结一下invalidate()
和requestLayout()
的异同:
相同点
1.invalidate()
和requestLayout()
方法最终都会调用ViewRootImpl的performTraversals()
方法。
不同点
1.invalidate()
方法不会执行measureHierarchy()
和performLayout()
方法,也就不会执行measure和layout流程,只执行draw流程,如果开启了硬件加速则只进行调用者View的重绘。
2.requestLayout()
方法会依次measureHierarchy()
、performLayout()
和performDraw()
方法,调用者View和它的父级View会重新进行measure、layout,一般情况下不会执行draw流程,子View不一定会重新measure和layout。
综上,当只需要进行重新绘制时就调用invalidate()
,如果需要重新测量和布局就调用requestLayout()
,但是requestLayout()
不保证进行重新绘制,如果要进行重新绘制可以再手动调用invalidate()
。
光介绍结论可能不是很直观,下面就以一个简单的例子验证一下上面的几个结论,我定义了两个ViewGroup和一个View,代码很简单,如下所示:
MyViewGroup1.java
public class MyViewGroup1 extends ViewGroup {
public MyViewGroup1(Context context) {
this(context, null);
}
public MyViewGroup1(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyViewGroup1(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setWillNotDraw(false);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.e("TAG", "MyViewGroup1执行了onMeasure");
measureChildren(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Log.e("TAG", "MyViewGroup1执行了onLayout");
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
int width = child.getMeasuredWidth();
int height = child.getMeasuredHeight();
child.layout(l, t, l + width, t + height);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.e("TAG", "MyViewGroup1执行了onDraw");
canvas.drawColor(Color.YELLOW);
}
}
MyViewGroup2.java
public class MyViewGroup2 extends ViewGroup {
public MyViewGroup2(Context context) {
this(context, null);
}
public MyViewGroup2(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyViewGroup2(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setWillNotDraw(false);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.e("TAG", "MyViewGroup2执行了onMeasure");
measureChildren(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Log.e("TAG", "MyViewGroup2执行了onLayout");
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
int width = child.getMeasuredWidth();
int height = child.getMeasuredHeight();
child.layout(l, t, l + width, t + height);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.e("TAG", "MyViewGroup2执行了onDraw");
canvas.drawColor(Color.GREEN);
}
}
MyView.java
public class MyView extends View {
public MyView(Context context) {
super(context);
}
public MyView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.e("TAG", "MyView执行了onMeasure");
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
Log.e("TAG", "MyView执行了onLayout");
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.e("TAG", "MyView执行了onDraw");
canvas.drawColor(Color.RED);
}
}
为了保证ViewGroup的onDraw()
方法执行,我在构造方法中调用了setWillNotDraw(false)
。布局文件也很简单,一个三级的嵌套:
activity_test.xml
<?xml version="1.0" encoding="utf-8"?>
<com.example.MyViewGroup1 xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/myViewGroup1"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.MyViewGroup2
android:id="@+id/myViewGroup2"
android:layout_width="300dp"
android:layout_height="300dp">
<com.example.MyView
android:id="@+id/myView"
android:layout_width="200dp"
android:layout_height="200dp" />
</com.example.MyViewGroup2>
</com.example.MyViewGroup1>
运行后的效果如下:
我给三个View添加了点击事件,点击后分别调用自身的invalidate()
或requestLayout()
方法,下面分情况看一下。
- 调用
invalidate()
方法
开启硬件加速
调用MyView的invalidate()
方法:
调用MyViewGroup2的invalidate()
方法:
调用MyViewGroup1的invalidate()
方法:
关闭硬件加速
调用MyView的invalidate()
方法:
调用MyViewGroup2的invalidate()
方法:
调用MyViewGroup1的invalidate()
方法:
可以看出,关闭硬件加速时,调用任何一个View的invalidate()
方法都会导致整个View树的重新绘制;开启硬件加速时,调用哪一个View的invalidate()
方法就会重绘哪一个View。invalidate()
方法不会导致onMeasure()
和onLayout()
被调用。
- 调用
requestLayout()
方法
开启硬件加速
调用MyView的requestLayout()
方法:
调用MyViewGroup2的requestLayout()
方法:
调用MyViewGroup1的requestLayout()
方法:
关闭硬件加速
调用MyView的requestLayout()
方法:
调用MyViewGroup2的requestLayout()
方法:
调用MyViewGroup1的requestLayout()
方法:
可以看出此时是否开启硬件加速对于requestLayout()
方法的调用流程没有影响,调用View的requestLayout()
方法会导致自身及其父View的onMeasure()
和onLayout()
方法被调用,并不会调用onDraw()
方法进行重绘,当然前面也分析过了,onDraw()
方法不是一定不会被调用,当View重新绘制时硬件加速的作用就会有所体现了。
其实这篇文章本来是作为上一篇文章Android自定义View的基石——View工作原理总结的一个章节的,由于字数限制只能单独提出来了,因此难免有地方介绍得不是很详细,可以结合上一篇文章来看。由于自身水平的原因,文章中分析得不正确的地方,欢迎大家交流指出,