仿qq记步效果及invalidate源码分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wangwo1991/article/details/84559696
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方法进行绘制了。

猜你喜欢

转载自blog.csdn.net/wangwo1991/article/details/84559696