作者: ztelur
联系方式:segmentfault,csdn,github
本文转载请注明原作者、文章来源,链接,版权归原文作者所有。

本篇为Android Scroll系列文章的最后一篇,主要讲解Android视图绘制机制,由于本系列文章内容都是视图滚动相关的,所以,本篇从视图内容滚动的视角来梳理视图绘制过程。

如果没有看过本系列之前文章或者不太了解相关的知识,请大家阅读一下一下的文章:

  • Android MotionEvent详解

  • Android Scroll详解(一):基础知识

  • Android Scroll详解(二):OverScroller实战

为了节约大家的时间,本文内容主要如下:

  • Scroller相关机制。

  • mScrollXmScrollY是如何影响视图内容。

  • Android视图绘制逻辑,包括相关API和Canvas的相关操作。

一切从Scroller使用开始

使用scroller的实例代码,之后的讲解流程就是scroller和computeScroll是如何调用的啦。

在系列文章的第二篇中,我们具体学习了Scroller的使用方法。通过ScrollerflingViewcomputeScroll的配合,实现视图滚动效果。实例代码如下

.....mScroller.fling(0,getScrollY(),0,speed,0,0,-500,10000) invalidate();.....@Overridepublic void computeScroll() {        if (mScroller.computeScrollOffset()) {                    scrollTo(mScroller.getCurrX(),                    mScroller.getCurrY());           postInvalidate();        }}

本篇文章就带大家探究一下这段代码背后的原理和机制。

Invalidate的寻父之路

这一节主要分析在View中调用invalidateViewRoot执行performTraversals的原理,对android视图架构不是很熟悉的同学可以先阅读一下《Android视图架构详解》。

Android Scroll详解(三):Android 绘制过程详解_第1张图片

我们先来看一下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) {        .....        //DRAWN和HAS_BOUNDS是否被设置为1,说明上一次请求执行的UI绘制已经完成,那么可以再次请求执行        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) { //是否让view的缓存都失效                mPrivateFlags |= PFLAG_INVALIDATED;                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;            }            // Propagate the damage rectangle to the parent view.            final AttachInfo ai = mAttachInfo;            final ViewParent p = mParent;            //通过ViewParent来执行操作,如果当前视图是顶层视图也就是DecorView的视图,那么它的            //mParent就是ViewRoot对象,所以是通过ViewRoot的对象来实现的。            if (p != null && ai != null && l < r && t < b) {                final Rect damage = ai.mTmpInvalRect;                damage.set(l, t, r, b);                p.invalidateChild(this, damage);//TODO:这是invalidate执行的主体            }            .....        }    }

我们可以看到,调用invalidate()会导致整个视图进行刷新,并且会刷新缓存。

然后我们再来详细的研究一下invalidateInternal中的代码。我们先来着重看一下if语句的判断条件把。

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))
  • mPrivateFlagsFLAG_DRAWNFLAG_HAS_BOUNDS位设置为1时,说明上一次请求执行的UI绘制已经完成,那么可以再次请求重新绘制。FLAG_DRAWN位会在draw函数中会被置为1,而FLAG_HAS_BOUNDS会在setFrame函数中被设置为1。

  • mPrivateFlagsPFLAG_DRAWING_CACHE_VALID标示视图缓存是否有效,如果有效并且invalidateCache为true,那么可以请求重新绘制。

  • 另外两个布尔判断的具体含义并没有分析清楚,大家感兴趣的请自行研究。

然后将mPrivateFlagsPFLAG_DIRTY置为1。并且如果是要刷新缓存的话,将PFLAG_INVALIDATED位设置为1,并且将PFLAG_DRAWING_CACHE_VALID位设置为0,这一步和之前的if判断中后两个布尔判断相对应,可见,如果已经有一个invalidate设置了上述两个标志位,那么下一个invalidate就不会进行任何操作。

接着,调用ViewParent接口的invalidateChild函数,在《Android视图架构详解》,我们已经知道ViewGroupViewRoot都实现了上述接口,那么,根据Android视图树状结构,ViewGroup的相应方法会被调用。

