接着上篇Android进阶之自定义控件(2)高仿钉钉运动步数实现可动的进度圆环(上)的基础,我们来实现钉钉运动的效果:
《一》View效果分析:
对钉钉运动的效果进行分析:
1、圆弧应该是从135°起,绘制了270°。
2、步数小于10000步时,背景圆弧为灰色,进度圆弧为蓝色渐变色;步数大于10000步时,进度圆弧为渐变的黄色
3、需要绘制中间的步数及步数上面的名次
4、实现步数及进度的动态变化
![img_445f948ff9eeda54b7ef69ce88949e08.gif](https://yqfile.alicdn.com/img_445f948ff9eeda54b7ef69ce88949e08.gif?x-oss-process=image/resize,w_1400/format,webp)
dingding.gif
有了上篇的基础,实现钉钉运动步数的效果,就很简单了。还是先看一下我最终实现的高仿版效果,给大家点信心。变化的速度可以配置:
![img_5bc54771437b86bd35ffcba0dd7e868d.gif](https://yqfile.alicdn.com/img_5bc54771437b86bd35ffcba0dd7e868d.gif?x-oss-process=image/resize,w_1400/format,webp)
高仿钉钉运动效果.gif
《二》实现步骤分解:
(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即可。上篇这些基础的知识及需要注意的小细节都有详细的讲解,不多做解释。到此步骤,实现的效果如下:
//绘制中间的步数的文字
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);
//绘制排名的文字,在步数文字的上方
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);
![img_9a6b19fb03602eb457fb23b653dc7c06.png](https://yqfile.alicdn.com/img_9a6b19fb03602eb457fb23b653dc7c06.png?x-oss-process=image/resize,w_1400/format,webp)
image.png
(4)可以看到,上图还是静态的效果,接下来我们通过两种方式来让进度圆弧和文字动起来。
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()必须在主线程中执行,此处不能使用
// postInvalidate();//强制重绘,postInvalidate()可以在主线程也可以在分线程中执行
changeProgress = changeProgress - rate;
}
//由于上面的循环结束时,可能计算后最终无法到达mCurrentProgress的值,所以在循环结束后,将mCurrentProgress重新设置
setCurrentStep(currentStep);
}
}).start();
2、方法二:使用属性动画和差值器实现
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来处理,如下,给画笔设置Shader,简单的设置如下。
![img_2a1bc7e1de4e71eb96ff1e6edbe1ab3f.png](https://yqfile.alicdn.com/img_2a1bc7e1de4e71eb96ff1e6edbe1ab3f.png?x-oss-process=image/resize,w_1400/format,webp)
image.png
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);
![img_7aa4e07190511568a0ba5e6beab0f2b9.png](https://yqfile.alicdn.com/img_7aa4e07190511568a0ba5e6beab0f2b9.png?x-oss-process=image/resize,w_1400/format,webp)
image.png
还是和钉钉的效果有点区别,钉钉的圆弧两边是浅色的,上面顶部的部分是深色的,我们稍作处理一下,代码如下:
![img_d577e93e2020fcf75c7535f9acf16754.png](https://yqfile.alicdn.com/img_d577e93e2020fcf75c7535f9acf16754.png?x-oss-process=image/resize,w_1400/format,webp)
image.png
/**
* 处理渐变色
*/
//渐变颜色数组
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);
渐变的效果搞定。
![img_fba8ea14b6235ebb3a568b3ed5c5bf17.png](https://yqfile.alicdn.com/img_fba8ea14b6235ebb3a568b3ed5c5bf17.png?x-oss-process=image/resize,w_1400/format,webp)
image.png
测试代码,在使用的地方调用startCountStep()即可,是不是敲极简单:
//当前实际的步数
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: 仿钉钉运动步数效果
*/
public class SportStepView extends View {
private Context mContext;
//内圆环颜色
private int innerRoundColor;
//外圆环颜色
private int outerRoundColor;
//绘制背景圆环的画笔
private Paint mPaint;
//绘制外面进度的圆环的画笔
private Paint mProgressPaint;
//绘制外面进度的圆环的画笔
private Paint mTextPaint;
//背景圆弧的绘制的宽度
private int mRoundWidth = 10;
//进度圆环的宽度
private float mProgressRoundWidth = 15;
//中间步数文字的大小
private int mTextSize = 40;//单位 sp
//名次文字大小
private int mRandTextSize = 15;
//圆环最大进度
private int mMaxStep = 10000;
//圆环当前进度
private float mCurrentStep = 0;
//排名与步数文字直接的间隔
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;
}
//以宽度为标准保存丈量结果
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);
/**
* 处理渐变色
*/
//默认的渐变颜色数组:
// 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);
//绘制背景圆环
canvas.drawArc(oval, 135, 270, false, mPaint);
//绘制进度圆环,绘制的角度最大不超过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);
//绘制背景圆环
canvas.drawArc(oval, 135, 270, false, mPaint);
//绘制进度圆环
if (mCurrentStep * 1f / mMaxStep <= 1) {
canvas.drawArc(oval, 0 + 135, mCurrentStep / mMaxStep * 270, false, mProgressPaint);
} else {
canvas.drawArc(oval, 0 + 135, 270, false, mProgressPaint);
}
}
//绘制中间的步数的文字
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);
//绘制排名的文字,在步数文字的上方
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()可以在主线程也可以在分线程中执行
postInvalidate();
}
public void setMaxProgress(int maxStep) {
this.mMaxStep = maxStep;
}
public int getMaxtep() {
return mMaxStep;
}
public float getCurrentStep() {
return mCurrentStep;
}
/**
* 将sp转换成px
*
* @param sp
* @return
*/
private int sp2px(int sp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp,
getResources().getDisplayMetrics());
}
/**
* 开始动态计步
*
* @param currentStep
*/
public void startCountStep(final float currentStep) {
//方法一:开一个分线程,动态改变进度的值,不断绘制达到进度变化的效果
// 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()必须在主线程中执行,此处不能使用
//// postInvalidate();//强制重绘,postInvalidate()可以在主线程也可以在分线程中执行
// changeProgress = changeProgress - rate;
// }
// //由于上面的循环结束时,可能计算后最终无法到达mCurrentProgress的值,所以在循环结束后,将mCurrentProgress重新设置
// 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">
<!--圆环半径-->
<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>