Android自定義View實(shí)現(xiàn)環(huán)形進(jìn)度條的思路與實(shí)例
前言
前段時(shí)間看到了豆瓣FM的音樂播放界面,有一個(gè)環(huán)形的進(jìn)度條,非常的好看,于是想了想,為什么不自己做一個(gè)呢,于是就開始了自定義的過(guò)程
豆瓣FM的播放界面如下圖:
功能分析
雖然功能比較簡(jiǎn)單,但是仍然需要仔細(xì)分析
1.圖標(biāo)外還有一圈圓圈,可以設(shè)置寬度
2.圓形進(jìn)度條和進(jìn)度條底部,可以設(shè)置寬度,顏色等
3.內(nèi)部有一個(gè)圓形圖片,可旋轉(zhuǎn)
實(shí)現(xiàn)思路分析
1.可以設(shè)置寬度的圓圈
這個(gè)比較容易,直接在onDraw方法中使用canvas繪制即可,當(dāng)然,在間距和半徑的處理上需要仔細(xì),控件本體其實(shí)還是一個(gè)長(zhǎng)方形,我們需要選取較短的那一邊作為直徑,同時(shí)也要處理內(nèi)部的padding
2.圓形進(jìn)度條和進(jìn)度條底部,可以設(shè)置寬度,顏色等
這個(gè)可以用canvas的drawArc方法來(lái)實(shí)現(xiàn),通過(guò)繪制不同長(zhǎng)度的弧形來(lái)達(dá)到顯示進(jìn)度的目的,但是需要注意的是,我們需要計(jì)算好弧形的半徑以及開始和結(jié)束點(diǎn)。
3.內(nèi)部有一個(gè)圓形圖片,可旋轉(zhuǎn)
這個(gè)需求可以分為三個(gè)部分,有圖片,圓形,可以旋轉(zhuǎn)
先說(shuō)有圖,很簡(jiǎn)單,canvas的drawbitmap方法繪制(canvas真是好東西)
再說(shuō)圓形,這就比較復(fù)雜了,但是整體來(lái)說(shuō)依然是使用canvas來(lái)對(duì)bitmap進(jìn)行操作,會(huì)在代碼中細(xì)說(shuō)
最后是可以旋轉(zhuǎn),我們可以通過(guò)canvas的rotate方法來(lái)做。
效果展示
說(shuō)了這么多,那么最后的效果是怎樣的呢?畢竟空口無(wú)憑,在進(jìn)入代碼展示的環(huán)節(jié)之前還是看看最后的效果吧。
這是我自己做的一個(gè)定時(shí)鎖屏的項(xiàng)目,地址是這里是地址或者本地下載
這是這個(gè)項(xiàng)目運(yùn)行鎖屏的時(shí)候的動(dòng)圖(大家都喜歡動(dòng)圖)
代碼實(shí)現(xiàn)
下面開始展示代碼,并加以分析
我們主要的工作是在一個(gè)自定義的view中的onDraw方法實(shí)現(xiàn)的,所以,我們需要有一個(gè)繼承View類的子類,我們就叫他MyProgress吧
我們展示的就是這個(gè)MyProgress的onDraw方法
1.可以設(shè)置寬度的圓圈
很簡(jiǎn)單,我們只需要調(diào)用canvas的drawCircle方法即可,但是需要注意對(duì)padding的處理,因?yàn)椴惶幚砭蜁?huì)無(wú)效
super.onDraw(canvas); //需要在函數(shù)開始的地方調(diào)用父類的onDraw final int paddingLeft = getPaddingLeft(); final int paddingRight = getPaddingRight(); final int paddingTop = getPaddingTop(); final int paddingBottom = getPaddingBottom(); //獲取padding //get the view's width and height and decide the radiu int width = getWidth() - paddingLeft - paddingRight; int height = getHeight() - paddingTop - paddingBottom; radiu = Math.min(width , height) / 2 - boundWidth - progressWidth; //計(jì)算半徑,選取長(zhǎng)寬中短的那個(gè)做處理,boundWidth是圓圈的寬度,progressWidth是進(jìn)度條的寬度 //setup the paint paint.setStyle(Paint.Style.STROKE); //設(shè)置paint為畫輪廓 paint.setStrokeWidth(boundWidth); //設(shè)置寬度 paint.setColor(Color.BLACK); //設(shè)置顏色 //draw the inner circle int centerX = paddingLeft + getWidth()/2; int centerY = paddingTop + getHeight() / 2; //計(jì)算圓的中心點(diǎn) canvas.drawCircle(centerX,centerY, radiu, paint); //繪制圓形
2.圓形進(jìn)度條和進(jìn)度條底部,可以設(shè)置寬度,顏色等
這里需要注意的就是開始的角度和結(jié)束的角度了,為了達(dá)到進(jìn)度條目的,所以我們需要隨著業(yè)務(wù)狀態(tài)的改變來(lái)改變這個(gè)值
//set paint for arc paint.setStrokeWidth(progressWidth); paint.setStrokeCap(Paint.Cap.ROUND);//設(shè)置進(jìn)度寬度,設(shè)置末端是一個(gè)圓弧 //prepare for draw arc RectF oval = new RectF(); oval.left = centerX -totalRadiu ; oval.top =centerY- totalRadiu ; oval.right = centerX + totalRadiu; oval.bottom = centerY+ totalRadiu; //新建一個(gè)橢圓,設(shè)置其四個(gè)點(diǎn)的坐標(biāo) paint.setColor(progressBackColor);//設(shè)置進(jìn)度條背景的顏色 //draw background arc canvas.drawArc(oval, arcStar, arcEnd, false, paint); //繪制底部的一個(gè)圓弧,作為背景 //draw progress arc paint.setColor(progressColor);//設(shè)置進(jìn)度條的顏色 canvas.drawArc(oval, arcStar, progress, false, paint);//繪制進(jìn)度條
3.內(nèi)部有一個(gè)圓形圖片,可旋轉(zhuǎn)
這一段比較復(fù)雜,直接用代碼解釋
float totalRadiu = radiu +boundWidth +progressWidth/2;//設(shè)置外徑 //draw the circlr pic if (drawable != null&&bitmap == null) { image = ((BitmapDrawable) drawable).getBitmap();//獲取設(shè)置的bitmap資源 bitmap = Bitmap.createBitmap((int)(2*totalRadiu),(int)(2*totalRadiu), Bitmap.Config.ARGB_8888); Canvas bitmapCanvas = new Canvas(bitmap);//新建一個(gè)bitmap并新建一個(gè)canvas用以操作 Paint bitmapPaint = new Paint(); bitmapPaint.setAntiAlias(true);//新建一個(gè)paint并設(shè)置反鋸齒 bitmapCanvas.drawCircle(totalRadiu, totalRadiu, radiu, bitmapPaint);//畫一個(gè)圓 bitmapPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));//關(guān)鍵代碼,設(shè)置為交集模式,會(huì)讓后面的內(nèi)容和已有內(nèi)容取交集 bitmapCanvas.drawBitmap(image,null,new RectF(0,0,2*totalRadiu,2*totalRadiu) , bitmapPaint);//繪制自己的圖片到現(xiàn)有畫布上 } Rect rect = new Rect((int)(centerX -totalRadiu),(int)(centerY-totalRadiu),(int)(centerX+totalRadiu),(int)(centerY+ totalRadiu));//新建一個(gè)rect,設(shè)定邊界點(diǎn) canvas.save(); if(isRotate) canvas.rotate(rotateDegree,centerX,centerY);//設(shè)置旋轉(zhuǎn),為了實(shí)現(xiàn)圖片轉(zhuǎn)動(dòng)效果,rotateDegree為旋轉(zhuǎn)角度 canvas.drawBitmap(bitmap,null ,rect, paint);//繪制處理過(guò)的圖片
有了上面這些代碼,我們自定義View的主體部分就完成了,當(dāng)然還有一些輔助的部分,比如更新進(jìn)度和選擇角度的函數(shù),設(shè)置一些顏色和寬度之類的參數(shù)等
完整代碼
MyProgress
public class MyProgressBar extends View { float progress = 360; float arcStar = 270; float arcEnd = 360; double rotateStep = 0.2; Bitmap bitmap; int totalTime; Bitmap image; Drawable drawable; int boundWidth = 5; private int progressWidth = 30; private boolean isRotate = false; private int progressColor = Color.GREEN; private int progressBackColor = Color.GREEN; private float rotateDegree = 0; public MyProgressBar(Context context) { super(context); } public MyProgressBar(Context context, AttributeSet attrs) { super(context, attrs); } public MyProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } private float radiu; private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); public void setRadiu(float radiu) { this.radiu = radiu; invalidate(); } //start 函數(shù)使用 countDownTimer類來(lái)更新progress和旋轉(zhuǎn)角度 public void start(long time) { bitmap = null; time *= 60000; final float step = (float) 360 / (time / 30); CountDownTimer mTimer = new CountDownTimer(time, 30) { public void onTick(long millisUntilFinished) { progress -= step; rotateDegree -= rotateStep; invalidate(); } @Override public void onFinish() { end(step); } }; mTimer.start(); } private void end(float step) { progress -= step; invalidate(); progress = 0; rotateDegree = 0; invalidate(); } public void setBoundWidth(int width) { boundWidth = width; } public void setProgressWidth(int width) { progressWidth = width; } public void setProgressColor(int color) { progressColor = color; } public void setProgressBackColor(int color) { progressBackColor = color; } public void setDrawable(Drawable drawable) { this.drawable = drawable; invalidate(); } public void setIsRote(boolean rotate) { this.isRotate = rotate; invalidate(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); final int paddingLeft = getPaddingLeft(); final int paddingRight = getPaddingRight(); final int paddingTop = getPaddingTop(); final int paddingBottom = getPaddingBottom(); //get the view's width and height and decide the radiu int width = getWidth() - paddingLeft - paddingRight; int height = getHeight() - paddingTop - paddingBottom; radiu = Math.min(width , height) / 2 - boundWidth - progressWidth; //setup the paint paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(boundWidth); paint.setColor(Color.BLACK); //draw the inner circle int centerX = paddingLeft + getWidth()/2; int centerY = paddingTop + getHeight() / 2; canvas.drawCircle(centerX,centerY, radiu, paint); float totalRadiu = radiu +boundWidth +progressWidth/2; //draw the circlr pic if (drawable != null&&bitmap == null) { image = ((BitmapDrawable) drawable).getBitmap(); bitmap = Bitmap.createBitmap((int)(2*totalRadiu),(int)(2*totalRadiu), Bitmap.Config.ARGB_8888); Canvas bitmapCanvas = new Canvas(bitmap); Paint bitmapPaint = new Paint(); bitmapPaint.setAntiAlias(true); bitmapCanvas.drawCircle(totalRadiu, totalRadiu, radiu, bitmapPaint); bitmapPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); bitmapCanvas.drawBitmap(image,null,new RectF(0,0,2*totalRadiu,2*totalRadiu) , bitmapPaint); } Rect rect = new Rect((int)(centerX -totalRadiu),(int)(centerY-totalRadiu),(int)(centerX+totalRadiu),(int)(centerY+ totalRadiu)); canvas.save(); if(isRotate) canvas.rotate(rotateDegree,centerX,centerY); canvas.drawBitmap(bitmap,null ,rect, paint); canvas.restore(); //set paint for arc paint.setStrokeWidth(progressWidth); paint.setStrokeCap(Paint.Cap.ROUND); //prepare for draw arc RectF oval = new RectF(); oval.left = centerX -totalRadiu ; oval.top =centerY- totalRadiu ; oval.right = centerX + totalRadiu; oval.bottom = centerY+ totalRadiu; paint.setColor(progressBackColor); //draw background arc canvas.drawArc(oval, arcStar, arcEnd, false, paint); //draw progress arc paint.setColor(progressColor); canvas.drawArc(oval, arcStar, progress, false, paint); } }
完整的工程,包括對(duì)這個(gè)自定義VIEW的應(yīng)用例子可以參考我在GitHub上的工程地址在這里,也可以本地下載
總結(jié)
這個(gè)看似簡(jiǎn)單的自定義View的制作當(dāng)中還是遇到了不少值得思考的問(wèn)題,這也是為什么有這篇文章的原因
1.在處理圓形剪裁圖片的時(shí)候,要注意剪裁的canvas所用的坐標(biāo)是相對(duì)于處理圖片的,而不是整體坐標(biāo)
2.在繪制時(shí),應(yīng)該盡量減少重復(fù)的處理,比如圓形圖片剪裁,一次就夠了,如果次數(shù)過(guò)多,每次更新進(jìn)度的時(shí)候就會(huì)去進(jìn)行一次,導(dǎo)致整個(gè)View比較卡,進(jìn)度不準(zhǔn)確
3.對(duì)于自定義View中幾個(gè)關(guān)鍵點(diǎn)的坐標(biāo),應(yīng)該用一個(gè)比較簡(jiǎn)單易懂的表達(dá)式表示,否則做到后期會(huì)搞混淆,而陷入坐標(biāo)的泥潭之中
4.某些看起來(lái)很厲害的效果只要合理分析,分步實(shí)現(xiàn),并不會(huì)很難
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)各位Android開發(fā)者們能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
- android自定義進(jìn)度條漸變色View的實(shí)例代碼
- Android自定義View實(shí)現(xiàn)漸變色進(jìn)度條
- Android自定義View實(shí)現(xiàn)帶數(shù)字的進(jìn)度條實(shí)例代碼
- Android 自定義view和屬性動(dòng)畫實(shí)現(xiàn)充電進(jìn)度條效果
- Android自定義View實(shí)現(xiàn)水平帶數(shù)字百分比進(jìn)度條
- Android自定義View仿華為圓形加載進(jìn)度條
- Android自定義View實(shí)現(xiàn)加載進(jìn)度條效果
- Android view自定義實(shí)現(xiàn)動(dòng)態(tài)進(jìn)度條
- Android自定義View基礎(chǔ)開發(fā)之圖片加載進(jìn)度條
- Android如何自定義View實(shí)現(xiàn)橫向的雙水波紋進(jìn)度條
相關(guān)文章
Android SQLite數(shù)據(jù)庫(kù)增刪改查操作的案例分析
本篇文章介紹了,在Android中SQLite數(shù)據(jù)庫(kù)增刪改查操作的案例分析,需要的朋友參考下2013-04-04Android實(shí)現(xiàn)靜態(tài)廣播監(jiān)聽器的方法
這篇文章主要介紹了Android實(shí)現(xiàn)靜態(tài)廣播監(jiān)聽器的方法,涉及Android的廣播機(jī)制與記錄監(jiān)聽廣播信息的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07Flutter進(jìn)階之實(shí)現(xiàn)動(dòng)畫效果(九)
這篇文章主要為大家詳細(xì)介紹了Flutter進(jìn)階之實(shí)現(xiàn)動(dòng)畫效果的第九篇,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-08-08EditText實(shí)現(xiàn)輸入限制和校驗(yàn)功能實(shí)例代碼
本文通過(guò)實(shí)例代碼給大家介紹EditText實(shí)現(xiàn)輸入限制和校驗(yàn)功能,感興趣的朋友參考下吧2017-08-08Android EasyPermissions官方庫(kù)高效處理權(quán)限相關(guān)教程
Easypermissions簡(jiǎn)化了Android M的運(yùn)行時(shí)權(quán)限的申請(qǐng)、結(jié)果處理、判斷等步驟。這篇文章主要介紹了Android EasyPermissions官方庫(kù)高效處理權(quán)限相關(guān)教程,需要的朋友可以參考下2017-11-11android 添加按(power鍵)電源鍵結(jié)束通話(掛斷電話)
首先我們發(fā)現(xiàn)現(xiàn)在我們所用的android智能手機(jī)大部分都有當(dāng)你在打電話時(shí)按power鍵來(lái)掛斷電話,一般都是在設(shè)置中2013-01-01Android選擇圖片或拍照?qǐng)D片上傳到服務(wù)器
這篇文章主要為大家詳細(xì)介紹了android選擇圖片或拍照?qǐng)D片上傳到服務(wù)器的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01Android利用SoundPool實(shí)現(xiàn)音樂池
這篇文章主要為大家詳細(xì)介紹了Android利用SoundPool實(shí)現(xiàn)音樂池,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11Activity生命周期與啟動(dòng)模式圖文解說(shuō)
這篇文章主要介紹了Activity生命周期與啟動(dòng)模式圖文解說(shuō),內(nèi)容比較詳細(xì),具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11