Android自定義view之仿支付寶芝麻信用儀表盤示例
自定義view練習(xí) 仿支付寶芝麻信用的儀表盤
對比圖:


首先是自定義一些屬性,可自己再添加,挺基礎(chǔ)的,上代碼
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="RoundIndicatorView">
<!--最大數(shù)值-->
<attr name="maxNum" format="integer"/>
<!--圓盤起始角度-->
<attr name="startAngle" format="integer"/>
<!--圓盤掃過的角度-->
<attr name="sweepAngle" format="integer"/>
</declare-styleable>
</resources>
接著在構(gòu)造方法里初始化自定義屬性和畫筆:
private void initAttr(AttributeSet attrs) {
TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.RoundIndicatorView);
maxNum = array.getInt(R.styleable.RoundIndicatorView_maxNum,500);
startAngle = array.getInt(R.styleable.RoundIndicatorView_startAngle,160);
sweepAngle = array.getInt(R.styleable.RoundIndicatorView_sweepAngle,220);
//內(nèi)外圓弧的寬度
sweepInWidth = dp2px(8);
sweepOutWidth = dp2px(3);
array.recycle();
}
private void initPaint() {
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setDither(true);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(0xffffffff);
paint_2 = new Paint(Paint.ANTI_ALIAS_FLAG);
paint_3 = new Paint(Paint.ANTI_ALIAS_FLAG);
paint_4 = new Paint(Paint.ANTI_ALIAS_FLAG);
}
接下來重寫onMeasure,也是比較簡單,對于不是確定值的直接給定300*400的大?。?br />
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int wSize = MeasureSpec.getSize(widthMeasureSpec);
int wMode = MeasureSpec.getMode(widthMeasureSpec);
int hSize = MeasureSpec.getSize(heightMeasureSpec);
int hMode = MeasureSpec.getMode(heightMeasureSpec);
if (wMode == MeasureSpec.EXACTLY ){
mWidth = wSize;
}else {
mWidth =dp2px(300);
}
if (hMode == MeasureSpec.EXACTLY ){
mHeight= hSize;
}else {
mHeight =dp2px(400);
}
setMeasuredDimension(mWidth,mHeight);
}
核心部分onDraw來了,注意圓的半徑不要在構(gòu)造方法里就去設(shè)置,那時候是得不到view的寬高值的,所以我在onDraw方法里設(shè)置半徑,默認(rèn)就view寬度的1/4吧。把原點(diǎn)移到view的中心去:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
radius = getMeasuredWidth()/4; //不要在構(gòu)造方法里初始化,那時還沒測量寬高
canvas.save();
canvas.translate(mWidth/2,(mWidth)/2);
drawRound(canvas); //畫內(nèi)外圓弧
drawScale(canvas);//畫刻度
drawIndicator(canvas); //畫當(dāng)前進(jìn)度值
drawCenterText(canvas);//畫中間的文字
canvas.restore();
}
步驟清晰,按順序畫出儀表盤的四個部分,我們一個一個部分的看
drawRound():這個很簡單,內(nèi)外圓弧所需的屬性都已經(jīng)定義好了,畫筆是白色的,我們通過setAlpha()設(shè)置一下它的透明度,范圍是00~ff。
private void drawRound(Canvas canvas) {
canvas.save();
//內(nèi)圓
paint.setAlpha(0x40);
paint.setStrokeWidth(sweepInWidth);
RectF rectf = new RectF(-radius,-radius,radius,radius);
canvas.drawArc(rectf,startAngle,sweepAngle,false,paint);
//外圓
paint.setStrokeWidth(sweepOutWidth);
int w = dp2px(10);
RectF rectf2 = new RectF(-radius-w , -radius-w , radius+w , radius+w);
canvas.drawArc(rectf2,startAngle,sweepAngle,false,paint);
canvas.restore();
}
第一部分完成,如圖

