package com.yulin.carrunningarcviewdemo;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.RectF;
import android.graphics.SweepGradient;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
/**
* Created by YuLin on 2017/3/28 0028.
*/
public class CarLoadingViewOne extends View {
private static final String TAG = CarLoadingViewOne.class.getSimpleName();
private static final float DEFAULT_STROKE = 15;
private static final float DEFAULT_START_ANGLE = 150;
private static final float DEFAULT_SWIPE_ANGLE = 240;
private static final int DEFAULT_SLEEP_TIME = 20;
private Paint mPaintInside;
private Paint mPaintOutside;
private int mWidth;
private int mHeight;
private int mCenterX;
private int mCenterY;
private float mRadius;
private RectF mRectOval;
private int mArcInsideColor = Color.WHITE;
private float mArcInsideStroke = DEFAULT_STROKE;
private float mArcOutSideStroke = DEFAULT_STROKE + 5;
private float mStartAngle = DEFAULT_START_ANGLE;
private float mSwipeAngle = DEFAULT_SWIPE_ANGLE;
private int mSleepTime = DEFAULT_SLEEP_TIME;
private int mProgress;
private double mRatio;
private Bitmap mBitmap;
private Matrix mMatrix = null;
private Path mPath;
private PathMeasure mPathMeasure;
private float mBitmapDegrees = 0f;
private float mCurrentValue = 0;
private float[] mPos;
private float[] mTan;
private float mPositions[];
private SweepGradient mSweepGradient;
private int[] mArcInsideColors;
public CarLoadingViewOne(Context context) {
this(context, null);
}
public CarLoadingViewOne(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CarLoadingViewOne(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CarRunningArcView);
int count = a.getIndexCount();
for (int i = 0; i < count; i++) {
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.CarRunningArcView_arcInsideColor:
mArcInsideColor = a.getColor(attr, Color.GRAY);
break;
case R.styleable.CarRunningArcView_arcInsideStroke:
mArcInsideStroke = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 5, getResources().getDisplayMetrics()));
break;
case R.styleable.CarRunningArcView_arcOutSideStroke:
mArcOutSideStroke = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 5, getResources().getDisplayMetrics()));
break;
case R.styleable.CarRunningArcView_radius:
mRadius = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 0, getResources().getDisplayMetrics()));
break;
case R.styleable.CarRunningArcView_sleepTime:
mSleepTime = a.getInt(attr, DEFAULT_SLEEP_TIME);
break;
case R.styleable.CarRunningArcView_ratio:
mRatio = a.getFloat(attr, 0);
break;
default:
break;
}
}
a.recycle();
initPaint();
initMipmap();
initOther();
setProgress(1);
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
if(mProgress < mSwipeAngle) {
mProgress = getProgress();
setCurrentValue();
postInvalidate();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}).start();
}
private void initPaint() {
mPaintInside = getPaint(false);
mPaintOutside = getPaint(true);
}
private Paint getPaint(boolean isOutside) {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStyle(Paint.Style.STROKE);
if(isOutside) {
paint.setStrokeWidth(mArcOutSideStroke);
} else {
paint.setStrokeWidth(mArcInsideStroke);
paint.setColor(mArcInsideColor);
paint.setAlpha(20);
}
return paint;
}
private void initOther() {
mPath = new Path();
mPathMeasure = new PathMeasure();
mMatrix = new Matrix();
mPos = new float[2];
mTan = new float[2];
}
public void setColors(int[] colors) {
if(null == colors || 0 == colors.length) {
throw new NullPointerException("the color array that you defined must not be null.");
}
if(colors.length < 2) {
throw new IllegalArgumentException("you must specify at least two kinds of colors.");
}
mArcInsideColors = colors;
}
private void initMipmap() {
mBitmap = BitmapFactory.decodeResource(this.getContext().getResources(), R.mipmap.icon_big_car);
}
@Override protected void onSizeChanged(int w, int h, int oldW, int oldH) {
super.onSizeChanged(w, h, oldW, oldH);
mWidth = w;
mHeight = h;
mCenterX = mWidth / 2;
mCenterY = mHeight / 2;
if (mRadius + mArcOutSideStroke > Math.min(mWidth / 2, mHeight / 2) || mRadius <= 0) {
mRadius = Math.min(mWidth / 2, mHeight / 2) - mArcOutSideStroke;
}
mRectOval = new RectF(mCenterX - mRadius, mCenterY - mRadius, mCenterX + mRadius, mCenterY + mRadius);
mSweepGradient = new SweepGradient(mCenterX, mCenterX, mArcInsideColors, mPositions);
mPositions = new float[]{0f,0.5f,0.8f,0.9f};
final float rotationDegrees = mSwipeAngle * 1.0f / 6;
mMatrix.setRotate(rotationDegrees, mCenterX, mCenterX);
mSweepGradient.setLocalMatrix(mMatrix);
}
@Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawArcInside(canvas);
drawArcOutside(canvas);
}
/**
* Draw the arc inside as the progress' background.
*
* @param canvas
*/
private void drawArcInside(Canvas canvas) {
mPath.reset();
mPath.addArc(mRectOval, mStartAngle, mSwipeAngle);
canvas.drawPath(mPath, mPaintInside);
}
/**
* Set ratio which makes influenced on the arc outside's swipe angle.
*
* @param ratio
*/
public void setRatio(double ratio) {
if(ratio < 0) {
ratio = 0;
}
if(ratio > 1) {
ratio = 1;
}
this.mRatio = ratio;
setProgress((int) (mSwipeAngle * ratio));
}
/**
* Draw the arc outside for current progress.
*
* @param canvas
*/
private void drawArcOutside(Canvas canvas) {
mPath.reset();
mPath.addArc(mRectOval, mStartAngle, mProgress == 0 ? 0.01f : mProgress);
mPathMeasure.setPath(mPath,false);
mPathMeasure.getPosTan(mPathMeasure.getLength() * mCurrentValue, mPos, mTan);
mBitmapDegrees = (float) (Math.atan2(mTan[1], mTan[0]) * 180.0 / Math.PI);
mMatrix.reset();
mMatrix.postRotate(mBitmapDegrees, mBitmap.getWidth() / 2, mBitmap.getHeight() / 2);
mMatrix.postTranslate(mPos[0] - mBitmap.getWidth() / 2, mPos[1] - mBitmap.getHeight() / 2); // 将图片绘制中心调整到与当前点重合
mPaintOutside.setShader(mSweepGradient);
canvas.drawPath(mPath, mPaintOutside);
canvas.drawBitmap(mBitmap, mMatrix, mPaintOutside);
}
public int getProgress() {
return mProgress;
}
/**
* Set current value([0,1])
*/
private void setCurrentValue() {
mCurrentValue += mProgress * 1.0 / mSwipeAngle;
if (mCurrentValue >= 1) {
mCurrentValue = 1;
}
}
/**
* Get he ratio.
*
* @return
*/
public double getRatio() {
return mRatio;
}
/**
* Saved state for arc's progress.
*/
private static class SavedState extends BaseSavedState {
int progress;
float bitmapDegrees;
float currentValue;
double ratio;
float[] pos = new float[2];
float[] tan = new float[2];
/**
* Constructor called from {@link CarLoadingViewOne#onSaveInstanceState()}
*/
SavedState(Parcelable superState) {
super(superState);
}
/**
* Constructor called from {@link #CREATOR}
*/
private SavedState(Parcel in) {
super(in);
progress = in.readInt();
bitmapDegrees = in.readFloat();
currentValue = in.readFloat();
ratio = in.readDouble();
in.readFloatArray(pos);
in.readFloatArray(tan);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(progress);
out.writeFloat(bitmapDegrees);
out.writeFloat(currentValue);
out.writeDouble(ratio);
out.writeFloatArray(pos);
out.writeFloatArray(tan);
}
public static final Creator<SavedState> CREATOR
= new Creator<CarLoadingViewOne.SavedState>() {
public CarLoadingViewOne.SavedState createFromParcel(Parcel in) {
return new CarLoadingViewOne.SavedState(in);
}
public CarLoadingViewOne.SavedState[] newArray(int size) {
return new CarLoadingViewOne.SavedState[size];
}
};
}
@Override public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.progress = mProgress;
ss.bitmapDegrees = mBitmapDegrees;
ss.currentValue = mCurrentValue;
ss.ratio = mRatio;
ss.pos = mPos;
ss.tan = mTan;
return ss;
}
@Override public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
setProgress(ss.progress);
mBitmapDegrees = ss.bitmapDegrees;
mCurrentValue = ss.currentValue;
mRatio = ss.ratio;
mPos = ss.pos;
mTan = ss.tan;
}
public void setProgress(int progress) {
this.mProgress = progress;
}
/**
* Get sleep time which makes influenced on arc outside's drawing speed.
*
* @return
*/
public int getSleepTime() {
return mSleepTime;
}
/**
* Set sleep time which makes influence on the arc's drawing speed.
*
* @param sleepTime
*/
public void setSleepTime(int sleepTime) {
this.mSleepTime = sleepTime;
}
/**
* Get arc inside color.
*
* @return
*/
public int getArcInsideColor() {
return mArcInsideColor;
}
/**
* Set arc inside color.
*
* @param arcInsideColor
*/
public void setArcInsideColor(int arcInsideColor) {
this.mArcInsideColor = arcInsideColor;
invalidate();
}
/**
* Get arc inside stroke.
*
* @return
*/
public float getArcInsideStroke() {
return mArcInsideStroke;
}
/**
* Set arc inside stroke which makes influence on the arc's width.
*
* @param arcInsideStroke
*/
public void setArcInsideStroke(float arcInsideStroke) {
this.mArcInsideStroke = arcInsideStroke;
invalidate();
}
/**
* Get arc outside's stroke.
*
* @return
*/
public float getArcOutSideStroke() {
return mArcOutSideStroke;
}
/**
* Set arc outside's stroke which makes influenced on arc's width.
*
* @param arcOutSideStroke
*/
public void setArcOutSideStroke(float arcOutSideStroke) {
this.mArcOutSideStroke = arcOutSideStroke;
invalidate();
}
/**
* Get arc's radius.
*
* @return
*/
public float getRadius() {
return mRadius;
}
/**
* Set arc's radius which makes influenced on arc's width and height.
*
* @param radius
*/
public void setRadius(float radius) {
this.mRadius = radius;
}
/**
* Get the bitmap using for moving forward or back.
* @return
*/
public Bitmap getBitmap() {
return mBitmap;
}
/**
* Set the bitmap using for moving forward or back.
* @param bitmap
*/
public void setBitmap(Bitmap bitmap) {
if(null == bitmap) {
throw new NullPointerException("the bitmap that you specified must not be null.");
}
this.mBitmap = bitmap;
}
}