自定義的圓環計時器提供了以下屬性
<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();
}
}
效果圖
擴展——線性進度條
結束
有興趣來這個群里交流呀