本篇文章介紹自定義View配合屬性動畫來實現如下的效果
實現思路如下:
- 根據播放按鈕的圖片大小計算出圓形進度條的大小
- 根據音頻的時間長度計算出圓形進度條繪制的弧度
- 通過Handler刷新界面來更新圓形進度條的進度
具體實現過程分析:
首先來看看自定義View中定義的一些成員變量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
//表示坐標系中的一塊矩形區域 private RectF mRectF; //畫筆 private Paint mPaint; //畫筆寬度 private int mCircleStoreWidth = 3 ; //最大進度值 private int mMaxProcessValue = 100 ; //進度值 private int mProcessValue; private int width; private int height; //播放器按鈕id值 private int bitmapPlay; private int bitmapStop; //播放器按鈕Bitmap對象 private Bitmap drawBitmapPlay; private Bitmap drawBitmapStop; private Context context; //標記是否正在播放中 private boolean isPlay; |
初始化自定義View,在這里獲取播放器按鈕圖片以及初始化畫布畫筆對象以及設置將畫筆設置抗鋸齒
1
2
3
4
5
6
7
8
9
10
11
12
|
private void init(Context context, AttributeSet attrs, int defStyleAttr) { this .context = context; TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.circle_progress_image_attrs); bitmapPlay = a.getResourceId(R.styleable.circle_progress_image_attrs_play_image, R.mipmap.play_button); bitmapStop = a.getResourceId(R.styleable.circle_progress_image_attrs_stop_image, R.mipmap.stop_button); a.recycle(); drawBitmapPlay = BitmapFactory.decodeResource(context.getResources(), bitmapPlay); drawBitmapStop = BitmapFactory.decodeResource(context.getResources(), bitmapStop); mRectF = new RectF(); mPaint = new Paint(); mPaint.setAntiAlias( true ); } |
這里使用了自定義attrs來獲取播放器按鈕圖片
在attrs.xml中新建如下:
1
2
3
4
|
< declare-styleable name = "circle_progress_image_attrs" > < attr name = "play_image" format = "reference" /> < attr name = "stop_image" format = "reference" /> </ declare-styleable > |
然后在xml布局的自定義View中加入就能獲取圖片的id值了
1
2
|
circle:play_image= "@mipmap/play_button" circle:stop_image= "@mipmap/stop_button" |
然后我們重寫onMeasure()來測量圓形進度條繪制的位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
@Override protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) { super .onMeasure(widthMeasureSpec, heightMeasureSpec); width = measureWidth(widthMeasureSpec); height = measureWidth(heightMeasureSpec); mRectF.left = width / 2 - drawBitmapPlay.getWidth() / 2 ; mRectF.top = height / 2 - drawBitmapPlay.getHeight() / 2 ; mRectF.right = width / 2 + drawBitmapPlay.getWidth() / 2 ; mRectF.bottom = height / 2 + drawBitmapPlay.getHeight() / 2 ; } public int measureWidth( int measureSpec) { int result = 0 ; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); if (specMode == MeasureSpec.EXACTLY) { result = specSize; } else { result = 200 ; if (specMode == MeasureSpec.AT_MOST) { result = Math.min(specSize, result); } } return result; } |
獲取播放器按鈕圖片的大小后,計算出進度條的相應的坐標放入RectF對象中,RectF對象是用來表示坐標系中的一塊矩形區域,用于在特定的位置畫圖
然后我們就可以通過重寫onDraw()方法來繪制View了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@Override protected void onDraw(Canvas canvas) { super .onDraw(canvas); canvas.drawColor(Color.TRANSPARENT); //畫圓 mPaint.setColor(ContextCompat.getColor(context, R.color.orange)); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(mCircleStoreWidth); // canvas.drawArc(mRectF, -90, 360, false, mPaint); mPaint.setColor(ContextCompat.getColor(context, R.color.gray)); canvas.drawArc(mRectF, - 90 , (( float ) mProcessValue / mMaxProcessValue) * 360 , false , mPaint); Log.d(TAG, (( float ) mProcessValue / mMaxProcessValue) * 360 + "" ); float imageLeft = width / 2 - drawBitmapPlay.getWidth() / 2 ; float imageTop = height / 2 - drawBitmapPlay.getHeight() / 2 ; if (isPlay) { canvas.drawBitmap(drawBitmapStop, imageLeft, imageTop, mPaint); } else { canvas.drawBitmap(drawBitmapPlay, imageLeft, imageTop, mPaint); } } |
要點其實就是canvas.drawArc()方法在RecfF的位置里畫弧形,通過音頻播放的開始時間/總時間*360來計算出弧度
要注意的是每次調用onDraw()方法的時候都需要先將canvas畫透明色來起到清屏的作用
通過handler來每150毫秒刷新一次界面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
private Handler handler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { case 1 : //定時更新界面 if (isPlay) { mProcessValue += 150 ; if (mProcessValue == mMaxProcessValue) { isPlay = false ; } invalidate(); Message message = handler.obtainMessage( 1 ); handler.sendMessageDelayed(message, 150 ); } } super .handleMessage(msg); } }; |
最后是一些包裝方法,很簡單不仔細介紹了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
public void play() { isPlay = true ; Message message = handler.obtainMessage( 1 ); handler.sendMessageDelayed(message, 150 ); } public void setDuration( int duration) { this .mMaxProcessValue = duration; } public void clearDuration() { this .mMaxProcessValue = 0 ; this .mProcessValue = 0 ; } public void pause() { isPlay = false ; invalidate(); } public void stop() { isPlay = false ; this .mMaxProcessValue = 0 ; this .mProcessValue = 0 ; invalidate(); } |
音頻播放的邏輯實現部分因為不屬于自定義view因此可以自行參考demo
代碼示例: CustomViewSamples
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://blog.csdn.net/lj402159806/article/details/55803967