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


首先是自定義一些屬性,可自己再添加,挺基礎(chǔ)的,上代碼
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="RoundIndicatorView">
<!--最大數(shù)值-->
<attr name="maxNum" format="integer"/>
<!--圓盤起始角度-->
<attr name="startAngle" format="integer"/>
<!--圓盤掃過(guò)的角度-->
<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);
}
接下來(lái)重寫onMeasure,也是比較簡(jiǎn)單,對(duì)于不是確定值的直接給定300*400的大小:
@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來(lái)了,注意圓的半徑不要在構(gòu)造方法里就去設(shè)置,那時(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)造方法里初始化,那時(shí)還沒測(cè)量寬高
canvas.save();
canvas.translate(mWidth/2,(mWidth)/2);
drawRound(canvas); //畫內(nèi)外圓弧
drawScale(canvas);//畫刻度
drawIndicator(canvas); //畫當(dāng)前進(jìn)度值
drawCenterText(canvas);//畫中間的文字
canvas.restore();
}
步驟清晰,按順序畫出儀表盤的四個(gè)部分,我們一個(gè)一個(gè)部分的看
drawRound():這個(gè)很簡(jiǎn)單,內(nèi)外圓弧所需的屬性都已經(jīng)定義好了,畫筆是白色的,我們通過(guò)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():如果你看過(guò)幾篇自定義view文章,應(yīng)該都知道了靠旋轉(zhuǎn)畫布來(lái)畫刻度和文字的套路了,調(diào)用canvas.rotate就可以旋轉(zhuǎn)畫布,負(fù)數(shù)代表順時(shí)針,這里我們打算把起始位置旋轉(zhuǎn)到原點(diǎn)正上方,即270度的地方,這樣畫刻度和文字的坐標(biāo)就很好計(jì)算了,每畫完一次讓畫布逆時(shí)針轉(zhuǎn)一個(gè)刻度間隔,一直循環(huán)到畫完。我們觀察一下原圖,粗的刻度線一共有6條,數(shù)字的刻度是再粗刻度線下面的,每?jī)蓚€(gè)粗刻度線之間有5條細(xì)刻度線,并且中間那條細(xì)刻度線下方有對(duì)應(yīng)文字。我們把掃過(guò)的角度除以30,就是每個(gè)刻度的間隔了,然后通過(guò)判斷就可以畫對(duì)應(yīng)刻度和文字了。
關(guān)于獲取文字的寬高,有兩種方法,一種是paint.measureText(text)測(cè)量文字寬度,返回值類型是float,但是得不到高度。另一種是Rect rect = new Rect();paint.getTextBounds(text,0,text.length(),rect); 將文字的屬性放入rect里,不過(guò)是int值,我們畫的文字夠小的了,所以最好用第一種,除非需要高度值。
另外,我發(fā)現(xiàn)繪制文字時(shí),坐標(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); //逆時(shí)針
}
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來(lái)說(shuō),這個(gè)方法獲得的類型是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)有三個(gè)問(wèn)題需要解決:表示進(jìn)度的弧度值和小圓點(diǎn)的坐標(biāo)怎么計(jì)算,進(jìn)度值的透明度漸變?cè)趺磳?shí)現(xiàn)?小圓點(diǎn)像光源一樣邊緣模糊的效果怎么實(shí)現(xiàn)?
對(duì)于坐標(biāo)計(jì)算,其實(shí)也較簡(jiǎn)單,將當(dāng)前值比上最大值,得到一個(gè)比例就可以計(jì)算進(jìn)度條掃過(guò)的弧度,小圓點(diǎn)呢繪制與進(jìn)度條的尾端,角度已經(jīng)有了(起始角度+掃過(guò)的角度),用三角函數(shù)就可以算了。
對(duì)于顏色漸變,可以用paint的shader渲染,它有5個(gè)子類
- BitmapShader位圖
- LinearGradient線性漸變
- RadialGradient光束漸變
- SweepGradient梯度漸變
- ComposeShader混合漸變
我們使用梯度漸變來(lái)實(shí)現(xiàn),傳入坐標(biāo)和一個(gè)顏色數(shù)組就可以實(shí)現(xiàn)對(duì)顏色的梯度漸變,這里我們對(duì)顏色的修改當(dāng)然只是修改它的透明度,我們知道32位的顏色值前8位就是表示透明度的。
對(duì)于小圓點(diǎn)有光源一樣的邊緣模糊效果,我用的是paint的setMaskFilter,其中有一個(gè)子類BlurMaskFilter可以實(shí)現(xiàn)邊緣模糊效果~( 不知道有沒有什么別的方法實(shí)現(xiàn)這種效果) 具體看代碼
private int[] indicatorColor = {0xffffffff,0x00ffffff,0x99ffffff,0xffffffff};
這里顏色數(shù)組這樣取值的原因在文章最后說(shuō)明
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:這步簡(jiǎn)單,注意剛才說(shuō)的繪制文字時(shí)從左下角開始的和兩種測(cè)量文字寬度的區(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();
}
到這里繪制部分差不多完成了。接下來(lái)要實(shí)現(xiàn)的是當(dāng)改變值時(shí)的動(dòng)畫效果,同時(shí)改變背景顏色。
setCurrentNumAnim就是供用戶調(diào)用的。我們可以通過(guò)屬性動(dòng)畫來(lái)改變當(dāng)前值,注意要給當(dāng)前值(currentNum)加上setter和getter,因?yàn)閷傩詣?dòng)畫內(nèi)部需要調(diào)用它們。
對(duì)于動(dòng)畫的時(shí)間,簡(jiǎn)單寫個(gè)計(jì)算公式就好,然后監(jiān)聽動(dòng)畫過(guò)程,在里面實(shí)現(xiàn)背景顏色的改變。怎么才能像支付寶芝麻信用那樣紅橙黃綠藍(lán)的漸變呢?我按自己思路實(shí)現(xiàn)了一個(gè)可以三種顏色之間漸變的效果。
大家學(xué)習(xí)屬性動(dòng)畫時(shí)應(yīng)該了解過(guò)插值器估值器的作用,我就是用ArgbEvaluator估值器實(shí)現(xiàn)顏色漸變的,調(diào)用它的evaluate方法,傳入一個(gè)0~1的比例,傳入開始和結(jié)束的顏色,就可以根據(jù)當(dāng)前比例得到介于這兩個(gè)顏色之間的顏色值。
這里我實(shí)現(xiàn)了紅到橙再到藍(lán)的漸變,假設(shè)最大值是500,那么當(dāng)前值x從0~250的過(guò)程中是從紅到橙,x/(500/2)就可以得到一個(gè)0~1的變化比例,當(dāng)前值從250~500的過(guò)程是橙到藍(lán),也需要一個(gè)0~1的變化過(guò)程的比例,計(jì)算方法就是(x-250)/(250) 其中250就是(500/2)得來(lái)的。按照這樣的思路當(dāng)然可以實(shí)現(xiàn)更多顏色之間的漸變,就是想辦法在各區(qū)間里算出一個(gè)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ì)算動(dòng)畫時(shí)間
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就可以動(dòng)畫的改變數(shù)值啦


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

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

