1. 程式人生 > >仿qq記步效果及invalidate原始碼分析

仿qq記步效果及invalidate原始碼分析

public class MyStepView extends View{
    //中間文字大小
    private int MyStepTextSize=30;
    //圓環邊框大小
    private int MyStepWidth=20;
    //中間文字顏色
    private int MyStepTextColor= Color.parseColor("#000000");
    //內環顏色
    private int MyStepInnerColor=Color.parseColor("#000000");
    //外環顏色
    private int MyStepOuterColor=Color.parseColor("#000000");
    //外環畫筆
    private Paint outPaint;
    //內環畫筆
    private Paint innerPaint;
    //繪製文字畫筆
    private Paint textPaint;
    //總步數
    private int mMaxStep=0;
    //當前步數
    private int mCurrentStep=0;
    public MyStepView(Context context) {
        this(context,null);
    }

    public MyStepView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public MyStepView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyStepView);
        MyStepTextSize=a.getDimensionPixelSize(R.styleable.MyStepView_myStepTextSize,sp2px(MyStepTextSize));
        MyStepWidth= (int) a.getDimension(R.styleable.MyStepView_myStepWidth,dip2px(MyStepWidth));
        MyStepTextColor=a.getColor(R.styleable.MyStepView_myStepTextColor,MyStepTextColor);
        MyStepInnerColor=a.getColor(R.styleable.MyStepView_myStepInnerColor,MyStepInnerColor);
        MyStepOuterColor=a.getColor(R.styleable.MyStepView_myStepOuterColor,MyStepOuterColor);
        a.recycle();
        initPaint();
    }

    private void initPaint() {
        //初始化外環畫筆
        outPaint=new Paint();
        outPaint.setAntiAlias(true);
        outPaint.setStrokeWidth(MyStepWidth);
        outPaint.setColor(MyStepOuterColor);
        outPaint.setStyle(Paint.Style.STROKE);
        outPaint.setStrokeCap(Paint.Cap.ROUND);

        //初始化內環畫筆
        innerPaint=new Paint();
        innerPaint.setAntiAlias(true);
        innerPaint.setStrokeWidth(MyStepWidth);
        innerPaint.setColor(MyStepInnerColor);
        innerPaint.setStyle(Paint.Style.STROKE);
        innerPaint.setStrokeCap(Paint.Cap.ROUND);
        //初始化文字畫筆
        textPaint=new Paint();
        textPaint.setAntiAlias(true);
        textPaint.setTextSize(MyStepTextSize);
        textPaint.setColor(MyStepTextColor);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //獲取寬高模式
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode=MeasureSpec.getMode(heightMeasureSpec);
        //獲取寬高
        int width=MeasureSpec.getSize(widthMeasureSpec);
        int height=MeasureSpec.getSize(heightMeasureSpec);
        if(widthMode==MeasureSpec.AT_MOST){
            width= (int) dip2px(300);
        }
        if(heightMode==MeasureSpec.AT_MOST){
            height= (int) dip2px(300);
        }
        width=height=Math.max(width,height);
        setMeasuredDimension(width,height);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //中心點座標
        int center=getWidth()/2;
        int widthCount=center-MyStepWidth/2;
        //繪製外環
        RectF rectf=new RectF(center-widthCount,center-widthCount,center+widthCount,center+widthCount);
        canvas.drawArc(rectf,135,270,false,outPaint);
        if(mMaxStep==0){
            return;
        }
        float sweepAngle=(float) mCurrentStep/(float)mMaxStep*270;
        //繪製內環
        canvas.drawArc(rectf,135,sweepAngle,false,innerPaint);
        //繪製文字
        String text=mCurrentStep+"";
        //計算文字起始位置
        Rect bounds=new Rect();
        textPaint.getTextBounds(text,0,text.length(),bounds);
        float x=getWidth()/2-bounds.width()/2;
        //計算文字基線
        Paint.FontMetricsInt metricsInt = textPaint.getFontMetricsInt();
        int dy=(metricsInt.bottom-metricsInt.top)/2-metricsInt.bottom;
        float baseline=getHeight()/2+dy;
        canvas.drawText(text,x,baseline,textPaint);
    }

    /**
     * 設定最大步數
     * @param maxStep
     */
    public void setMaxStep(int maxStep){
        this.mMaxStep=maxStep;
    }

    /**
     * 設定當前步數
     * @param currentStep
     */
    public void setCurrentStep(int currentStep){
        this.mCurrentStep=currentStep;
        //進行重繪
        invalidate();
    }
    private float dip2px(int dip) {
        return  TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dip,getResources().getDisplayMetrics());
    }

    private int sp2px(int sp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,sp,getResources().getDisplayMetrics());
    }
}
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final MyStepView stepView = (MyStepView) findViewById(R.id.step_view);
        stepView.setMaxStep(5000);
        //設定屬性動畫
        ValueAnimator animator = ObjectAnimator.ofFloat(0, 3000);
        animator.setDuration(5000);
        animator.setInterpolator(new DecelerateInterpolator());
        //監聽動畫進度
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float animatedValue = (float) animation.getAnimatedValue();
                stepView.setCurrentStep((int)animatedValue);
            }
        });
        //開啟動畫
        animator.start();
    }
}

