对号动画实现

作为菜逼,第一次尝试做动画。动画的需求是模仿支付宝付款验密成功的对号动画。

主View的代码:

package gt.research.test.androidtest;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.widget.ImageView;

public class TickView extends ImageView {
    public static final int FRAME = 16;
    public static final int INIT = 0;
    public static final int DIRTY = 1;
    public static final int DRAWN = 2;
    public static final int DONE = 3;
    //ppi related
    private static final int sMARGIN = 5;
    private Paint mPaint;
    private float mMiddleX;
    private float mStartX;
    private float mEndX;
    private float mBaseY;
    private int mOffset;
    private int mTickPaddingLeft;
    private int mTickPaddingRight;
    private int mTickPaddingBottom;

    private static final int sDEFAULT_COLOR = Color.parseColor("#00307f");
    private static final int sDEFAULT_STROCK = 10;
    private static int sDEFAULT_DURATION = 100;
    private static int sDEFAULT_PADDING = 20;

    private volatile float mX;
    private volatile float mY;
    private float mB0;
    private float mB1;
    private float mStep;
    private long mDuration;

    private long mDelay = FRAME;
    private volatile int mState = INIT;
    private Handler mHandler;
    private volatile Rect mDirty;
    private Runnable mPostInvalidate = new Runnable() {
        @Override
        public void run() {
            if (DRAWN == mState) {
                mDirty = getDirtyRect(mX, mY);
                if (null == mDirty) {
                    mState = DONE;
                    return;
                }
                invalidate(mDirty);
            }
            if (INIT == mState || DRAWN == mState || DIRTY == mState) {
                mHandler.removeCallbacks(mPostInvalidate);
                mHandler.postDelayed(mPostInvalidate, mDelay);
                if (DRAWN == mState) {
                    mState = DIRTY;
                }
            }
        }
    };

    public TickView(Context context) {
        super(context);
        init(null, 0);
    }