drawScale():如果你看過幾篇自定義view文章,應(yīng)該都知道了靠旋轉(zhuǎn)畫布來畫刻度和文字的套路了,調(diào)用canvas.rotate就可以旋轉(zhuǎn)畫布,負(fù)數(shù)代表順時針,這里我們打算把起始位置旋轉(zhuǎn)到原點(diǎn)正上方,即270度的地方,這樣畫刻度和文字的坐標(biāo)就很好計(jì)算了,每畫完一次讓畫布逆時針轉(zhuǎn)一個刻度間隔,一直循環(huán)到畫完。我們觀察一下原圖,粗的刻度線一共有6條,數(shù)字的刻度是再粗刻度線下面的,每兩個粗刻度線之間有5條細(xì)刻度線,并且中間那條細(xì)刻度線下方有對應(yīng)文字。我們把掃過的角度除以30,就是每個刻度的間隔了,然后通過判斷就可以畫對應(yīng)刻度和文字了。
關(guān)于獲取文字的寬高,有兩種方法,一種是paint.measureText(text)測量文字寬度,返回值類型是float,但是得不到高度。另一種是Rect rect = new Rect();paint.getTextBounds(text,0,text.length(),rect); 將文字的屬性放入rect里,不過是int值,我們畫的文字夠小的了,所以最好用第一種,除非需要高度值。
另外,我發(fā)現(xiàn)繪制文字時,坐標(biāo)值代表的是文字的左下角,不同于一般的從左上角,所以canvas.drawText傳入的xy坐標(biāo)是text的左下角坐標(biāo)
private String[] text ={"較差","中等","良好","優(yōu)秀","極好"};
private void drawScale(Canvas canvas) {
canvas.save();
float angle = (float)sweepAngle/30;//刻度間隔
canvas.rotate(-270+startAngle); //將起始刻度點(diǎn)旋轉(zhuǎn)到正上方(270)
for (int i = 0; i <= 30; i++) {
if(i%6 == 0){ //畫粗刻度和刻度值
paint.setStrokeWidth(dp2px(2));
paint.setAlpha(0x70);
canvas.drawLine(0, -radius-sweepInWidth/2,0, -radius+sweepInWidth/2+dp2px(1), paint);
drawText(canvas,i*maxNum/30+"",paint);
}else { //畫細(xì)刻度
paint.setStrokeWidth(dp2px(1));
paint.setAlpha(0x50);
canvas.drawLine(0,-radius-sweepInWidth/2,0, -radius+sweepInWidth/2, paint);
}
if(i==3 || i==9 || i==15 || i==21 || i==27){ //畫刻度區(qū)間文字
paint.setStrokeWidth(dp2px(2));
paint.setAlpha(0x50);
drawText(canvas,text[(i-3)/6], paint);
}
canvas.rotate(angle); //逆時針
}
canvas.restore();
}
private void drawText(Canvas canvas ,String text ,Paint paint) {
paint.setStyle(Paint.Style.FILL);
paint.setTextSize(sp2px(8));
float width = paint.measureText(text); //相比getTextBounds來說,這個方法獲得的類型是float,更精確些
// Rect rect = new Rect();
// paint.getTextBounds(text,0,text.length(),rect);
canvas.drawText(text,-width/2 , -radius + dp2px(15),paint);
paint.setStyle(Paint.Style.STROKE);
}
第二部分完畢,看圖

drawIndicator:這一步是畫外圓弧上的進(jìn)度值,觀察原圖,發(fā)現(xiàn)有三個問題需要解決:表示進(jìn)度的弧度值和小圓點(diǎn)的坐標(biāo)怎么計(jì)算,進(jìn)度值的透明度漸變怎么實(shí)現(xiàn)?小圓點(diǎn)像光源一樣邊緣模糊的效果怎么實(shí)現(xiàn)?
對于坐標(biāo)計(jì)算,其實(shí)也較簡單,將當(dāng)前值比上最大值,得到一個比例就可以計(jì)算進(jìn)度條掃過的弧度,小圓點(diǎn)呢繪制與進(jìn)度條的尾端,角度已經(jīng)有了(起始角度+掃過的角度),用三角函數(shù)就可以算了。
對于顏色漸變,可以用paint的shader渲染,它有5個子類
- BitmapShader位圖
- LinearGradient線性漸變
- RadialGradient光束漸變
- SweepGradient梯度漸變
- ComposeShader混合漸變
我們使用梯度漸變來實(shí)現(xiàn),傳入坐標(biāo)和一個顏色數(shù)組就可以實(shí)現(xiàn)對顏色的梯度漸變,這里我們對顏色的修改當(dāng)然只是修改它的透明度,我們知道32位的顏色值前8位就是表示透明度的。
對于小圓點(diǎn)有光源一樣的邊緣模糊效果,我用的是paint的setMaskFilter,其中有一個子類BlurMaskFilter可以實(shí)現(xiàn)邊緣模糊效果~( 不知道有沒有什么別的方法實(shí)現(xiàn)這種效果) 具體看代碼
private int[] indicatorColor = {0xffffffff,0x00ffffff,0x99ffffff,0xffffffff};
這里顏色數(shù)組這樣取值的原因在文章最后說明
private void drawIndicator(Canvas canvas) {
canvas.save();
paint_2.setStyle(Paint.Style.STROKE);
int sweep;
if(currentNum<=maxNum){
sweep = (int)((float)currentNum/(float)maxNum*sweepAngle);
}else {
sweep = sweepAngle;
}
paint_2.setStrokeWidth(sweepOutWidth);
Shader shader =new SweepGradient(0,0,indicatorColor,null);
paint_2.setShader(shader);
int w = dp2px(10);
RectF rectf = new RectF(-radius-w , -radius-w , radius+w , radius+w);
canvas.drawArc(rectf,startAngle,sweep,false,paint_2);
float x = (float) ((radius+dp2px(10))*Math.cos(Math.toRadians(startAngle+sweep)));
float y = (float) ((radius+dp2px(10))*Math.sin(Math.toRadians(startAngle+sweep)));
paint_3.setStyle(Paint.Style.FILL);
paint_3.setColor(0xffffffff);
paint_3.setMaskFilter(new BlurMaskFilter(dp2px(3), BlurMaskFilter.Blur.SOLID)); //需關(guān)閉硬件加速
canvas.drawCircle(x,y,dp2px(3),paint_3);
canvas.restore();
}
記得關(guān)閉硬件加速,就是加一句<activity Android:hardwareAccelerated="false" >
第三部完畢,看圖