這個就是一個實現的大致效果,在setCurrentStep()方法中呼叫了invalidate()方法對其每次步數的變化進行繪製,在呼叫invalidate()方法後就會去View中的invalidate()方法,接著回去呼叫View中的invalidateInternal()方法,

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
            boolean fullInvalidate) {
        if (mGhostView != null) {
            mGhostView.invalidate(true);
            return;
        }
//在skipInvalidate方法中會去判斷是否可,當前是否有動畫
        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;
//將mParent賦值給ViewParent
            final ViewParent p = mParent;
            if (p != null && ai != null && l < r && t < b) {
                final Rect damage = ai.mTmpInvalRect;
                damage.set(l, t, r, b);
//呼叫ViewParent中的invalidateChild方法,而ViewParent是一個介面,
//最終呼叫的是實現了中的invalidateChild方法,而ViewGroup和ViewRootImpl都是其實現類,所以
//應該呼叫了ViewGroup中的invalidateChild方法
                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();
            }
        }
    }

最終呼叫了ViewParent中的invalidateChild方法,但是ViewParent是一個介面,就要去找其實現類,ViewGroup和ViewRootImpl是其實現類,這裡首先呼叫的是ViewGoup中的invalidateChild方法;

 //ViewGoup中invalidateChild方法原始碼