    public TickView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs, 0);
    }

    public TickView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(attrs, defStyle);
    }

    private void init(AttributeSet attrs, int defStyle) {
        mPaint = new Paint();
        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.TickView);
        mPaint.setColor(a.getColor(R.styleable.TickView_tickColor, sDEFAULT_COLOR));
        mPaint.setStrokeWidth(a.getDimensionPixelSize(R.styleable.TickView_strokeWidth, sDEFAULT_STROCK));
        mOffset = a.getDimensionPixelOffset(R.styleable.TickView_turnOffset, 0);
        mDuration = a.getInt(R.styleable.TickView_duration, sDEFAULT_DURATION);
        mTickPaddingLeft = a.getDimensionPixelSize(R.styleable.TickView_tickPaddingLeft, sDEFAULT_PADDING);
        mTickPaddingRight = a.getDimensionPixelSize(R.styleable.TickView_tickPaddingRight, sDEFAULT_PADDING);
        mTickPaddingBottom = a.getDimensionPixelSize(R.styleable.TickView_tickPaddingBottom, sDEFAULT_COLOR);
        mHandler = new Handler(Looper.getMainLooper());
        a.recycle();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mMiddleX = getMeasuredWidth() / 2 - mOffset;
        mX = mStartX = mTickPaddingLeft;
        mEndX = getMeasuredWidth() - mTickPaddingRight;
        mBaseY = getMeasuredHeight() - mTickPaddingBottom;

        mB0 = mBaseY - mMiddleX;
        mB1 = mBaseY + mMiddleX;
        mY = mX + mB0;

        mStep = (mEndX - mStartX) / (float) mDuration * FRAME;
        setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));
    }

    public void startAnimation() {
        mHandler.post(mPostInvalidate);
        mState = INIT;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (DONE == mState) {
            super.onDraw(canvas);
            drawTotal(canvas);
            return;
        }
        long start = System.currentTimeMillis();
        if (canvas.isHardwareAccelerated()) {
            super.onDraw(canvas);
            drawWhole(canvas);
        } else {
            if (null != mDirty) {
                canvas.clipRect(mDirty);
            }
            super.onDraw(canvas);
            drawPartial(canvas);
        }
        mState = DRAWN;
        long end = System.currentTimeMillis();
        mDelay = FRAME - end + start;
        mDelay = mDelay < 0 ? 0 : mDelay;
    }

    private void drawWhole(Canvas canvas) {
        if (mX + mStep < mMiddleX) {
            canvas.drawLine(mStartX, mStartX + mB0, mX + mStep + sMARGIN, mY + mStep + sMARGIN, mPaint);
            mX += mStep;
            mY += mStep;
        } else {
            canvas.drawLine(mStartX, mStartX + mB0, mMiddleX + sMARGIN, mBaseY + sMARGIN, mPaint);
            canvas.drawLine(mBaseY - mB0 - sMARGIN, mBaseY + sMARGIN, mX + sMARGIN, mB1 - mX - sMARGIN, mPaint);
            mX += mStep;
            mY -= mStep;
        }
    }

    private void drawPartial(Canvas canvas) {
        if (mX + mStep < mMiddleX) {
            canvas.drawLine(mX - sMARGIN, mY - sMARGIN, mX + mStep + sMARGIN, mY + mStep + sMARGIN, mPaint);
            mX += mStep;
            mY += mStep;
        } else if (mX > mMiddleX) {
            canvas.drawLine(mX - sMARGIN, mY + sMARGIN, mX + mStep + sMARGIN, mY - mStep - sMARGIN, mPaint);
            mX += mStep;
            mY -= mStep;
        } else {
            canvas.drawLine(mX - sMARGIN, mY - sMARGIN, mBaseY - mB0 + sMARGIN, mBaseY + sMARGIN, mPaint);
            canvas.drawLine(mBaseY - mB0 - sMARGIN, mBaseY + sMARGIN, mX + mStep + sMARGIN, mB1 - mX - mStep - sMARGIN, mPaint);
            mX += mStep;
            mY = mB1 - mX;
        }
    }

    private void drawTotal(Canvas canvas) {
        canvas.drawLine(mStartX, mStartX + mB0, mMiddleX + sMARGIN, mBaseY + sMARGIN, mPaint);
        canvas.drawLine(mBaseY - mB0 - sMARGIN, mBaseY + sMARGIN, mEndX, mB1 - mEndX, mPaint);
    }

    private Rect getDirtyRect(float x, float y) {
        if (x >= mEndX) {
            return null;
        }
        Rect rect = new Rect();
        if (x + mStep < mMiddleX) {
            rect.left = (int) x - sMARGIN;
            rect.right = (int) (x + mStep) + sMARGIN;
            rect.top = (int) y - sMARGIN;
            rect.bottom = (int) (y + mStep) + sMARGIN;
        } else if (x > mMiddleX) {
            rect.left = (int) x - sMARGIN;
            rect.right = (int) (x + mStep) + sMARGIN;
            rect.top = (int) (y - mStep) - sMARGIN;
            rect.bottom = (int) y + sMARGIN;
        } else {
            rect.left = (int) x - sMARGIN;
            rect.right = (int) (x + mStep) + sMARGIN;
            rect.top = (int) Math.min(y, mB1 - x - mStep) - sMARGIN;
            rect.bottom = (int) mBaseY + sMARGIN;
        }
        return rect;
    }
}

大体思路是,外围圆圈是背景图片,中间对勾使用drawLine渐变绘出。
有几个坑:
- 硬件加速的View不会对某块DirtyRect重绘,而是全局重绘参考
- draw时使用的是int,需要在转化时加上margin,避免强转时露点。margin与ppi相关,基本xhdpi就是1,xx是2
- onDraw时postInvalidate貌似不能用

你可能感兴趣的:(学Android,代码段)