drawCenterText:這步簡單,注意剛才說的繪制文字時從左下角開始的和兩種測量文字寬度的區(qū)別就好。
private void drawCenterText(Canvas canvas) {
canvas.save();
paint_4.setStyle(Paint.Style.FILL);
paint_4.setTextSize(radius/2);
paint_4.setColor(0xffffffff);
canvas.drawText(currentNum+"",-paint_4.measureText(currentNum+"")/2,0,paint_4);
paint_4.setTextSize(radius/4);
String content = "信用";
if(currentNum < maxNum*1/5){
content += text[0];
}else if(currentNum >= maxNum*1/5 && currentNum < maxNum*2/5){
content += text[1];
}else if(currentNum >= maxNum*2/5 && currentNum < maxNum*3/5){
content += text[2];
}else if(currentNum >= maxNum*3/5 && currentNum < maxNum*4/5){
content += text[3];
}else if(currentNum >= maxNum*4/5){
content += text[4];
}
Rect r = new Rect();
paint_4.getTextBounds(content,0,content.length(),r);
canvas.drawText(content,-r.width()/2,r.height()+20,paint_4);
canvas.restore();
}
到這里繪制部分差不多完成了。接下來要實(shí)現(xiàn)的是當(dāng)改變值時的動畫效果,同時改變背景顏色。
setCurrentNumAnim就是供用戶調(diào)用的。我們可以通過屬性動畫來改變當(dāng)前值,注意要給當(dāng)前值(currentNum)加上setter和getter,因?yàn)閷傩詣赢媰?nèi)部需要調(diào)用它們。
對于動畫的時間,簡單寫個計(jì)算公式就好,然后監(jiān)聽動畫過程,在里面實(shí)現(xiàn)背景顏色的改變。怎么才能像支付寶芝麻信用那樣紅橙黃綠藍(lán)的漸變呢?我按自己思路實(shí)現(xiàn)了一個可以三種顏色之間漸變的效果。
大家學(xué)習(xí)屬性動畫時應(yīng)該了解過插值器估值器的作用,我就是用ArgbEvaluator估值器實(shí)現(xiàn)顏色漸變的,調(diào)用它的evaluate方法,傳入一個0~1的比例,傳入開始和結(jié)束的顏色,就可以根據(jù)當(dāng)前比例得到介于這兩個顏色之間的顏色值。
這里我實(shí)現(xiàn)了紅到橙再到藍(lán)的漸變,假設(shè)最大值是500,那么當(dāng)前值x從0~250的過程中是從紅到橙,x/(500/2)就可以得到一個0~1的變化比例,當(dāng)前值從250~500的過程是橙到藍(lán),也需要一個0~1的變化過程的比例,計(jì)算方法就是(x-250)/(250) 其中250就是(500/2)得來的。按照這樣的思路當(dāng)然可以實(shí)現(xiàn)更多顏色之間的漸變,就是想辦法在各區(qū)間里算出一個0~1的比例值就行。注意數(shù)據(jù)類型轉(zhuǎn)換,上代碼!
public int getCurrentNum() {
return currentNum;
}
public void setCurrentNum(int currentNum) {
this.currentNum = currentNum;
invalidate();
}
public void setCurrentNumAnim(int num) {
float duration = (float)Math.abs(num-currentNum)/maxNum *1500+500; //根據(jù)進(jìn)度差計(jì)算動畫時間
ObjectAnimator anim = ObjectAnimator.ofInt(this,"currentNum",num);
anim.setDuration((long) Math.min(duration,2000));
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (int) animation.getAnimatedValue();
int color = calculateColor(value);
setBackgroundColor(color);
}
});
anim.start();
}
private int calculateColor(int value){
ArgbEvaluator evealuator = new ArgbEvaluator();
float fraction = 0;
int color = 0;
if(value <= maxNum/2){
fraction = (float)value/(maxNum/2);
color = (int) evealuator.evaluate(fraction,0xFFFF6347,0xFFFF8C00); //由紅到橙
}else {
fraction = ( (float)value-maxNum/2 ) / (maxNum/2);
color = (int) evealuator.evaluate(fraction,0xFFFF8C00,0xFF00CED1); //由橙到藍(lán)
}
return color;
}
鏘鏘鏘~ 完畢外部調(diào)用setCurrentNumAnim就可以動畫的改變數(shù)值啦


