Android自定義控件實現(xiàn)圓形進度CircleProgressBar
近日有朋友問我有沒有如下圖效果的開源控件

相信大家無論是用IOS還是Android,都對這種效果不陌生,很多主流APP都會有這樣或類似的效果,之前也打算研究一下這類控件的代碼,苦于一直不知道應(yīng)該怎么搜索這種效果(就是關(guān)鍵詞)或者所搜的結(jié)果不是自己想要的,所以就一直擱置了下來。
正好朋友需要這種效果,所以就忙里偷閑寫了一個類似的、更加常見和適用范圍更多的控件,效果如下圖所示:

自定義上圖所示效果的控件時,其實就是用Canvas繪制不同效果,比如漸變圓弧背景、圓周白色分割線、中間文字等,這篇博客也根據(jù)繪制的順序依次闡述。
1.自定義CircleProgressBar,繼承View,并實現(xiàn)響應(yīng)的構(gòu)造函數(shù)
代碼如下:
/**
* Created by WangChunLei on 2016.1.16
* E-mail:wcl_android@163.com
*/
public class GradientProgressBar extends View {
public GradientProgressBar(Context context) {
super(context);
init();
}
public GradientProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public GradientProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
}
其中init方法是對相關(guān)畫筆進行初始化的方法,init方法代碼如下:
private void init() {
backCirclePaint = new Paint();
backCirclePaint.setStyle(Paint.Style.STROKE);
backCirclePaint.setAntiAlias(true);
backCirclePaint.setColor(Color.LTGRAY);
backCirclePaint.setStrokeWidth(circleBorderWidth);
// backCirclePaint.setMaskFilter(new BlurMaskFilter(20, BlurMaskFilter.Blur.OUTER));
gradientCirclePaint = new Paint();
gradientCirclePaint.setStyle(Paint.Style.STROKE);
gradientCirclePaint.setAntiAlias(true);
gradientCirclePaint.setColor(Color.LTGRAY);
gradientCirclePaint.setStrokeWidth(circleBorderWidth);
linePaint = new Paint();
linePaint.setColor(Color.WHITE);
linePaint.setStrokeWidth(5);
textPaint = new Paint();
textPaint.setAntiAlias(true);
textPaint.setTextSize(textSize);
textPaint.setColor(Color.BLACK);
}
2.測量控件的寬高-onMeasure
onMeasure是自定義控件的第一步,目的就是測量得到該控件應(yīng)該占有的寬高尺寸。其中onMeasure方法的代碼如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(Math.min(measureWidth, measureHeight), Math.min(measureWidth, measureHeight));
}
貼上onMeasure的代碼后,大家估計是很少見過測量過程這么簡單的onMeasure,不要介意,有興趣的同僚們可以細化一下這個測量過程,對不同的測量模式分別進行處理和測量,讓控件適配效果更好更完善!
onMeasure方法中,分別獲取期望的寬度和高度,并取其中較小的尺寸作為該控件的寬和高。
3.依次繪制不同的控件組成部分。
因為控件是直接繼承自View,所以不需要再處理onLayout方法,這也是自定義View的難度遠小于自定義ViewGroup的原因,但繼承ViewGroup也并不一定要重寫onMeasure。
要實現(xiàn)如圖所示的效果,需要分以下步驟依次實現(xiàn)
(1)繪制灰色空心圓環(huán)
(2)繪制顏色漸變的圓環(huán)
(3)繪制圓環(huán)上分割的白色線條
(4)繪制百分比文字等。
繪制過程過,后繪制的內(nèi)容如果與之前繪制的內(nèi)容存在交集,則后繪制的內(nèi)容會覆蓋掉之前繪制的內(nèi)容。
按照上述步驟依次介紹
在繪制過程中,會產(chǎn)生以下成員變量,下文中會用到:
/*圓弧線寬*/
private float circleBorderWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, getResources().getDisplayMetrics());
/*內(nèi)邊距*/
private float circlePadding = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, getResources().getDisplayMetrics());
/*字體大小*/
private float textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 50, getResources().getDisplayMetrics());
/*繪制圓周的畫筆*/
private Paint backCirclePaint;
/*繪制圓周白色分割線的畫筆*/
private Paint linePaint;
/*繪制文字的畫筆*/
private Paint textPaint;
/*百分比*/
private int percent = 0;
/*漸變圓周顏色數(shù)組*/
private int[] gradientColorArray = new int[]{Color.GREEN, Color.parseColor("#fe751a"), Color.parseColor("#13be23"), Color.GREEN};
private Paint gradientCirclePaint;
3.1繪制灰色空心圓環(huán)
代碼如下:
//1.繪制灰色背景圓環(huán) canvas.drawArc( new RectF(circlePadding * 2, circlePadding * 2, getMeasuredWidth() - circlePadding * 2, getMeasuredHeight() - circlePadding * 2), -90, 360, false, backCirclePaint);
其中,-90為繪制圓弧的起始角度,360是圓弧繪制的角度,即sweepAngle.
3.2繪制顏色漸變的圓環(huán)
//2.繪制顏色漸變圓環(huán) LinearGradient linearGradient = new LinearGradient(circlePadding, circlePadding, getMeasuredWidth() - circlePadding, getMeasuredHeight() - circlePadding, gradientColorArray, null, Shader.TileMode.MIRROR); gradientCirclePaint.setShader(linearGradient); gradientCirclePaint.setShadowLayer(10, 10, 10, Color.RED); canvas.drawArc( new RectF(circlePadding * 2, circlePadding * 2, getMeasuredWidth() - circlePadding * 2, getMeasuredHeight() - circlePadding * 2), -90, (float) (percent / 100.0) * 360, false, gradientCirclePaint);
其中,linearGradient是Paint的shadow,是為了圓弧的顏色漸變效果的而需要設(shè)置的,日常開發(fā)中應(yīng)用頻率不高,但的確是可以實現(xiàn)非常理想的顏色漸變效果。
3.3繪制圓環(huán)上分割的白色線條
繪制圓弧上的白色線條時,需要進行一些簡單的運算,比如線條的起始坐標(biāo)startX,startY和線條的終止坐標(biāo)stopX,stopY等,利用簡單的三角函數(shù)還是很容易去計算出來的。
效果中,將圓弧使用白色線條平分成100分,每一個的階級為1,可以滿足int類型的百分比與效果圖比例的一致。
//半徑
float radius = (getMeasuredWidth() - circlePadding * 3) / 2;
//X軸中點坐標(biāo)
int centerX = getMeasuredWidth() / 2;
//3.繪制100份線段,切分空心圓弧
for (float i = 0; i < 360; i += 3.6) {
double rad = i * Math.PI / 180;
float startX = (float) (centerX + (radius - circleBorderWidth) * Math.sin(rad));
float startY = (float) (centerX + (radius - circleBorderWidth) * Math.cos(rad));
float stopX = (float) (centerX + radius * Math.sin(rad) + 1);
float stopY = (float) (centerX + radius * Math.cos(rad) + 1);
canvas.drawLine(startX, startY, stopX, stopY, linePaint);
}
3.4繪制百分比文字等
最后繪制百分比文字。
繪制文字時,為了保持文字的中心點和圓弧的原點一致,需要先測量得到要顯示文字的寬度和高度,然后再進行一些簡單的運算,原理不再贅述,相信大家數(shù)學(xué)一定都比我好。
//4.繪制文字 float textWidth = textPaint.measureText(percent + "%"); int textHeight = (int) (Math.ceil(textPaint.getFontMetrics().descent - textPaint.getFontMetrics().ascent) + 2); canvas.drawText(percent + "%", centerX - textWidth / 2, centerX + textHeight / 4, textPaint);
最后,暴漏一個公共的方法供改變顯示的百分比,代碼如下:
/**
* 設(shè)置百分比
*
* @param percent
*/
public void setPercent(int percent) {
if (percent < 0) {
percent = 0;
} else if (percent > 100) {
percent = 100;
}
this.percent = percent;
invalidate();
}
至此,所有繪制過程簡述完畢,130行代碼就能實現(xiàn)很炫酷的效果有木有?
最后,貼上項目完整代碼,供懶得看實現(xiàn)過程的同僚們使用,O(∩_∩)O哈哈~
package com.example.myview;
import android.content.Context;
import android.graphics.*;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
/**
* Created by WangChunLei on 2016.1.16
* e-mail:wcl_android@163.com
*/
public class GradientProgressBar extends View {
/*圓弧線寬*/
private float circleBorderWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, getResources().getDisplayMetrics());
/*內(nèi)邊距*/
private float circlePadding = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, getResources().getDisplayMetrics());
/*字體大小*/
private float textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 50, getResources().getDisplayMetrics());
/*繪制圓周的畫筆*/
private Paint backCirclePaint;
/*繪制圓周白色分割線的畫筆*/
private Paint linePaint;
/*繪制文字的畫筆*/
private Paint textPaint;
/*百分比*/
private int percent = 0;
/*漸變圓周顏色數(shù)組*/
private int[] gradientColorArray = new int[]{Color.GREEN, Color.parseColor("#fe751a"), Color.parseColor("#13be23"), Color.GREEN};
private Paint gradientCirclePaint;
public GradientProgressBar(Context context) {
super(context);
init();
}
public GradientProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public GradientProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
backCirclePaint = new Paint();
backCirclePaint.setStyle(Paint.Style.STROKE);
backCirclePaint.setAntiAlias(true);
backCirclePaint.setColor(Color.LTGRAY);
backCirclePaint.setStrokeWidth(circleBorderWidth);
// backCirclePaint.setMaskFilter(new BlurMaskFilter(20, BlurMaskFilter.Blur.OUTER));
gradientCirclePaint = new Paint();
gradientCirclePaint.setStyle(Paint.Style.STROKE);
gradientCirclePaint.setAntiAlias(true);
gradientCirclePaint.setColor(Color.LTGRAY);
gradientCirclePaint.setStrokeWidth(circleBorderWidth);
linePaint = new Paint();
linePaint.setColor(Color.WHITE);
linePaint.setStrokeWidth(5);
textPaint = new Paint();
textPaint.setAntiAlias(true);
textPaint.setTextSize(textSize);
textPaint.setColor(Color.BLACK);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(Math.min(measureWidth, measureHeight), Math.min(measureWidth, measureHeight));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//1.繪制灰色背景圓環(huán)
canvas.drawArc(
new RectF(circlePadding * 2, circlePadding * 2,
getMeasuredWidth() - circlePadding * 2, getMeasuredHeight() - circlePadding * 2), -90, 360, false, backCirclePaint);
//2.繪制顏色漸變圓環(huán)
LinearGradient linearGradient = new LinearGradient(circlePadding, circlePadding,
getMeasuredWidth() - circlePadding,
getMeasuredHeight() - circlePadding,
gradientColorArray, null, Shader.TileMode.MIRROR);
gradientCirclePaint.setShader(linearGradient);
gradientCirclePaint.setShadowLayer(10, 10, 10, Color.RED);
canvas.drawArc(
new RectF(circlePadding * 2, circlePadding * 2,
getMeasuredWidth() - circlePadding * 2, getMeasuredHeight() - circlePadding * 2), -90, (float) (percent / 100.0) * 360, false, gradientCirclePaint);
//半徑
float radius = (getMeasuredWidth() - circlePadding * 3) / 2;
//X軸中點坐標(biāo)
int centerX = getMeasuredWidth() / 2;
//3.繪制100份線段,切分空心圓弧
for (float i = 0; i < 360; i += 3.6) {
double rad = i * Math.PI / 180;
float startX = (float) (centerX + (radius - circleBorderWidth) * Math.sin(rad));
float startY = (float) (centerX + (radius - circleBorderWidth) * Math.cos(rad));
float stopX = (float) (centerX + radius * Math.sin(rad) + 1);
float stopY = (float) (centerX + radius * Math.cos(rad) + 1);
canvas.drawLine(startX, startY, stopX, stopY, linePaint);
}
//4.繪制文字
float textWidth = textPaint.measureText(percent + "%");
int textHeight = (int) (Math.ceil(textPaint.getFontMetrics().descent - textPaint.getFontMetrics().ascent) + 2);
canvas.drawText(percent + "%", centerX - textWidth / 2, centerX + textHeight / 4, textPaint);
}
/**
* 設(shè)置百分比
*
* @param percent
*/
public void setPercent(int percent) {
if (percent < 0) {
percent = 0;
} else if (percent > 100) {
percent = 100;
}
this.percent = percent;
invalidate();
}
}
最后,貼上自定義控件代碼(自定義控件、Activity,布局文件)下載地址: Android圓形進度CircleProgressBar
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
淺析Kotlin使用infix函數(shù)構(gòu)建可讀語法流程講解
這篇文章主要介紹了淺析Kotlin使用infix函數(shù)構(gòu)建可讀語法,我們在Kotlin中就多次使用A to B這樣的語法結(jié)構(gòu)構(gòu)建鍵值對,包括Kotlin自帶的mapOf()函數(shù),這種語法結(jié)構(gòu)的優(yōu)點是可讀性強2023-01-01
Android動畫 實現(xiàn)開關(guān)按鈕動畫(屬性動畫之平移動畫)實例代碼
這篇文章主要介紹了Android動畫 實現(xiàn)開關(guān)按鈕動畫(屬性動畫之平移動畫)實例代碼的相關(guān)資料,需要的朋友可以參考下2016-11-11
淺析Android Studio 3.0 升級各種坑(推薦)
本文是小編給大家收藏整理的關(guān)于Android Studio 3.0 升級后遇到的一些坑,非常不錯,具有參考借鑒價值,需要的朋友參考下吧2017-11-11

