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"/> <!--圓盤掃過的角度--> <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,也是比較簡單,對(duì)于不是確定值的直接給定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è)置,那時(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í)還沒測量寬高 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è)很簡單,內(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ù)代表順時(shí)針,這里我們打算把起始位置旋轉(zhuǎn)到原點(diǎn)正上方,即270度的地方,這樣畫刻度和文字的坐標(biāo)就很好計(jì)算了,每畫完一次讓畫布逆時(shí)針轉(zhuǎn)一個(gè)刻度間隔,一直循環(huán)到畫完。我們觀察一下原圖,粗的刻度線一共有6條,數(shù)字的刻度是再粗刻度線下面的,每兩個(gè)粗刻度線之間有5條細(xì)刻度線,并且中間那條細(xì)刻度線下方有對(duì)應(yīng)文字。我們把掃過的角度除以30,就是每個(gè)刻度的間隔了,然后通過判斷就可以畫對(duì)應(yīng)刻度和文字了。
關(guān)于獲取文字的寬高,有兩種方法,一種是paint.measureText(text)測量文字寬度,返回值類型是float,但是得不到高度。另一種是Rect rect = new Rect();paint.getTextBounds(text,0,text.length(),rect); 將文字的屬性放入rect里,不過是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來說,這個(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è)問題需要解決:表示進(jìn)度的弧度值和小圓點(diǎn)的坐標(biāo)怎么計(jì)算,進(jìn)度值的透明度漸變?cè)趺磳?shí)現(xiàn)?小圓點(diǎn)像光源一樣邊緣模糊的效果怎么實(shí)現(xiàn)?
對(duì)于坐標(biāo)計(jì)算,其實(shí)也較簡單,將當(dāng)前值比上最大值,得到一個(gè)比例就可以計(jì)算進(jìn)度條掃過的弧度,小圓點(diǎn)呢繪制與進(jìn)度條的尾端,角度已經(jīng)有了(起始角度+掃過的角度),用三角函數(shù)就可以算了。
對(duì)于顏色漸變,可以用paint的shader渲染,它有5個(gè)子類
- BitmapShader位圖
- LinearGradient線性漸變
- RadialGradient光束漸變
- SweepGradient梯度漸變
- ComposeShader混合漸變
我們使用梯度漸變來實(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ù)組這樣取值的原因在文章最后說明
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:這步簡單,注意剛才說的繪制文字時(shí)從左下角開始的和兩種測量文字寬度的區(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)改變值時(shí)的動(dòng)畫效果,同時(shí)改變背景顏色。
setCurrentNumAnim就是供用戶調(diào)用的。我們可以通過屬性動(dòng)畫來改變當(dāng)前值,注意要給當(dāng)前值(currentNum)加上setter和getter,因?yàn)閷傩詣?dòng)畫內(nèi)部需要調(diào)用它們。
對(duì)于動(dòng)畫的時(shí)間,簡單寫個(gè)計(jì)算公式就好,然后監(jiān)聽動(dòng)畫過程,在里面實(shí)現(xiàn)背景顏色的改變。怎么才能像支付寶芝麻信用那樣紅橙黃綠藍(lán)的漸變呢?我按自己思路實(shí)現(xiàn)了一個(gè)可以三種顏色之間漸變的效果。
大家學(xué)習(xí)屬性動(dòng)畫時(shí)應(yīng)該了解過插值器估值器的作用,我就是用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的過程中是從紅到橙,x/(500/2)就可以得到一個(gè)0~1的變化比例,當(dāng)前值從250~500的過程是橙到藍(lán),也需要一個(gè)0~1的變化過程的比例,計(jì)算方法就是(x-250)/(250) 其中250就是(500/2)得來的。按照這樣的思路當(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è)問題,就是前面提到的
為什么透明度漸變的顏色數(shù)組是這樣的
private int[] indicatorColor = {0xffffffff,0x00ffffff,0x99ffffff,0xffffffff};
大概就是從不透明-->透明-->半透明-->不透明的變化
問:第一個(gè)不是多余的么?為什么要一開始不透明?
答:我也有點(diǎn)納悶,因?yàn)閟weepGradient顏色漸變是從x正軸開始的,如果我顏色數(shù)組是這樣的,即從透明-->半透明-->不透明:
private int[] indicatorColor = {0x00ffffff,0x99ffffff,0xffffffff};
那么畫個(gè)圓是長這樣的
而我們的儀表盤這里是從160度開始,掃220度,也就是如果這樣有一部分角度(0~20度)會(huì)變透明,不是我們想要的效果。。。所以我用了這樣:
private int[] indicatorColor = {0xffffffff,0x00ffffff,0x99ffffff,0xffffffff};
這樣的數(shù)組。。畫出來是這樣的
這樣至少保證0~20度看起來也是很白的,整個(gè)進(jìn)度條就實(shí)現(xiàn)了像從透明到不透明的效果。
其實(shí)也不是很優(yōu)雅。。因?yàn)槠鹗冀嵌群蛼哌^的角度是可以自定義更改的。。所以小伙伴們有什么更好的方法么?
源碼地址:http://xiazai.jb51.net/201701/yuanma/diy_roundindicator_jb51.rar
以上就是本文的全部內(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-12Android編程中TextView寬度過大導(dǎo)致Drawable無法居中問題解決方法
這篇文章主要介紹了Android編程中TextView寬度過大導(dǎo)致Drawable無法居中問題解決方法,以實(shí)例形式較為詳細(xì)的分析了TextView設(shè)置及xml布局與調(diào)用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10Android自定義view實(shí)現(xiàn)多色進(jìn)度條GradientProgressView的繪制
我們常使用shape實(shí)現(xiàn)漸變色,但是shape的極限卻只有三色,如果有超過三種顏色的View的要求,那么我們就不得不去自定義View來實(shí)現(xiàn)這個(gè)需求,所以下面我們就來看看如何自定義view實(shí)現(xiàn)多色進(jìn)度條的繪制吧2023-08-08Android開發(fā)Intent跳轉(zhuǎn)傳遞list集合實(shí)現(xiàn)示例
這篇文章主要為大家介紹了Android開發(fā)Intent跳轉(zhuǎn)傳遞list集合實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07Android自定義帶有圓形進(jìn)度條的可長按控件功能
這篇文章主要介紹了Android自定義帶有圓形進(jìn)度條的可長按控件,思路很簡單,使用簡單的畫筆工具就可以完成這個(gè)控件,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2022-06-06Android 加載assets中的資源文件實(shí)例代碼
這篇文章主要介紹了Android 加載assets中的資源文件實(shí)例代碼的相關(guān)資料,這里附有實(shí)例代碼,需要的朋友可以參考下2017-01-01Android沉浸式頂部實(shí)現(xiàn)代碼及效果
這篇文章主要介紹了Android沉浸式頂部實(shí)現(xiàn)代碼及效果,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09Android采用File形式保存與讀取數(shù)據(jù)的方法
這篇文章主要介紹了Android采用File形式保存與讀取數(shù)據(jù)的方法,涉及Android文件流操作的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06