Android scrollTo滑动原理分析

在这里先说一下结论,我们调用scrollTo使View内容发生移动的原因是:view的画布发生了移动;即scrollTo的调用最终会调用:

canvas.translate(-mScrollX, -mScrollY)

这也同时解释了为什么我们对scrollTo方法传入正值,view的内容却往左/上移动。在分析之前,我们先给出一个scrollTo方法的使用例子:

这里的MyLinearLayout直接继承了LinearLayout,并没有修改LinearLayout的逻辑代码,使MyLinearLayout滑动代码

    public void click(View v){myLinearLayout.scrollTo(10,10);}

运行结果分析:

当我们点击按钮时,这两个TextView向左上移动了10px,这是正常的。但是在这个过程中,我发现MYLinearLayout的onDraw方法没有调用。然后百度了一下这个问题,网上说是只有设置了MyLinearLayout的背景,它的onDraw方法才会调用,测试了一下确实是这样。那么接下来,我们就带着这个问题来分析一下scrollTo方法的方法调用序列。

我们先来看一下scrollTo方法:

public void scrollTo(int x, int y) {//只有与上次移动的位置不相同,才会移动if (mScrollX != x || mScrollY != y) {int oldX = mScrollX;int oldY = mScrollY;mScrollX = x;mScrollY = y;invalidateParentCaches();onScrollChanged(mScrollX, mScrollY, oldX, oldY);if (!awakenScrollBars()) {postInvalidateOnAnimation();}}}

从这里我们看到scrollTo方法,最终会去调用postInvalidateOnAnimation方法,

/*** 

Cause an invalidate to happen on the next animation time step, typically the* next display frame.

**

This method can be invoked from outside of the UI thread* only when this View is attached to a window.

** @see #invalidate()*/public void postInvalidateOnAnimation() {// We try only with the AttachInfo because there's no point in invalidating// if we are not attached to our windowfinal AttachInfo attachInfo = mAttachInfo;if (attachInfo != null) {attachInfo.mViewRootImpl.dispatchInvalidateOnAnimation(this);}}

对于这个方法,从注释和它的名字,我们可以看出它应该是请求Vieiw绘制的,而且这个方法可以从非UI线程调用。在这个方法里又调用了dispatchInvalidateOnAnimation,我们继续跟进:

 public void dispatchInvalidateOnAnimation(View view) {mInvalidateOnAnimationRunnable.addView(view);}
final class InvalidateOnAnimationRunnable implements Runnable {private boolean mPosted;private final ArrayList mViews = new ArrayList();private final ArrayList mViewRects =new ArrayList();private View[] mTempViews;private AttachInfo.InvalidateInfo[] mTempViewRects;//关键代码public void addView(View view) {synchronized (this) {mViews.add(view);//将要刷新的view方法到mViewspostIfNeededLocked();}}......@Overridepublic void run() {final int viewCount;final int viewRectCount;synchronized (this) {mPosted = false;//将mViews中的对象放到mTempViews中viewCount = mViews.size();if (viewCount != 0) {mTempViews = mViews.toArray(mTempViews != null? mTempViews : new View[viewCount]);mViews.clear();}......}//关键代码for (int i = 0; i < viewCount; i++) {mTempViews[i].invalidate();mTempViews[i] = null;}......}}

从这里我们可以看到dispatchInvalidateOnAnimation(View view),这个方法最终会将view传给mViews,并且会调用View的invalidate方法(invalidate绘制流程)。我们知道invalidate方法最终会引起View树的绘制(View绘制原理)。而View树在绘制时,会调用View的draw(Canvas,ViewGroup,long)方法

/*** 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();/* If an attached view draws to a HW canvas, it may use its RenderNode + DisplayList.** If a view is dettached, its DisplayList shouldn't exist. If the canvas isn't* HW accelerated, it can't handle drawing RenderNodes.*/boolean drawingWithRenderNode = mAttachInfo != null&& mAttachInfo.mHardwareAccelerated&& hardwareAcceleratedCanvas;......// Sets the flag as early as possible to allow draw() implementations// to call invalidate() successfully when doing animationsmPrivateFlags |= PFLAG_DRAWN;......RenderNode renderNode = null;Bitmap cache = null;int layerType = getLayerType(); // TODO: signify cache state with just 'cache' localif (layerType == LAYER_TYPE_SOFTWARE || !drawingWithRenderNode) {if (layerType != LAYER_TYPE_NONE) {// If not drawing with RenderNode, treat HW layers as SWlayerType = LAYER_TYPE_SOFTWARE;buildDrawingCache(true);}cache = getDrawingCache(true);}if (drawingWithRenderNode) {// Delay getting the display list until animation-driven alpha values are// set up and possibly passed on to the viewrenderNode = updateDisplayListIfDirty();if (!renderNode.isValid()) {// Uncommon, but possible. If a view is removed from the hierarchy during the call// to getDisplayList(), the display list will be marked invalid and we should not// try to use it again.renderNode = null;drawingWithRenderNode = false;}}......mRecreateDisplayList = false;return more;}

首先,我们会看到canvas.isHardwareAccelerated。这个方法的返回值为true,然后设置drawingWithRenderNode的值,这里它的值也为true,这个变量猜测是决定此次绘制是否启用硬件加速的(对于硬件加速可以看下这个链接上的文章)。因为drawingWithRenderNode的值为true,所以接下来会去调用updateDisplayListIfDirty方法。

public RenderNode updateDisplayListIfDirty() {final RenderNode renderNode = mRenderNode;if (!canHaveDisplayList()) {// can't populate RenderNode, don't tryreturn renderNode;}if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0|| !renderNode.isValid()|| (mRecreateDisplayList)) {......// If we got here, we're recreating it. Mark it as such to ensure that// we copy in child display lists into ours in drawChild()mRecreateDisplayList = true;int width = mRight - mLeft;int height = mBottom - mTop;int layerType = getLayerType();final DisplayListCanvas canvas = renderNode.start(width, height);try {if (layerType == LAYER_TYPE_SOFTWARE) {......} else {computeScroll();canvas.translate(-mScrollX, -mScrollY);mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;mPrivateFlags &= ~PFLAG_DIRTY_MASK;// Fast path for layouts with no backgroundsif ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {dispatchDraw(canvas);drawAutofilledHighlight(canvas);......} else {draw(canvas);}}} finally {renderNode.end(canvas);setDisplayListProperties(renderNode);}} else {mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;mPrivateFlags &= ~PFLAG_DIRTY_MASK;}return renderNode;}

在这里,我们就可以看到canvas.translate方法了,它传入的参数就是scrollTo方法中设置mScrollX与mScrollY,但是在调用这个方法之前有一个判断,即判断此次绘制是否是软件绘制。这里我们发现MyLinearLayout的layerType类型是LAYER_TYPE_NONE,所以不会进行软件绘制。接着往下看,

if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {dispatchDraw(canvas);drawAutofilledHighlight(canvas);......
} 

我们发现在绘制view时,会检查view的标志位是否是PFLAG_SKIP_DRAW,如果是,则标志此次绘制不用自身的内容,即不用调用onDraw方法。因为MyLinearLayout只是进行了滑动,所以这个判断为true。

同时,我们也发现了滑动时,MyLinearLayout的onDraw方法不调用的原因:“Fast path for layouts with no backgrounds”。也就是说没有背景的View会走这个快速执行路径,直接调用dispatchDraw(Canvas),而不是去调用draw(Canvas)方法绘制。

到此我们的scrollTo分析也就结束了。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部