@Override
    public final void invalidateChild(View child, final Rect dirty) {
//首先將當前ViewGroup物件賦值給父類ViewParent
        ViewParent parent = this;

        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            // If the child is drawing an animation, we want to copy this flag onto
            // ourselves and the parent to make sure the invalidate request goes
            // through
            final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION)
                    == PFLAG_DRAW_ANIMATION;

            // Check whether the child that requests the invalidate is fully opaque
            // Views being animated or transformed are not considered opaque because we may
            // be invalidating their old position and need the parent to paint behind them.
            Matrix childMatrix = child.getMatrix();
            final boolean isOpaque = child.isOpaque() && !drawAnimation &&
                    child.getAnimation() == null && childMatrix.isIdentity();
            // Mark the child as dirty, using the appropriate flag
            // Make sure we do not set both flags at the same time
            int opaqueFlag = isOpaque ? PFLAG_DIRTY_OPAQUE : PFLAG_DIRTY;

            if (child.mLayerType != LAYER_TYPE_NONE) {
                mPrivateFlags |= PFLAG_INVALIDATED;
                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
            }

            final int[] location = attachInfo.mInvalidateChildLocation;
            location[CHILD_LEFT_INDEX] = child.mLeft;
            location[CHILD_TOP_INDEX] = child.mTop;
            if (!childMatrix.isIdentity() ||
                    (mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
                RectF boundingRect = attachInfo.mTmpTransformRect;
                boundingRect.set(dirty);
                Matrix transformMatrix;
                if ((mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
                    Transformation t = attachInfo.mTmpTransformation;
                    boolean transformed = getChildStaticTransformation(child, t);
                    if (transformed) {
                        transformMatrix = attachInfo.mTmpMatrix;
                        transformMatrix.set(t.getMatrix());
                        if (!childMatrix.isIdentity()) {
                            transformMatrix.preConcat(childMatrix);
                        }
                    } else {
                        transformMatrix = childMatrix;
                    }
                } else {
                    transformMatrix = childMatrix;
                }
                transformMatrix.mapRect(boundingRect);
                dirty.set((int) Math.floor(boundingRect.left),
                        (int) Math.floor(boundingRect.top),
                        (int) Math.ceil(boundingRect.right),
                        (int) Math.ceil(boundingRect.bottom));
            }
//這裡是一個do while迴圈,當parent不為null的時候就會跳出迴圈
            do {
                View view = null;
                if (parent instanceof View) {
                    view = (View) parent;
                }

                if (drawAnimation) {
                    if (view != null) {
                        view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
                    } else if (parent instanceof ViewRootImpl) {
                        ((ViewRootImpl) parent).mIsAnimating = true;
                    }
                }

                // If the parent is dirty opaque or not dirty, mark it dirty with the opaque
                // flag coming from the child that initiated the invalidate
                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;
                    }
                }
//這裡會一直遍歷迴圈呼叫invalidateChildInParent方法,會一直往外傳遞,一直到最外層,
//到最外層的時候,呼叫的就是ViewRootImpl中的invalidateChildInParent方法
                parent = parent.invalidateChildInParent(location, dirty);
                if (view != null) {
                    // Account for transform on current parent
                    Matrix m = view.getMatrix();
                    if (!m.isIdentity()) {
                        RectF boundingRect = attachInfo.mTmpTransformRect;
                        boundingRect.set(dirty);
                        m.mapRect(boundingRect);
                        dirty.set((int) Math.floor(boundingRect.left),
                                (int) Math.floor(boundingRect.top),
                                (int) Math.ceil(boundingRect.right),
                                (int) Math.ceil(boundingRect.bottom));
                    }
                }
            } while (parent != null);
        }
    }

在invalidateChild方法中會有一個do while的迴圈,會一直遍歷呼叫invalidateChildInParent方法,並往外傳遞,一直傳遞到最外成,在最外成時就會去呼叫ViewRootImpl中的invalidateChildInParent方法;

//ViewRootImpl中invalidateChildInParent方法原始碼
@Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
//呼叫checkThread方法去檢測當前執行緒是否是主執行緒,如果不是主執行緒就會拋異常,
//這也就是不能在子執行緒中更新ui的原因
        checkThread();
        if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);

        if (dirty == null) {
            invalidate();
            return null;
        } 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方法
        invalidateRectOnScreen(dirty);

        return null;
    }

在invalidateRectOnScreen()方法中接著呼叫了scheduleTraversals();

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//這裡呼叫postCallback並傳入一個TraversalRunnable
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

在傳入的TraversalRunnable類中就會去呼叫doTraversal()方法;

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

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
//呼叫preformTraversals方法
            performTraversals();

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

在performTraversals方法中就會呼叫performMeasure、performLayout、performDraw等方法進行測量、擺放、繪製,不過需要注意的是在invalidate方法流程中不會呼叫performMeasure和performLayout方法,只會呼叫performDraw方法,所有這裡就先看performDraw方法;在performDraw方法中就會去呼叫draw方法,接著會去呼叫draw方法中的drawSoftware方法;在drawSoftware方法中會呼叫View中的draw方法並傳入一個canvas畫布,到這裡終於回到了View中的draw方法了;

//View中draw方法原始碼
public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
//下面會根據dirtyOpaque 標識來判斷onDraw()等方法的呼叫
        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *       整個繪製工作大致可以分為6步    
                第一步:繪製背景
         *      1. Draw the background
                第二步:必要時,儲存畫布的層以準備漸變
         *      2. If necessary, save the canvas' layers to prepare for fading
                 第三步:繪製內容
         *      3. Draw view's content
                第四步:繪製子view
         *      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
        int saveCount;

        if (!dirtyOpaque) {
//dirtyOpaque標識來控制是否需要繪製背景,如果需要才會呼叫drawBackground方法繪製背景,
//否則不會呼叫drawBackground方法
            drawBackground(canvas);
        }

        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
