自定义的圆环计时器提供了以下属性
<declare-styleable name="TimerCircle">
<!--圆环的宽度-->
<attr name="width" format="dimension"></attr>
<!--内圆的颜色-->
<attr name="circleColor" format="color"></attr>
<!--圆环的颜色-->
<attr name="ringColor" format="color"></attr>
<!--倒计时时间-->
<attr name="maxTime" format="integer"></attr>
<!--圆环的路径颜色-->
<attr name="path" format="color"></attr>
<!--倒计时字体颜色-->
<attr name="textColor" format="color"></attr>
<!--倒计时字体大小-->
<attr name="textSize" format="dimension"></attr>
</declare-styleable>
代码与注释
package com.example.view;
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.util.AttributeSet;
import android.view.View;
import android.view.animation.LinearInterpolator;
import androidx.annotation.Nullable;
import com.example.binder.R;
public class TimerCircle extends View {
//圆环画笔
private Paint mPaint;
//圆画笔
private Paint nPaint;
//1.圆的颜色
private int circle_color;
//2.圆环颜色
private int ring_color;
//3.圆环大小对应着画笔的宽度
private int ring_width;
//4.指定控件宽度和长度
private int width;
private int height;
//50.字体颜色
private int text_color;
//51.字体大小
private int text_size;
//52.路径颜色
private int path_color;
//5.通过宽度和高度计算得出圆的半径
private int radius;
//6.指定动画的当前值,比如指定动画从0-10000。
private int current_value;
//7.进行到x秒时,对应的圆弧弧度为 x/ * 360.f x可以currentValue得出 currentValue/1000代表秒
private float angle_value;//圆弧角度
//8.通过valueAnimator我们可以获得currentValue
private ValueAnimator animator;
//9.表示valueAnimator的持续时间,这里设置为和最大倒计时时间相同
private float duration;
//10.最大倒计时时间,如果1分钟计时,这里就有600000ms,单位是ms
private int maxTime=10000;
//这里设置为10是为了避免 angleValue = currentValue/maxTime*360.0f除数为0的异常,如果既没有在xml中设置最大值就会报错,这是由绘制流程决定的。
//11.当前的时间是指倒计时剩余时间,需要显示在圆中
private int currentTime;
private onFinishListener finishListenter;
public TimerCircle(Context context) {
this(context,null);//12.TimerCircle circle = new TimerCircle()时调用该构造方法
}
public TimerCircle(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);//13.通过xml使用自定义View时使用该构造方法
}
public TimerCircle(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);//14.一般不会主动调用该方法,除非手动指定调用。
//15.获取属性
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TimerCircle);
circle_color = typedArray.getColor(R.styleable.TimerCircle_circleColor, Color.BLUE);
ring_color = typedArray.getInteger(R.styleable.TimerCircle_ringColor, 0);
ring_width = (int) typedArray.getDimension(R.styleable.TimerCircle_width, getResources().getDimension(R.dimen.width));
text_color = typedArray.getColor(R.styleable.TimerCircle_path, Color.RED);
text_size = (int) typedArray.getDimension(R.styleable.TimerCircle_textSize,getResources().getDimension(R.dimen.textSize));
path_color = typedArray.getColor(R.styleable.TimerCircle_path, Color.RED);
typedArray.recycle();
InitPaint();
}
private void InitPaint() {
mPaint = new Paint();
mPaint.setColor(ring_color);
mPaint.setAntiAlias(true);
nPaint = new Paint();
nPaint.setAntiAlias(true);
nPaint.setColor(circle_color);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//16.如果padding和wrap-content失效,还要在这里处理
int widthPixels = this.getResources().getDisplayMetrics().widthPixels;//17. 获取屏幕宽度
int heightPixels = this.getResources().getDisplayMetrics().heightPixels;//18.获取屏幕高度
//19.测量,目的是为了根据指定的宽高和屏幕的宽高最终确定圆的半径,四个中最小的即为半径
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int minWidth = Math.min(width, widthPixels);
int minHeight = Math.min(height, heightPixels);
setMeasuredDimension(Math.min(minHeight, minWidth), Math.min(minHeight, minWidth));
}
@Override
protected void onDraw(Canvas canvas) {
//20.确定内圆的半径
int in_radius = (this.getWidth()-2*ring_width)/2;
//21.确定圆的圆心
int center = this.getWidth()-in_radius-ring_width;
//22.绘制内圆和圆环
drawInner(canvas, in_radius,center);
//23.绘制倒计时的字体
drawText(canvas,center);
}
private void drawInner(Canvas canvas, int radius, int center) {
//24.先画出内圆
canvas.drawCircle(center, center, radius, nPaint);
//25.画圆环,设置为空心圆,指定半径为内圆的半径,画笔的宽度就是圆环的宽度
mPaint.setStyle(Paint.Style.STROKE);//26.设置空心圆
mPaint.setStrokeWidth(ring_width);//27.画笔宽度即圆环宽度
mPaint.setColor(ring_color);//28. 圆环的颜色
canvas.drawCircle(center, center, radius, mPaint);
//30.内圆的外接矩形,有什么作用?绘制圆弧时根据外接矩形绘制
RectF rectF = new RectF(center-radius,center-radius,center+radius,center+radius);
//31.计算弧度,通过当前的currentValue的值得到
angle_value = current_value * 360.0f / maxTime * 1.0f;
//32.设置阴影大小和颜色
mPaint.setShadowLayer(10, 10, 10, Color.BLUE);
//33.指定线帽样式,可以理解为一条有宽度的直线的两端是带有弧度的
mPaint.setStrokeCap(Paint.Cap.ROUND);
//34.圆弧的颜色
mPaint.setColor(path_color);
//35.绘制圆弧
canvas.drawArc(rectF, -90,angle_value , false, mPaint);
}
public void setDuration(int duration, final int maxTime) {
//36.如果外部指定了最大倒计时的时间,则xml定义的最大倒计时无效,以外部设置的为准
this.maxTime=maxTime;
//37.持续时间和最大时间保持一致,方便计算
this.duration=duration;
if (animator != null) {
animator.cancel();
} else {
animator = ValueAnimator.ofInt(0, maxTime);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//38.获取此时的进度
current_value = (int) animation.getAnimatedValue();
if(current_value==maxTime){
finishListenter.onFinish();
}
//39.invalidate()方法系统会自动调用 View的onDraw()方法。
invalidate();
}
});
//40.线性插值器,匀速变化
animator.setInterpolator(new LinearInterpolator());
}
animator.setDuration(duration);
animator.start();
}
private void drawText(Canvas canvas,int center) {
//41.计算当前的剩余的时间,单位s
currentTime=(maxTime-current_value)/1000;
//42.显示的倒计时字符串
String Text=null;
if(currentTime<10){
Text="00:0"+currentTime;
}else if(currentTime>=10&¤tTime<=60){
Text="00:"+currentTime;
}else if(currentTime>60&¤tTime<600){
int min = currentTime/60;
int sen = currentTime%60;
if (sen<10) {
Text="0"+min+":0"+sen;
}else{
Text="0"+min+":"+sen;
}
}else{
int min = currentTime/60;
int sen = currentTime%60;
if (sen<10) {
Text=min+":0"+sen;
}else{
Text=min+":"+sen;
}
}
// 43.设置文字居中,以左下角为基准的(x,y)就是这里的center baseline。具体的关于drawText需要查看https://blog.csdn.net/harvic880925/article/details/50423762/
mPaint.setTextAlign(Paint.Align.CENTER);
// 44.设置文字颜色
mPaint.setColor(text_color);
mPaint.setTextSize(text_size);
mPaint.setStrokeWidth(0);//清除画笔宽度
mPaint.clearShadowLayer();//清除阴影
// 45.文字边框
Rect bounds = new Rect();
// 46.获得绘制文字的边界矩形
mPaint.getTextBounds(Text, 0, Text.length(), bounds);
// 47.获取绘制Text时的四条线
Paint.FontMetricsInt fontMetrics = mPaint.getFontMetricsInt();
// 48.计算文字的基线
int baseline = center - (fontMetrics.bottom+fontMetrics.top)/2;
// 49.绘制表示进度的文字
canvas.drawText(Text, center, baseline, mPaint);
}
public interface onFinishListener{
void onFinish();
}
public void setFinishListenter(onFinishListener listenter){
this.finishListenter = listenter;
}
}
使用
<com.example.view.TimerCircle
android:id="@+id/timer"
android:layout_marginLeft="60dp"
android:layout_marginTop="66dp"
android:layout_width="200dp"
android:layout_height="200dp"
app:width="20dp"
app:circleColor="#CCCCCC"
app:path="#FF6666"
app:maxTime="10000"
app:ringColor="#BBBBBB"
app:textColor="#0099CC"
app:textSize="28sp">
具体不多说了,注释非常清楚了,请看效果图
扩展——仪表盘
style.xml
<declare-styleable name="PanelView">
<!--圆环颜色-->
<attr name="ring_color" format="color"></attr>
<!--内圆环颜色-->
<attr name="back_color" format="color"></attr>
<!--圆环宽度-->
<attr name="ring_width" format="dimension"></attr>
<!--圆环开始的角度,需要做异常检测-270-90-->
<attr name="start_angle" format="integer"></attr>
<!--圆环结束的角度-->
<attr name="sweep_angle" format="integer"></attr>
<!--字体颜色-->
<attr name="text_color" format="color"></attr>
<!--字体大小-->
<attr name="text_size" format="dimension"></attr>
<!--路径颜色-->
<attr name="path_color" format="color"></attr>
</declare-styleable>
代码和注释
package com.example.view;
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.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
import com.example.binder.R;
import java.text.DecimalFormat;
//仪表盘自定义view
public class PanelView extends View {
private Paint ringPaint;//圆环的画笔
private Paint textPaint;//字体画笔
private int width;//空间的宽度
private int height;//空间的高度
private int ring_color;//圆环颜色
private int back_color;//背景颜色
private int ring_width;//圆环宽度
private int start_angle;//起始角度
private int sweep_angle;//结束角度
private int current_angle;//当前的角度
private int text_color;//字体颜色
private int text_size;//字体大小
private int path_color;//路径颜色
private int precent;//进度
private ValueAnimator animator;
public PanelView(Context context) throws Exception {
this(context,null);
}
public PanelView(Context context, @Nullable AttributeSet attrs) throws Exception {
this(context,attrs,0);
}
public PanelView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) throws Exception {
super(context, attrs, defStyleAttr);
TypedArray ta = context.obtainStyledAttributes(attrs,R.styleable.PanelView);
ring_color = ta.getColor(R.styleable.PanelView_ring_color, Color.GREEN);
back_color = ta.getColor(R.styleable.PanelView_back_color, Color.WHITE);
ring_width = (int) ta.getDimension(R.styleable.PanelView_ring_width, getResources().getDimension(R.dimen.width));
start_angle = ta.getInteger(R.styleable.PanelView_start_angle, -225);
sweep_angle = ta.getInteger(R.styleable.PanelView_sweep_angle, 270);
text_color = ta.getColor(R.styleable.PanelView_text_color, Color.RED);
text_size = (int) ta.getDimension(R.styleable.PanelView_text_size, getResources().getDimension(R.dimen.textSize));
path_color = ta.getColor(R.styleable.PanelView_path_color, Color.BLACK);
ta.recycle();
if(start_angle<-360||sweep_angle>360)
throw new Exception("angel not allow");
InitPaint();
}
private void InitPaint() {
ringPaint = new Paint();
textPaint = new Paint();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width_pixels = getResources().getDisplayMetrics().widthPixels;
int height_pixels = getResources().getDisplayMetrics().heightPixels;
width = MeasureSpec.getSize(widthMeasureSpec);
height = MeasureSpec.getSize(heightMeasureSpec);
//获取四个的最小值
int layout_width = Math.min(width_pixels, width);
int layout_height = Math.min(height_pixels, height);
int final_width = Math.min(layout_height, layout_width);
setMeasuredDimension(final_width,final_width);
}
@Override
protected void onDraw(Canvas canvas) {
//先话内部圆环
int center = getWidth()/2;
int radius = center - ring_width;
drawRing(canvas,radius,center);
drawText(canvas,radius,center);
}
private void drawRing(Canvas canvas, int radius, int center) {
//画圆环
RectF oval = new RectF(center-radius, center-radius, center+radius, center+radius);
ringPaint.setStrokeWidth(0);
ringPaint.setStyle(Paint.Style.FILL);
ringPaint.setColor(back_color);
ringPaint.setStrokeCap(Paint.Cap.ROUND);
canvas.drawArc(oval, start_angle*1.0f, sweep_angle*1.0f, false,ringPaint );
ringPaint.setStyle(Paint.Style.STROKE);
ringPaint.setStrokeWidth(ring_width);
ringPaint.setStrokeCap(Paint.Cap.ROUND);
ringPaint.setColor(ring_color);
canvas.drawArc(oval, start_angle*1.0f, sweep_angle*1.0f, false, ringPaint);
ringPaint.setStyle(Paint.Style.STROKE);
ringPaint.setStrokeWidth(ring_width);
ringPaint.setStrokeCap(Paint.Cap.ROUND);
ringPaint.setColor(path_color);
canvas.drawArc(oval, start_angle*1.0f, current_angle*1.0f, false, ringPaint);
}
private void drawText(Canvas canvas, int radius, int center) {
//字体
String text = precent+"%";
Rect rect = new Rect();
textPaint.getTextBounds(text, 0, text.length(), rect);
Paint.FontMetricsInt fontMetricsInt = textPaint.getFontMetricsInt();
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setColor(text_color);
textPaint.setTextSize(text_size);
int baseline = center-(fontMetricsInt.bottom+fontMetricsInt.top)/2;
canvas.drawText(text,center ,baseline,textPaint);
}
public void setDuration(final int duration){
if(animator==null){
animator = ValueAnimator.ofInt(0,duration);
}
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (int) animation.getAnimatedValue();
precent = curValue*100/duration;
current_angle=precent*sweep_angle/100;
invalidate();
}
});
animator.setDuration(duration);
animator.start();
}
}
效果图
扩展——线性进度条
结束
有兴趣来这个群里交流呀