作为菜逼,第一次尝试做动画。动画的需求是模仿支付宝付款验密成功的对号动画。
主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貌似不能用