接著上篇Android進階之自定義控件(2)高仿釘釘運動步數(shù)實現(xiàn)可動的進度圓環(huán)(上)的基礎(chǔ),我們來實現(xiàn)釘釘運動的效果:
《一》View效果分析:
對釘釘運動的效果進行分析:
1、圓弧應(yīng)該是從135°起,繪制了270°。
2、步數(shù)小于10000步時,背景圓弧為灰色,進度圓弧為藍色漸變色;步數(shù)大于10000步時,進度圓弧為漸變的黃色
3、需要繪制中間的步數(shù)及步數(shù)上面的名次
4、實現(xiàn)步數(shù)及進度的動態(tài)變化
有了上篇的基礎(chǔ),實現(xiàn)釘釘運動步數(shù)的效果,就很簡單了。還是先看一下我最終實現(xiàn)的高仿版效果,給大家點信心。變化的速度可以配置:
《二》實現(xiàn)步驟分解:
(1)繪制灰色背景圓弧,135°起,繪制了270°
canvas.drawArc(oval, 135, 270, false, mPaint);
(2)繪制進度圓弧
canvas.drawArc(oval, 0 + 135, mCurrentStep/ mMaxStep * 270, false, mProgressPaint);
(3)繪制文字很簡單,只需要以正中間的文字為標準,往上移動一個textRect.height()和文字之間的間距textPadding即可。上篇這些基礎(chǔ)的知識及需要注意的小細節(jié)都有詳細的講解,不多做解釋。到此步驟,實現(xiàn)的效果如下:
//繪制中間的步數(shù)的文字
Rect textRect = new Rect();
String mShowText = (int) mCurrentStep + "";
mTextPaint.setTextSize(sp2px(mTextSize));
mTextPaint.getTextBounds(mShowText, 0, mShowText.length(), textRect);
canvas.drawText(mShowText, getWidth() / 2 - textRect.width() / 2, getHeight() / 2 + textRect.height() / 2, mTextPaint);
//繪制排名的文字,在步數(shù)文字的上方
String mRandText = "第4名";//使用時,名次動態(tài)傳入即可,此處寫死為了測試
mTextPaint.setTextSize(sp2px(mRandTextSize));
mTextPaint.getTextBounds(mRandText, 0, mRandText.length(), textRect);
canvas.drawText(mRandText, getWidth() / 2 - textRect.width() / 2, getHeight() / 2 + textRect.height() / 2 - (textRect.height() + textPadding), mTextPaint);
(4)可以看到,上圖還是靜態(tài)的效果,接下來我們通過兩種方式來讓進度圓弧和文字動起來。
1、方法一:還是上篇講解過的方法,通過開分線程的方式,不斷更新進度值,不斷重繪。
new Thread(new Runnable() {
@Override
public void run() {
setCurrentStep(0);
float changeProgress = currentStep;
for (float i = 0; i < changeProgress; i++) {
setCurrentStep(getCurrentStep() + rate);
SystemClock.sleep(20);
// invalidate();//invalidate()必須在主線程中執(zhí)行,此處不能使用
// postInvalidate();//強制重繪,postInvalidate()可以在主線程也可以在分線程中執(zhí)行
changeProgress = changeProgress - rate;
}
//由于上面的循環(huán)結(jié)束時,可能計算后最終無法到達mCurrentProgress的值,所以在循環(huán)結(jié)束后,將mCurrentProgress重新設(shè)置
setCurrentStep(currentStep);
}
}).start();
2、方法二:使用屬性動畫和差值器實現(xiàn)
ValueAnimator valueAnimator = ObjectAnimator.ofFloat(0, currentStep);
valueAnimator.setDuration(1000);
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentStep = (float) animation.getAnimatedValue();
setCurrentStep((int) currentStep);
}
});
valueAnimator.start();
(5)最后一步了,來看看漸變色的進度圓弧如何處理。
處理漸變有兩個類:
1、線性漸變:LinearGradient
2、掃描式漸變:SweepGradient ——在中心點畫一個掃描梯度的著色器
很明顯,在這里我們需要用到SweepGradient來處理,如下,給畫筆設(shè)置Shader,簡單的設(shè)置如下。
int[] mGradientColorArray = new int[]{ContextCompat.getColor(mContext, R.color.color_5e9fff), ContextCompat.getColor(mContext, R.color.color_4fd8f5), ContextCompat.getColor(mContext, R.color.color_5e9fff)};
SweepGradient shader = new SweepGradient(getWidth() / 2 - mRoundWidth / 2, getWidth() / 2 - mRoundWidth / 2, mGradientColorArray, null);
mProgressPaint.setShader(shader);
還是和釘釘?shù)男Ч悬c區(qū)別,釘釘?shù)膱A弧兩邊是淺色的,上面頂部的部分是深色的,我們稍作處理一下,代碼如下:
/**
* 處理漸變色
*/
//漸變顏色數(shù)組
int[] mGradientColorArray = new int[]{ContextCompat.getColor(mContext, R.color.color_4fd8f5), ContextCompat.getColor(mContext, R.color.color_4fd8f5), ContextCompat.getColor(mContext, R.color.color_5e9fff), ContextCompat.getColor(mContext, R.color.color_4fd8f5)};
int count = mGradientColorArray.length;
int[] colors = new int[count];
System.arraycopy(mGradientColorArray, 0, colors, 0, count);
float[] positions = new float[count];
//由于此處繪制的是不完整的圓弧,故需要額外處理一下變化的比例值
float v = (360f / 270);
positions[0] = 0.0f;
positions[1] = 0.33f * v;
positions[2] = 0.67f * v;
positions[3] = 1.0f;
SweepGradient shader = new SweepGradient(getWidth() / 2 - mRoundWidth / 2, getWidth() / 2 - mRoundWidth / 2, mGradientColorArray, positions);
mProgressPaint.setShader(shader);
漸變的效果搞定。
測試代碼,在使用的地方調(diào)用startCountStep()即可,是不是敲極簡單:
//當前實際的步數(shù)
float mCurrentStep = 9709;
sportStepView.startCountStep(mCurrentStep);
View的完整代碼:
package com.example.jojo.learn.customview;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
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.graphics.RectF;
import android.graphics.SweepGradient;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import com.example.jojo.learn.R;
/**
* Created by JoJo on 2018/8/1.
* wechat:18510829974
* description: 仿釘釘運動步數(shù)效果
*/
public class SportStepView extends View {
private Context mContext;
//內(nèi)圓環(huán)顏色
private int innerRoundColor;
//外圓環(huán)顏色
private int outerRoundColor;
//繪制背景圓環(huán)的畫筆
private Paint mPaint;
//繪制外面進度的圓環(huán)的畫筆
private Paint mProgressPaint;
//繪制外面進度的圓環(huán)的畫筆
private Paint mTextPaint;
//背景圓弧的繪制的寬度
private int mRoundWidth = 10;
//進度圓環(huán)的寬度
private float mProgressRoundWidth = 15;
//中間步數(shù)文字的大小
private int mTextSize = 40;//單位 sp
//名次文字大小
private int mRandTextSize = 15;
//圓環(huán)最大進度
private int mMaxStep = 10000;
//圓環(huán)當前進度
private float mCurrentStep = 0;
//排名與步數(shù)文字直接的間隔
private float textPadding = 80;
//速度,值越大,變化速度越快
private float rate = 128;
public SportStepView(Context context) {
this(context, null);
}
public SportStepView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SportStepView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SportStepView);
innerRoundColor = typedArray.getColor(R.styleable.SportStepView_innerRoundColor, ContextCompat.getColor(mContext, R.color.color_e4e4e4));
outerRoundColor = typedArray.getColor(R.styleable.SportStepView_outerRoundColor, ContextCompat.getColor(mContext, R.color.color_4fd8f5));
init();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//獲取寬的模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//獲取寬的尺寸
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//對wrap_content這種模式進行處理
if (heightMode == MeasureSpec.AT_MOST) {
heightSize = widthSize;
}
//以寬度為標準保存丈量結(jié)果
setMeasuredDimension(widthSize, heightSize);
}
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(innerRoundColor);
mPaint.setStrokeCap(Paint.Cap.ROUND);// 圓形筆頭
mPaint.setStrokeWidth(mRoundWidth);
mProgressPaint = new Paint();
mProgressPaint.setAntiAlias(true);// 抗鋸齒效果
mProgressPaint.setStyle(Paint.Style.STROKE);
mProgressPaint.setColor(outerRoundColor);
mProgressPaint.setStrokeCap(Paint.Cap.ROUND);// 圓形筆頭
mProgressPaint.setStrokeWidth(mProgressRoundWidth);
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);// 抗鋸齒效果
mTextPaint.setStyle(Paint.Style.STROKE);
mTextPaint.setColor(Color.BLACK);
mTextPaint.setColor(ContextCompat.getColor(mContext, R.color.color_56a9ff));
mTextPaint.setTextSize(sp2px(mTextSize));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
/**
* 處理漸變色
*/
//默認的漸變顏色數(shù)組:
// int[] mGradientColorArray = new int[]{ContextCompat.getColor(mContext, R.color.color_5e9fff), ContextCompat.getColor(mContext, R.color.color_4fd8f5), ContextCompat.getColor(mContext, R.color.color_5e9fff)};
int[] mGradientColorArray = new int[]{ContextCompat.getColor(mContext, R.color.color_4fd8f5), ContextCompat.getColor(mContext, R.color.color_4fd8f5), ContextCompat.getColor(mContext, R.color.color_5e9fff), ContextCompat.getColor(mContext, R.color.color_4fd8f5)};
int count = mGradientColorArray.length;
int[] colors = new int[count];
System.arraycopy(mGradientColorArray, 0, colors, 0, count);
float[] positions = new float[count];
float v = (360f / 270);
positions[0] = 0.0f;
positions[1] = 0.33f * v;
positions[2] = 0.67f * v;
positions[3] = 1.0f;
SweepGradient shader = new SweepGradient(getWidth() / 2 - mRoundWidth / 2, getWidth() / 2 - mRoundWidth / 2, mGradientColorArray, positions);
mProgressPaint.setShader(shader);
if (mRoundWidth < mProgressRoundWidth) {
RectF oval = new RectF(0 + mProgressRoundWidth / 2, 0 + mProgressRoundWidth / 2, getWidth() - mProgressRoundWidth / 2, getWidth() - mProgressRoundWidth / 2);
//繪制背景圓環(huán)
canvas.drawArc(oval, 135, 270, false, mPaint);
//繪制進度圓環(huán),繪制的角度最大不超過270°
if (mCurrentStep * 1f / mMaxStep <= 1) {
canvas.drawArc(oval, 0 + 135, mCurrentStep / mMaxStep * 270, false, mProgressPaint);
} else {
canvas.drawArc(oval, 0 + 135, 270, false, mProgressPaint);
}
} else {
RectF oval = new RectF(0 + mRoundWidth / 2, 0 + mRoundWidth / 2, getWidth() - mRoundWidth / 2, getWidth() - mRoundWidth / 2);
//繪制背景圓環(huán)
canvas.drawArc(oval, 135, 270, false, mPaint);
//繪制進度圓環(huán)
if (mCurrentStep * 1f / mMaxStep <= 1) {
canvas.drawArc(oval, 0 + 135, mCurrentStep / mMaxStep * 270, false, mProgressPaint);
} else {
canvas.drawArc(oval, 0 + 135, 270, false, mProgressPaint);
}
}
//繪制中間的步數(shù)的文字
Rect textRect = new Rect();
String mShowText = (int) mCurrentStep + "";
mTextPaint.setTextSize(
sp2px(mTextSize));
mTextPaint.getTextBounds(mShowText, 0, mShowText.length(), textRect);
canvas.drawText(mShowText, getWidth() / 2 - textRect.width() / 2, getHeight() / 2 + textRect.height() / 2, mTextPaint);
//繪制排名的文字,在步數(shù)文字的上方
String mRandText = "第4名";
mTextPaint.setTextSize(
sp2px(mRandTextSize));
mTextPaint.getTextBounds(mRandText, 0, mRandText.length(), textRect);
canvas.drawText(mRandText, getWidth() / 2 - textRect.width() / 2, getHeight() / 2 + textRect.height() / 2 - (textRect.height() + textPadding), mTextPaint);
}
public void setCurrentStep(float currentStep) {
this.mCurrentStep = currentStep;
//強制重繪,postInvalidate()可以在主線程也可以在分線程中執(zhí)行
postInvalidate();
}
public void setMaxProgress(int maxStep) {
this.mMaxStep = maxStep;
}
public int getMaxtep() {
return mMaxStep;
}
public float getCurrentStep() {
return mCurrentStep;
}
/**
* 將sp轉(zhuǎn)換成px
*
* @param sp
* @return
*/
private int sp2px(int sp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp,
getResources().getDisplayMetrics());
}
/**
* 開始動態(tài)計步
*
* @param currentStep
*/
public void startCountStep(final float currentStep) {
//方法一:開一個分線程,動態(tài)改變進度的值,不斷繪制達到進度變化的效果
// new Thread(new Runnable() {
// @Override
// public void run() {
// setCurrentStep(0);
// float changeProgress = currentStep;
// for (float i = 0; i < changeProgress; i++) {
// setCurrentStep(getCurrentStep() + rate);
// SystemClock.sleep(20);
//// invalidate();//invalidate()必須在主線程中執(zhí)行,此處不能使用
//// postInvalidate();//強制重繪,postInvalidate()可以在主線程也可以在分線程中執(zhí)行
// changeProgress = changeProgress - rate;
// }
// //由于上面的循環(huán)結(jié)束時,可能計算后最終無法到達mCurrentProgress的值,所以在循環(huán)結(jié)束后,將mCurrentProgress重新設(shè)置
// setCurrentStep(currentStep);
// }
// }).start();
/**
* 方法二
*/
ValueAnimator valueAnimator = ObjectAnimator.ofFloat(0, currentStep);
valueAnimator.setDuration(1000);
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentStep = (float) animation.getAnimatedValue();
setCurrentStep((int) currentStep);
}
});
valueAnimator.start();
}
}
涉及到的自定義屬性及color.xml
<!--SportStepView-->
<declare-styleable name="SportStepView">
<!--圓環(huán)半徑-->
<attr name="radius" format="dimension"></attr>
<attr name="outerRoundColor" format="color"></attr>
<attr name="innerRoundColor" format="color"></attr>
</declare-styleable>
<color name="color_4fd8f5">#4fd8f5</color>
<color name="color_5e9fff">#5e9fff</color>
<color name="color_e4e4e4">#e4e4e4</color>
最后,附上我的一個Kotlin編寫+組件化開發(fā)的開源項目Designer
Kotlin+組件化開發(fā)實踐—開源項目Designer-App
Designer項目算是傾注了我蠻多心血了,每個頁面和功能都當成是上線的App來做,App的logo還特地做了UI設(shè)計??力求做到精致和完善,其中還包括了很多自己項目開發(fā)中的經(jīng)驗匯總和對新技術(shù)的探索和整合,希望對各位讀者有所幫助,歡迎點個star,follow,或者給個小心心,嘻嘻??也可以分享給你更多的朋友一起學(xué)習(xí),您的支持是我不斷前進的動力。如果有任何問題,歡迎在GitHub上給我提issue或者留言。