好了,還有最后一個問題,就是前面提到的
為什么透明度漸變的顏色數(shù)組是這樣的
private int[] indicatorColor = {0xffffffff,0x00ffffff,0x99ffffff,0xffffffff};
大概就是從不透明-->透明-->半透明-->不透明的變化
問:第一個不是多余的么?為什么要一開始不透明?
答:我也有點(diǎn)納悶,因?yàn)閟weepGradient顏色漸變是從x正軸開始的,如果我顏色數(shù)組是這樣的,即從透明-->半透明-->不透明:
private int[] indicatorColor = {0x00ffffff,0x99ffffff,0xffffffff};
那么畫個圓是長這樣的

而我們的儀表盤這里是從160度開始,掃220度,也就是如果這樣有一部分角度(0~20度)會變透明,不是我們想要的效果。。。所以我用了這樣:
private int[] indicatorColor = {0xffffffff,0x00ffffff,0x99ffffff,0xffffffff};
這樣的數(shù)組。。畫出來是這樣的

這樣至少保證0~20度看起來也是很白的,整個進(jìn)度條就實(shí)現(xiàn)了像從透明到不透明的效果。
其實(shí)也不是很優(yōu)雅。。因?yàn)槠鹗冀嵌群蛼哌^的角度是可以自定義更改的。。所以小伙伴們有什么更好的方法么?
源碼地址:http://xiazai.jb51.net/201701/yuanma/diy_roundindicator_jb51.rar
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android開發(fā)實(shí)現(xiàn)撥打電話與發(fā)送信息的方法分析
這篇文章主要介紹了Android開發(fā)實(shí)現(xiàn)撥打電話與發(fā)送信息的方法,結(jié)合實(shí)例形式分析了Android撥打電話及發(fā)送信息相關(guān)布局、功能實(shí)現(xiàn)及權(quán)限控制操作技巧,需要的朋友可以參考下2017-12-12
Android編程中TextView寬度過大導(dǎo)致Drawable無法居中問題解決方法
這篇文章主要介紹了Android編程中TextView寬度過大導(dǎo)致Drawable無法居中問題解決方法,以實(shí)例形式較為詳細(xì)的分析了TextView設(shè)置及xml布局與調(diào)用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10
Android自定義view實(shí)現(xiàn)多色進(jìn)度條GradientProgressView的繪制
我們常使用shape實(shí)現(xiàn)漸變色,但是shape的極限卻只有三色,如果有超過三種顏色的View的要求,那么我們就不得不去自定義View來實(shí)現(xiàn)這個需求,所以下面我們就來看看如何自定義view實(shí)現(xiàn)多色進(jìn)度條的繪制吧2023-08-08
Android開發(fā)Intent跳轉(zhuǎn)傳遞list集合實(shí)現(xiàn)示例
這篇文章主要為大家介紹了Android開發(fā)Intent跳轉(zhuǎn)傳遞list集合實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07
Android自定義帶有圓形進(jìn)度條的可長按控件功能
這篇文章主要介紹了Android自定義帶有圓形進(jìn)度條的可長按控件,思路很簡單,使用簡單的畫筆工具就可以完成這個控件,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2022-06-06
Android 加載assets中的資源文件實(shí)例代碼
這篇文章主要介紹了Android 加載assets中的資源文件實(shí)例代碼的相關(guān)資料,這里附有實(shí)例代碼,需要的朋友可以參考下2017-01-01
Android沉浸式頂部實(shí)現(xiàn)代碼及效果
這篇文章主要介紹了Android沉浸式頂部實(shí)現(xiàn)代碼及效果,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09
Android采用File形式保存與讀取數(shù)據(jù)的方法
這篇文章主要介紹了Android采用File形式保存與讀取數(shù)據(jù)的方法,涉及Android文件流操作的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06