這樣至少保證0~20度看起來(lái)也是很白的,整個(gè)進(jìn)度條就實(shí)現(xiàn)了像從透明到不透明的效果。
其實(shí)也不是很優(yōu)雅。。因?yàn)槠鹗冀嵌群蛼哌^(guò)的角度是可以自定義更改的。。所以小伙伴們有什么更好的方法么?
源碼地址:http://xiazai.jb51.net/201701/yuanma/diy_roundindicator_jb51.rar
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(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寬度過(guò)大導(dǎo)致Drawable無(wú)法居中問(wèn)題解決方法
這篇文章主要介紹了Android編程中TextView寬度過(guò)大導(dǎo)致Drawable無(wú)法居中問(wèn)題解決方法,以實(shí)例形式較為詳細(xì)的分析了TextView設(shè)置及xml布局與調(diào)用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10
Android自定義view實(shí)現(xiàn)多色進(jìn)度條GradientProgressView的繪制
我們常使用shape實(shí)現(xiàn)漸變色,但是shape的極限卻只有三色,如果有超過(guò)三種顏色的View的要求,那么我們就不得不去自定義View來(lái)實(shí)現(xiàn)這個(gè)需求,所以下面我們就來(lái)看看如何自定義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)度條的可長(zhǎng)按控件功能
這篇文章主要介紹了Android自定義帶有圓形進(jìn)度條的可長(zhǎng)按控件,思路很簡(jiǎn)單,使用簡(jiǎn)單的畫筆工具就可以完成這個(gè)控件,本文結(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)代碼及效果,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09
Android采用File形式保存與讀取數(shù)據(jù)的方法
這篇文章主要介紹了Android采用File形式保存與讀取數(shù)據(jù)的方法,涉及Android文件流操作的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06