//在繪製內容的時候也會根據dirtyOpaque標識來決定是否呼叫onDraw方法,
//在正常情況下ViewGoup是不會呼叫onDraw方法的,就是說extend ViewGroup的自定義view或者容器都不會呼叫onDraw方法
            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children
//繪製子view不管是extend View的還是extend ViewGroup的都會呼叫,
//所以在extend ViewGroup的自定義view或者容器中其實可以重寫dispatchDraw()方法
            dispatchDraw(canvas);

            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // Step 6, draw decorations (foreground, scrollbars)
//繪製裝飾
            onDrawForeground(canvas);

            // we're done...
            return;
        }

        /*
         * Here we do the full fledged routine...
         * (this is an uncommon case where speed matters less,
         * this is why we repeat some of the tests that have been
         * done above)
         */

        boolean drawTop = false;
        boolean drawBottom = false;
        boolean drawLeft = false;
        boolean drawRight = false;

        float topFadeStrength = 0.0f;
        float bottomFadeStrength = 0.0f;
        float leftFadeStrength = 0.0f;
        float rightFadeStrength = 0.0f;

        // Step 2, save the canvas' layers
        int paddingLeft = mPaddingLeft;

        final boolean offsetRequired = isPaddingOffsetRequired();
        if (offsetRequired) {
            paddingLeft += getLeftPaddingOffset();
        }

        int left = mScrollX + paddingLeft;
        int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
        int top = mScrollY + getFadeTop(offsetRequired);
        int bottom = top + getFadeHeight(offsetRequired);

        if (offsetRequired) {
            right += getRightPaddingOffset();
            bottom += getBottomPaddingOffset();
        }

        final ScrollabilityCache scrollabilityCache = mScrollCache;
        final float fadeHeight = scrollabilityCache.fadingEdgeLength;
        int length = (int) fadeHeight;

        // clip the fade length if top and bottom fades overlap
        // overlapping fades produce odd-looking artifacts
        if (verticalEdges && (top + length > bottom - length)) {
            length = (bottom - top) / 2;
        }

        // also clip horizontal fades if necessary
        if (horizontalEdges && (left + length > right - length)) {
            length = (right - left) / 2;
        }

        if (verticalEdges) {
            topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
            drawTop = topFadeStrength * fadeHeight > 1.0f;
            bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
            drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
        }

        if (horizontalEdges) {
            leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
            drawLeft = leftFadeStrength * fadeHeight > 1.0f;
            rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
            drawRight = rightFadeStrength * fadeHeight > 1.0f;
        }

        saveCount = canvas.getSaveCount();

        int solidColor = getSolidColor();
        if (solidColor == 0) {
            final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;

            if (drawTop) {
                canvas.saveLayer(left, top, right, top + length, null, flags);
            }

            if (drawBottom) {
                canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
            }

            if (drawLeft) {
                canvas.saveLayer(left, top, left + length, bottom, null, flags);
            }

            if (drawRight) {
                canvas.saveLayer(right - length, top, right, bottom, null, flags);
            }
        } else {
            scrollabilityCache.setFadeColor(solidColor);
        }

        // 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
        final Paint p = scrollabilityCache.paint;
        final Matrix matrix = scrollabilityCache.matrix;
        final Shader fade = scrollabilityCache.shader;

        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);
        }

        if (drawBottom) {
            matrix.setScale(1, fadeHeight * bottomFadeStrength);
            matrix.postRotate(180);
            matrix.postTranslate(left, bottom);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(left, bottom - length, right, bottom, p);
        }

        if (drawLeft) {
            matrix.setScale(1, fadeHeight * leftFadeStrength);
            matrix.postRotate(-90);
            matrix.postTranslate(left, top);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(left, top, left + length, bottom, p);
        }

        if (drawRight) {
            matrix.setScale(1, fadeHeight * rightFadeStrength);
            matrix.postRotate(90);
            matrix.postTranslate(right, top);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(right - length, top, right, bottom, p);
        }

        canvas.restoreToCount(saveCount);

        // Overlay is part of the content and draws beneath Foreground
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }

        // Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);
    }

這個時候就會去執行自定義中onDraw方法進行繪製了。