本篇文章是有关于自定义控件的,为什么我会写这篇文章呢,也是因为自己在写一个自定义轮播图的控件的时候想到一些东西,促使我想要写这篇文章。在写完轮播图之后整理下思路,将这个过程跑一遍,感觉东西还是挺多的。我想将这个理解了,对以后自己自定义view会熟悉更多吧。
这篇文章我会着重讲解computeScroll()方法跟invalidate()之间的关系。乍一看,啥关系都没有,其实底层上看,关系也并不是很大,只是在我们硬件触发绘制操作的时候有点关系。这个放到后面说。我们先看computeScroll()方法。它是被view的draw()方法调用。我们看看draw()方法做了什么,
/**
* This method is called by ViewGroup.drawChild() to have each child view draw itself.
*
* This is where the View specializes rendering behavior based on layer type,
* and hardware acceleration.
*/
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
boolean drawingWithRenderNode = mAttachInfo != null
&& mAttachInfo.mHardwareAccelerated
&& hardwareAcceleratedCanvas;
......
int sx = 0;
int sy = 0;
if (!drawingWithRenderNode) {
computeScroll();
sx = mScrollX;
sy = mScrollY;
}
......
return more;
}
这个方法的代码比较多,我们只看重点部分,我们看到要想执行computeScroll()方法,必须是drawingWithRenderNode的值是false,而drawingWithRenderNode的值由上面三个条件一起控制,这个mAttachInfo 是一个封装了view的很多属性的一个类,所以他不为空,然后是view的mHardwareAccelerated是代表他是否在绘制的时候提供硬件加速,我们一般默认是不开启的,即使开启也没事,我们不是还有第三个条件来控制么,我们看看第三个条件是怎么来的,我们看到方法的第一行就有,他是代表canvas是否是硬件加速,我们再进去看看,
public boolean isHardwareAccelerated() {
return false;
}
我们看到默认是false的,也就是说我们的画布是不开启硬件加速的,所以hardwareAcceleratedCanvas;这个值是为false的,那么不管怎么样都会去执行computeScroll()方法了。OK在draw()方法中我们执行了computeScroll()方法,那么那里会执行draw()方法呢,我们继续去探索,我们看到draw()方法的注释,我想这个不用过多解释吧,说明是在viewgroup的drawChild()方法被执行,所以我们去瞧瞧,
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
果然是这里,但是这个方法的代码也太。。。。少了吧,不过直接明了,哈哈,所以我们得继续查看drawChild()方法是在哪里被调用了,发现是被viewgroup的dispatchDraw(Canvas canvas)方法调用了。所以我们继续查看dispatchDraw()是被谁调用的呢?发现是被view的draw()方法调用的,但是注意,此draw()方法并非彼draw()方法,这个draw()方法是被系统硬件调度的(也可以说是系统通过schedule线程去调度的),所以它的源头就到此结束了(因为再往里层走就是系统层的代码了)。 OK,到此computeScroll()方法的全程调用已经分析完毕。
我们继续看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;
}
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) {
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);
}
// Damage the entire projection receiver, if necessary.
if (mBackground != null && mBackground.isProjected()) {
final View receiver = getProjectionReceiver();
if (receiver != null) {
receiver.damageInParent();
}
}
// Damage the entire IsolatedZVolume receiving this view's shadow.
if (isHardwareAccelerated() && getZ() != 0) {
damageShadowReceiver();
}
}
}
终于不是一行代码了,我们找到重要的地方,p.invalidateChild(this, damage),在这里是对子view进行相应的绘制,我们看到是p来调用它,那我们来看看这个p是什么呢?我们去寻找这个p的来源,
/*
* Caller is responsible for calling requestLayout if necessary.
* (This allows addViewInLayout to not request a new layout.)
*/
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");
}
}
发现这个p的值是在这里被赋值的,那么这个方法何时被调用呢,我们先在ViewGroup中去看,
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
...
// tell our children
if (preventRequestLayout) {
child.assignParent(this);
} else {
child.mParent = this;
}
...
}
发现还真的在这里调用了,但是别急,我们看看是不是真的执行了这个方法,我们去寻找 addViewInner()的调用者,
public void addView(View child, int index, LayoutParams params) {
if (DBG) {
System.out.println(this + " addView");
}
if (child == null) {
throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
}
// addViewInner() will call child.requestLayout() when setting the new LayoutParams
// therefore, we call requestLayout() on ourselves before, so that the child's request
// will be blocked at our level
requestLayout();
invalidate(true);
addViewInner(child, index, params, false);
}
在最后一行,我们看到了调用它,但是最后一个参数赋值是false,什么鬼,也就是说 child.assignParent(this)并没有被执行,
所以说 assignParent()不是在ViewGroup中被执行的,别急,这个方法不止这一个地方被执行,他还会在ViewRootImpl中执行,不信你看;
/**
* We have one child
*/
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
......
view.assignParent(this);
.....
}
看到这里我们心放下一半了(好怕没地方执行啊,哈哈),那么这个setView又是在哪被执行的,实际上是在Activity的启动过
程执行的,参考http://blog.csdn.net/u013866845/article/details/54408825这篇文章,我们发现,最后发现这个mParent的值是
ViewRootImpl。OK,漫长的寻“父”之路终于结束了,让我们回到这里:
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
p.invalidateChild(this, damage);
}
也就是说这个p是ViewRootImpl,我们就看看ViewRootImpl的invalidateChild()方法了,
@Override
public void invalidateChild(View child, Rect dirty) {
invalidateChildInParent(null, dirty);
}
又来了,我们继续找吧,
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);
if (dirty == null) {
return null;
invalidate();
} else if (dirty.isEmpty() && !mIsAnimating) {
return null;
}
if (mCurScrollY != 0 || mTranslator != null) {
mTempRect.set(dirty);
dirty = mTempRect;
if (mCurScrollY != 0) {
dirty.offset(0, -mCurScrollY);
}
if (mTranslator != null) {
mTranslator.translateRectInAppWindowToScreen(dirty);
}
if (mAttachInfo.mScalingRequired) {
dirty.inset(-1, -1);
}
}
invalidateRectOnScreen(dirty);
return null;
}
发现最后不管怎样都调用到了invalidateRectOnScreen(dirty)方法了,继续往下:
private void invalidateRectOnScreen(Rect dirty) {
final Rect localDirty = mDirty;
if (!localDirty.isEmpty() && !localDirty.contains(dirty)) {
mAttachInfo.mSetIgnoreDirtyState = true;
mAttachInfo.mIgnoreDirtyState = true;
}
// Add the new dirty rect to the current one
localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
// Intersect with the bounds of the window to skip
// updates that lie outside of the visible region
final float appScale = mAttachInfo.mApplicationScale;
final boolean intersected = localDirty.intersect(0, 0,
(int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
if (!intersected) {
localDirty.setEmpty();
}
if (!mWillDrawSoon && (intersected || mIsAnimating)) {
scheduleTraversals();
}
}
因为我们的View正在进行重绘的动作,所以这个if条件是成立的(这里就不继续寻找了),所以我们进入到scheduleTraversals方法中去,
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
有木有很熟悉的赶脚,原来在这里通过线程来定时执行重绘任务了,说到我们不得不提一下在startScroll()的时候的代码
了,其实在startScroll()的时候是有一个默认的时间的,也就是说在这个默认的时间里去执行者一系列的重绘动作,所以这个线
程在这个时间段里会不停的去触发重绘的动作,也就是draw()动作,也就进行了我们Vieww的重绘了。到这里,我们也就清楚了整个流程了,我们终于是揭开了computescroll()方法与invalidate()的整个过程的面纱了,相信看过之后对这一系列有了更加清楚的了解了吧。
希望本篇文章会对您有一定的帮助,有不足的地方还望大家指正,谢谢!!