最近想把項(xiàng)目中用到的東西都寫出來(lái)虫啥,剛好做上傳時(shí)手寫了一個(gè)加載控件蔚约,在這就寫下來(lái)。效果如圖:
看到這個(gè)涂籽,會(huì)的人就不說(shuō)了苹祟,很簡(jiǎn)單,不會(huì)的人就想著網(wǎng)上找评雌,但是網(wǎng)上找的有不如意树枫,而且改起來(lái)還要讀懂別人代碼,其他的就要UI切圖景东,做成幀動(dòng)畫砂轻,但是這樣的東西還是沒(méi)必要做成幀動(dòng)畫,而且從這個(gè)入手還可以回顧下自定義view的步驟斤吐,所以還是動(dòng)手寫一個(gè)唄舔清。
創(chuàng)建類,集成View實(shí)現(xiàn)其構(gòu)造方法曲初。
public CircularLinesProgress(Context context) {
this(context, null);
}
public CircularLinesProgress(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CircularLinesProgress(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
View的繪制流程是onMeasure体谒、onDraw、onLayout臼婆,onMeasure是計(jì)算我們子View要多大的設(shè)置抒痒,這里我們的加載控件肯定是一個(gè)正方形,不管布局怎么設(shè)置寬高颁褂,我們都取最小的故响,木桶效應(yīng)將其搞成個(gè)正方形傀广,如代碼:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mWidth = MeasureSpec.getSize(widthMeasureSpec);
mHeigth = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(mWidth >= mHeigth ? mHeigth : mWidth, mWidth >= mHeigth ? mHeigth : mWidth);
}
然后就是畫,就需要我們實(shí)現(xiàn)onDraw方法彩届,里面自帶一個(gè)畫布伪冰,那我們的畫筆呢?而且畫筆還有樣式要設(shè)置樟蠕,找一個(gè)實(shí)例化畫筆最好的地方贮聂,就莫過(guò)于構(gòu)造方法了。
public CircularLinesProgress(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint = new Paint();
mPaint.setDither(true);
mPaint.setAntiAlias(true);
mPaint.setColor(Color.GRAY);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mLineWidth);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mChangePaint = new Paint();
mChangePaint.setDither(true);
mChangePaint.setAntiAlias(true);
mChangePaint.setColor(Color.RED);
mChangePaint.setStyle(Paint.Style.STROKE);
mChangePaint.setStrokeWidth(mLineWidth);
mChangePaint.setStrokeCap(Paint.Cap.ROUND);
mChangePaint.setStrokeJoin(Paint.Join.ROUND);
}
然后就是畫寨辩,onDraw里面我們先畫灰色的吓懈,我們先上個(gè)圖分析下:
我們實(shí)際看到的刻度線,是不是都夾雜在大圓與小圓的中間靡狞?只看12點(diǎn)方向的刻度耻警,線的兩端X值都是一樣的是圓形也是控件的寬的一半。然后看Y值甸怕,起點(diǎn)的Y是不是圓心的Y往上減去一定數(shù)值甘穿?終點(diǎn)是不是要減去更多?所以順著這個(gè)思路我們就能控制好畫刻度了梢杭,這個(gè)一定數(shù)值我取的是控件高度一半的百分比温兼。那么第二根第三根怎么辦?唉式曲?是不是只要將畫布轉(zhuǎn)一個(gè)角度在劃線是不是就可以了妨托?至于轉(zhuǎn)多少度缸榛,那就是360度除以我們刻度線的總數(shù)了吝羞。如代碼:
@Override
protected void onDraw(Canvas canvas) {
int x = mWidth / 2;
int y = mHeigth / 2;
int r = (int) (mWidth * 0.45);
canvas.save();
for (int i = 0; i < mLinesNumber; i++) {
//繪制下層菊花
canvas.drawLine(x, y - r, x, (float) (y - r + x * 0.4), mPaint);
canvas.rotate(360 / mLinesNumber, x, y);
}
canvas.restore();
}
這就是畫灰色刻度。
那么就輪到畫紅色進(jìn)度刻度了内颗,仔細(xì)觀察其實(shí)就是在灰色上覆蓋一個(gè)紅色的钧排,進(jìn)度的話用屬性動(dòng)畫控制,如代碼:
public void start() {
if(valueAnimator==null){
valueAnimator = ValueAnimator.ofFloat(0, 1.0f);
valueAnimator.setDuration(1000);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float value = (float) valueAnimator.getAnimatedValue();
setCurrentProgress(value);
}
});
valueAnimator.start();
}
}
設(shè)置當(dāng)前進(jìn)度均澳,并調(diào)用刷新
private float mCurrentProgress = 0f;
public void setCurrentProgress(float mCurrentProgress) {
this.mCurrentProgress = mCurrentProgress;
postInvalidate();
}
然后再上面剛畫灰色的地方畫紅色
@Override
protected void onDraw(Canvas canvas) {
int x = mWidth / 2;
int y = mHeigth / 2;
int r = (int) (mWidth * 0.45);
canvas.save();
for (int i = 0; i < mLinesNumber; i++) {
//繪制下層菊花
canvas.drawLine(x, y - r, x, (float) (y - r + x * 0.4), mPaint);
canvas.rotate(360 / mLinesNumber, x, y);
}
//應(yīng)該畫多少
int currentContent = (int) (mLinesNumber * mCurrentProgress);
for (int i = currentContent; i > 0; i--) {
//繪制下層菊花
canvas.drawLine(x, y - r, x, (float) (y - r + x * 0.4), mChangePaint);
canvas.rotate(360 / mLinesNumber, x, y);
}
canvas.restore();
}
這樣我們就能完成一次循環(huán)了恨溜,但是UI的建議是循環(huán)并且紅色和灰色交替作為進(jìn)度刻度,這樣看著順眼找前。那么動(dòng)畫就要加個(gè)循環(huán)了
public void start() {
if(valueAnimator==null){
valueAnimator = ValueAnimator.ofFloat(0, 1.0f);
valueAnimator.setDuration(1000);
valueAnimator.setRepeatCount(-1);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationRepeat(Animator animation) {
mRunContent += 1;
}
});
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float value = (float) valueAnimator.getAnimatedValue();
setCurrentProgress(value);
}
});
valueAnimator.start();
}
}
mRunContent 變量就是控制紅灰交替進(jìn)度了糟袁,修改下onDraw
@Override
protected void onDraw(Canvas canvas) {
mPaint.setStrokeWidth(mLineWidth);
mChangePaint.setStrokeWidth(mLineWidth);
int x = mWidth / 2;
int y = mHeigth / 2;
int r = (int) (mWidth * 0.45);
canvas.save();
if (mRunContent % 2 == 0) {
for (int i = 0; i < mLinesNumber; i++) {
//繪制下層菊花
canvas.drawLine(x, y - r, x, (float) (y - r + x * 0.4), mPaint);
canvas.rotate(360 / mLinesNumber, x, y);
}
//應(yīng)該畫多少
int currentContent = (int) (mLinesNumber * mCurrentProgress);
for (int i = currentContent; i > 0; i--) {
//繪制下層菊花
canvas.drawLine(x, y - r, x, (float) (y - r + x * 0.4), mChangePaint);
canvas.rotate(360 / mLinesNumber, x, y);
}
} else {
for (int i = 0; i < mLinesNumber; i++) {
//繪制下層菊花
canvas.drawLine(x, y - r, x, (float) (y - r + x * 0.4), mChangePaint);
canvas.rotate(360 / mLinesNumber, x, y);
}
//應(yīng)該畫多少
int currentContent = (int) (mLinesNumber * mCurrentProgress);
for (int i = currentContent; i > 0; i--) {
//繪制下層菊花
canvas.drawLine(x, y - r, x, (float) (y - r + x * 0.4), mPaint);
canvas.rotate(360 / mLinesNumber, x, y);
}
}
canvas.restore();
}
好,到此為止躺盛,效果達(dá)到项戴,但是我們用到了動(dòng)畫,就要記得銷毀動(dòng)畫槽惫,所以我們要做一個(gè)銷毀方法周叮,方便與Activity的onDestory綁定辩撑。
public void cancel(){
if(valueAnimator!=null){
valueAnimator.cancel();
valueAnimator.end();
valueAnimator.addUpdateListener(null);
valueAnimator.addListener(null);
}
}
完整代碼:
public class CircularLinesProgress extends View {
private int mLinesNumber = 20;
private int mLineWidth = 6;
private int mWidth;
private int mHeigth;
private Paint mPaint;
private float mCurrentProgress = 0f;
private Paint mChangePaint;
private int mRunContent = 0;
private ValueAnimator valueAnimator;
public void setCurrentProgress(float mCurrentProgress) {
this.mCurrentProgress = mCurrentProgress;
postInvalidate();
}
public CircularLinesProgress(Context context) {
this(context, null);
}
public CircularLinesProgress(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CircularLinesProgress(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint = new Paint();
mPaint.setDither(true);
mPaint.setAntiAlias(true);
mPaint.setColor(Color.GRAY);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mLineWidth);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mChangePaint = new Paint();
mChangePaint.setDither(true);
mChangePaint.setAntiAlias(true);
mChangePaint.setColor(Color.RED);
mChangePaint.setStyle(Paint.Style.STROKE);
mChangePaint.setStrokeWidth(mLineWidth);
mChangePaint.setStrokeCap(Paint.Cap.ROUND);
mChangePaint.setStrokeJoin(Paint.Join.ROUND);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mWidth = MeasureSpec.getSize(widthMeasureSpec);
mHeigth = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(mWidth >= mHeigth ? mHeigth : mWidth, mWidth >= mHeigth ? mHeigth : mWidth);
mLineWidth = mHeigth / mLinesNumber;
}
@Override
protected void onDraw(Canvas canvas) {
mPaint.setStrokeWidth(mLineWidth);
mChangePaint.setStrokeWidth(mLineWidth);
int x = mWidth / 2;
int y = mHeigth / 2;
int r = (int) (mWidth * 0.45);
canvas.save();
if (mRunContent % 2 == 0) {
for (int i = 0; i < mLinesNumber; i++) {
//繪制下層菊花
canvas.drawLine(x, y - r, x, (float) (y - r + x * 0.4), mPaint);
canvas.rotate(360 / mLinesNumber, x, y);
}
//應(yīng)該畫多少
int currentContent = (int) (mLinesNumber * mCurrentProgress);
for (int i = currentContent; i > 0; i--) {
//繪制下層菊花
canvas.drawLine(x, y - r, x, (float) (y - r + x * 0.4), mChangePaint);
canvas.rotate(360 / mLinesNumber, x, y);
}
} else {
for (int i = 0; i < mLinesNumber; i++) {
//繪制下層菊花
canvas.drawLine(x, y - r, x, (float) (y - r + x * 0.4), mChangePaint);
canvas.rotate(360 / mLinesNumber, x, y);
}
//應(yīng)該畫多少
int currentContent = (int) (mLinesNumber * mCurrentProgress);
for (int i = currentContent; i > 0; i--) {
//繪制下層菊花
canvas.drawLine(x, y - r, x, (float) (y - r + x * 0.4), mPaint);
canvas.rotate(360 / mLinesNumber, x, y);
}
}
canvas.restore();
}
public void start() {
if(valueAnimator==null){
valueAnimator = ValueAnimator.ofFloat(0, 1.0f);
valueAnimator.setDuration(1000);
valueAnimator.setRepeatCount(-1);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationRepeat(Animator animation) {
mRunContent += 1;
}
});
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float value = (float) valueAnimator.getAnimatedValue();
setCurrentProgress(value);
}
});
valueAnimator.start();
}
}
public void cancel(){
if(valueAnimator!=null){
valueAnimator.cancel();
valueAnimator.end();
valueAnimator.addUpdateListener(null);
valueAnimator.addListener(null);
}
}
}