public final void invalidateChild(View child, final Rect dirty) {    ViewParent parent = this;    final AttachInfo attachInfo = mAttachInfo;    if (attachInfo != null) {        ....        // while一直向上递归        do {            ......            parent = parent.invalidateChildInParent(location, dirty);            ....        } while (parent != null);    }}public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {    if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||            (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) {        if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) !=                    FLAG_OPTIMIZE_INVALIDATE) {            ......            return mParent;        } else {            .....            return mParent;        }    }    return null;}

通过上述代码我们可以看到ViewGroupinvalidateChild函数通过循环不断调用其父视图的invalidateChildInParent,而且我们知道ViewRootDecorView的父视图,也就是说ViewRoot是Android视图树状结构的根。所以,最终ViewRootinvalidateChildInParent会被调用。

  //在ViewGroup的invalidateChildInParent中while循环,一直调用到这里,然后在调用invalidateChildpublic ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {        invalidateChild(null, dirty);        return null; } public void invalidateChild(View child, Rect dirty) {    //先检查线程,必须是主线程    checkThread();    .....    //如果mWillDrawSoon为true那么就是消息队列中已经有一个DO_TRAVERSAL的消息啦    if (!mWillDrawSoon) {         //直接调用了这个喽        scheduleTraversals();    }}

最终,在ViewRootinvalidateChild函数中,调用了scheduleTraversals,开启了视图重绘之旅。

我们都被ViewRoot骗了

ViewRoot是Android视图树状结构的根节点,并且它实现了ViewParent接口,是DecorView的父视图。那么大家一定会认为它就是一个View吧。那我们就被它给骗了!!ViewRoot本质上是一个Handler,我们可以看一下scheduleTraversalsperformTraversals的原理就知道了。

public void scheduleTraversals() {    if (!mTraversalScheduled) {        mTraversalScheduled = true;        sendEmptyMessage(DO_TRAVERSAL);    }}

scheduleTraversals中,ViewRoot只是向自己发送了一个DO_TRAVERSAL的空信息。

@Overridepublic void handleMessage(Message msg) {    switch (msg.what) {    ....    case DO_TRAVERSAL:    //这里就是Handle处理travel信息的地方        if (mProfile) {            Debug.startMethodTracing("ViewRoot");        }        performTraversals();        if (mProfile) {            Debug.stopMethodTracing();            mProfile = false;        }        break;        .....    }}

然后我们在查看handleMessage方法,发现在处理DO_TRAVERSAL时,ViewRoot调用了performTraversals函数。

performTraversals中,视图要进行measure,layout,和draw三大步骤,篇幅有限,我们这里只研究绘制相关的机制。

ViewRootperformTraversals中调用了自身的draw方法,看吧,ViewRoot伪装的还挺像,连draw方法都有。但是我们会发现,在draw方法中,ViewRoot实际上只调用了自己的mView成员变量的draw方法,而且我们都知道的是,mView就是DecorView,于是,绘制流程来到了真正的View视图的根节点。

大家都来画的canvas

接下来,我们就正式研究一下Android的绘制机制,我们沿着Android视图的树状结构来分析绘制原理。

Android Scroll详解(三):Android 绘制过程详解_第2张图片

首先是DecorView的绘制相关的函数。在ViewRootdraw方法中,直接调用了DecorViewdraw(Canvas canvas)函数,我们知道DecorViewFrameLayout的子类,其draw(Canvas canvas)函数是从View中继承而来的。所以我们先来看Viewdraw(Canvas canvas)方法。

// http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/view/View.java#Viewpublic void draw(Canvas canvas) {         ........        /*         * Draw traversal performs several drawing steps which must be executed         * in the appropriate order:         *         *      1. Draw the background         *      2. If necessary, save the canvas' layers to prepare for fading         *      3. Draw view's content         *      4. Draw children         *      5. If necessary, draw the fading edges and restore layers         *      6. Draw decorations (scrollbars for instance)         */        // Step 1, draw the background, if needed        if (!dirtyOpaque) {            drawBackground(canvas);        }        .......        // Step 2, save the canvas' layers        .......        // Step 3, draw the content        if (!dirtyOpaque) onDraw(canvas);        // Step 4, draw the children        dispatchDraw(canvas);        // Step 5, draw the fade effect and restore layers        .......        if (drawTop) {            matrix.setScale(1, fadeHeight * topFadeStrength);            matrix.postTranslate(left, top);            fade.setLocalMatrix(matrix);            p.setShader(fade);            canvas.drawRect(left, top, right, top + length, p);        }        .....        // Step 6, draw decorations (scrollbars)        onDrawScrollBars(canvas);        ......    }

关于视图的组成部分,我在之前的文章中已经讲述过来,请不太熟悉这部分内容的同学自行查阅文章或者其他资料。通过上述代码我们可以看到,ViewdispatchDraw函数被调用了,它是向子视图分发绘制指令和相关数据的方法。在View中,上述函数是一个空函数,但是ViewGroup中对这个函数进行了实现。

protected void dispatchDraw(Canvas canvas) {    ....    final ArrayList preorderedList = usingRenderNodeProperties            ? null : buildOrderedChildList();    final boolean customOrder = preorderedList == null            && isChildrenDrawingOrderEnabled();    for (int i = 0; i < childrenCount; i++) {        int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;        final View child = (preorderedList == null)                ? children[childIndex] : preorderedList.get(childIndex);        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {            //在这里drawChild            more |= drawChild(canvas, child, drawingTime);        }    }    ....}protected boolean drawChild(Canvas canvas, View child, long drawingTime) {//这里就调用child的draw方法啦,而不是draw(canvas)方法!!!!!    return child.draw(canvas, this, drawingTime);}

通过上述代码我们可以看到,ViewGroup分别调用了自己的子View的draw方法,需要特别注意的是,这个draw和之前draw方法不是同一个方法,他们的参数不同。于是,我们再次转到View的源码中,看一下这个draw方法到底做了什么。

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {    ....    //进行计算滚动    if (!hasDisplayList) {        computeScroll();        sx = mScrollX;        sy = mScrollY;    }    ...    //这里进行了平移。    if (offsetForScroll) {        canvas.translate(mLeft - sx, mTop - sy);    }    .....     if (!layerRendered) {      if (!hasDisplayList) {        // Fast path for layouts with no backgrounds        if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {          mPrivateFlags &= ~PFLAG_DIRTY_MASK;          dispatchDraw(canvas);        } else {          // 在这里调用了draw          draw(canvas);        }      }             ......    }    ......}

首先,我们发现computeScroll方法是在其中被调用的,从而计算出新的mScrollXmScrollY,然后在平移画布,产生内容平移效果。

然后我们发现通过PFLAG_SKIP_DRAW标志位的判断,有些View是直接调用dispatchDraw函数,说明它自己没有需要绘制的内容,而有些View则是调用自己的draw方法。我们应该都知道ViewGroup默认是不进行绘制内容的吧,我们一般调用setNotWillDraw方法来让其可以绘制自身内容,通过调用setNotWillDraw方法,会导致PFLAG_SKIP_DRAW位被置为1,从而可以绘制自身内容。

分析到这里,我们就会发现draw函数沿着Android视图树状结构被不断调用,知道所有视图都完成绘制。

把一切连接起来的computeScroll

读到这里大家应该对Android视图绘制流程有了基本的了解了吧,那么,我们再来看一下文章开头的例子。在computeScroll方法中,我们调用了postInvalidate方法,这又是什么用意呢?

其实,在computeScroll中不掉用postInvalidate好像也可以达到正确的效果,具体原因我不太了解,猜测应该是Android自动刷新界面可以代替postInvalidate的效果吧。同学们如果知道其中具体原因,请告知我啊。

在《Android Scroll详解(一):基础知识》中,我们已经讲到
postInvalidate其实就是调用了invalidate,然后整个流程就连接了起来,mScrollXmScrollY每个循环都会改变一点,然后导致界面滚动,最终形成界面Scroll效果。

后记

Android Scroll的系列文章就此结束了,希望大家从中学习到有用的知识。如果其中有任何错误或者容易误解的地方,请大家及时通知我。谢谢各位读者和同学。

http://www.cppblog.com/fwxjj/archive/2013/01/13/197231.html
http://blog.csdn.net/luoshengyang/article/details/8372924

更多相关文章

  1. 【Android 界面效果29】研究一下Android滑屏的功能的原理,及scrol
  2. Android中View的绘制过程 onMeasure方法简述 附有自定义View例子
  3. Android 照相机打开方法
  4. AndEngine添加多个动画精灵的方法
  5. Android arm模拟器的速度提升方法
  6. Android getResources().getColor() 过时 替代方法111
  7. Android点击事件方法
  8. canvas.drawRoundRect方法,绘制圆角矩形
  9. android抓log方法

随机推荐

  1. 实现IOS版的抽屉效果(点击,拖拽滑动)
  2. Android(安卓)Studio的应用与快捷键
  3. Monkey入门之如何在android虚拟机中安装a
  4. Android使用getIdentifier()获取资源Id
  5. Android兼容性优化-8.0之后禁止在后台启
  6. [Android5.1]ContentProvider的Binder通
  7. 融云清空历史消息 Android(安卓)端
  8. Android(安卓)Telephony分析(四) ---- Te
  9. Android(安卓)开发入门-常用控件的使用方
  10. Kotlin Android(安卓)Extensions 的未来