Android實(shí)現(xiàn)絢麗的自定義進(jìn)度條
前言
進(jìn)度條是在Android項(xiàng)目中很常用的組件之一,如果想要理解它是怎么實(shí)現(xiàn)的,首先還需要了解自定義view和Android坐標(biāo)系相關(guān)的知識(shí),下面我來詳細(xì)地介紹一下自定義進(jìn)度條的實(shí)現(xiàn)過程。
本項(xiàng)目源碼:https://gitee.com/tu_erhongjiang/android-progress-bar
效果圖
實(shí)現(xiàn)步驟
1.繪制背景圓形矩形
首先要畫出一個(gè)圓形矩形,RectF里面?zhèn)鬟f的是矩形左上角和右下角的xy坐標(biāo),這是用來確定矩形的位置和大小,然后在矩形內(nèi)部畫出一個(gè)圓形矩形。
核心代碼:canvas.drawRoundRect()
private void drawBackground(Canvas canvas){ //圓角矩形 RectF rectF = new RectF(padding, padding, mWidth - padding, mHeight - padding); canvas.drawRoundRect(rectF, round, round, mPaintRoundRect); }
2.繪制進(jìn)度
其實(shí)里面的進(jìn)度條也是圓形矩形,只不過進(jìn)度條的畫筆是實(shí)心的。內(nèi)部進(jìn)度條矩形的大小需要略小于外面的矩形,這樣就可以實(shí)現(xiàn)上面的這種效果。如果進(jìn)度條矩形大于或等于背景矩形大小的話那就完全壓住背景中的邊框,顯示出來的只是一個(gè)沒有邊框的進(jìn)度條,所以這里需要減掉strokeWidth。
private void drawProgress(Canvas canvas){ if (process!=0){ RectF rectProgress = new RectF(padding + strokeWidth, padding + strokeWidth, process, mHeight - padding - strokeWidth);//內(nèi)部進(jìn)度條 canvas.drawRoundRect(rectProgress, round, round, mPaint); } }
3.繪制文字
下面來看看怎么居中文字:
getWidth() / 2 得到的結(jié)果是中間位置的x坐標(biāo),但是從這里開始繪制文字的話不能實(shí)現(xiàn)居中的效果。所以還需要計(jì)算出文字的長度,然后把文字整體左移。mTxtWidth / 2 是文字的中心位置,也就是說文字的中心位置移到矩形的中心位置就可以實(shí)現(xiàn)居中的效果。
private void updateText(Canvas canvas) { String finishedText = getResources().getString(R.string.finished); String defaultText = getResources().getString(R.string.defaultText); int percent = (int) (process / (mWidth - padding - strokeWidth) * 100); Paint.FontMetrics fm = mPaintText.getFontMetrics(); int mTxtWidth = (int) mPaintText.measureText(finishedText, 0, defaultText.length()); int mTxtHeight = (int) Math.ceil(fm.descent - fm.ascent); int x = getWidth() / 2 - mTxtWidth / 2; //文字在畫布中的x坐標(biāo) int y = getHeight() / 2 + mTxtHeight / 4; //文字在畫布中的y坐標(biāo) if (percent < 100) { canvas.drawText(percent + "%", x, y, mPaintText); } else { canvas.drawText(finishedText, x, y, mPaintText); } }
4.加入動(dòng)畫
最后就是加入動(dòng)畫效果了,讓進(jìn)度條動(dòng)起來。我這里用到的是屬性動(dòng)畫中的valueAnimator。這種動(dòng)畫不能直接修改view,它是類似于timer,需要我們傳遞一個(gè)數(shù)值范圍和執(zhí)行時(shí)間。比如說3秒內(nèi)從1加到100。然后在接口回調(diào)時(shí)拿到當(dāng)前的進(jìn)度,執(zhí)行view的invalidate()方法,刷新UI。
//屬性動(dòng)畫 public void start() { ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, mWidth - padding - strokeWidth); valueAnimator.setDuration(duration); valueAnimator.setInterpolator(new DecelerateInterpolator()); valueAnimator.addUpdateListener(animation -> { process = (float) animation.getAnimatedValue(); invalidate(); }); valueAnimator.start(); }
完整代碼
package com.example.floatingwindow.widget; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.RectF; import android.util.AttributeSet; import android.util.TypedValue; import android.view.View; import android.view.animation.DecelerateInterpolator; import androidx.annotation.Nullable; import com.example.floatingwindow.R; public class HorizontalProgressView extends View { private Paint mPaint; private Paint mPaintRoundRect; private Paint mPaintText; private int mWidth; private int mHeight; private int padding = 5; private int strokeWidth = 8; private int textSize = 15; private long duration = 3500; private int round; private float process; public HorizontalProgressView(Context context) { super(context); init(); } public HorizontalProgressView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); } public HorizontalProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } //初始化畫筆 private void init(){ mPaintRoundRect = new Paint();//圓角矩形 mPaintRoundRect.setColor(getResources().getColor(R.color.back));//圓角矩形顏色 mPaintRoundRect.setAntiAlias(true);// 抗鋸齒效果 mPaintRoundRect.setStyle(Paint.Style.STROKE);//設(shè)置畫筆樣式 mPaintRoundRect.setStrokeWidth(strokeWidth);//設(shè)置畫筆寬度 mPaint = new Paint(); mPaint.setColor(getResources().getColor(R.color.inner)); mPaint.setStyle(Paint.Style.FILL_AND_STROKE); mPaint.setAntiAlias(true); mPaint.setStrokeWidth(strokeWidth); mPaintText = new Paint(); mPaintText.setAntiAlias(true); mPaintText.setStyle(Paint.Style.FILL); mPaintText.setColor(getResources().getColor(R.color.back)); mPaintText.setTextSize(sp2px(textSize)); } public void setPadding(int padding) { this.padding = padding; } public void setStrokeWidth(int strokeWidth) { this.strokeWidth = strokeWidth; } public void setTextSize(int textSize) { this.textSize = textSize; } public void setDuration(long duration) { this.duration = duration; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); //MeasureSpec.EXACTLY,精確尺寸 if (widthSpecMode == MeasureSpec.EXACTLY || widthSpecMode == MeasureSpec.AT_MOST) { mWidth = widthSpecSize; } else { mWidth = 0; } //MeasureSpec.AT_MOST,最大尺寸,只要不超過父控件允許的最大尺寸即可,MeasureSpec.UNSPECIFIED未指定尺寸 if (heightSpecMode == MeasureSpec.AT_MOST || heightSpecMode == MeasureSpec.UNSPECIFIED) { mHeight = defaultHeight(); } else { mHeight = heightSpecSize; } //設(shè)置控件實(shí)際大小 round = mHeight / 2;//圓角半徑 setMeasuredDimension(mWidth, mHeight); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); drawBackground(canvas);//繪制背景矩形 drawProgress(canvas);//繪制進(jìn)度 updateText(canvas);//處理文字 } private void drawBackground(Canvas canvas){ RectF rectF = new RectF(padding, padding, mWidth - padding, mHeight - padding);//圓角矩形 canvas.drawRoundRect(rectF, round, round, mPaintRoundRect); } private void drawProgress(Canvas canvas){ if (process!=0){ RectF rectProgress = new RectF(padding + strokeWidth, padding + strokeWidth, process, mHeight - padding - strokeWidth);//內(nèi)部進(jìn)度條 canvas.drawRoundRect(rectProgress, round, round, mPaint); } } private void updateText(Canvas canvas) { String finishedText = getResources().getString(R.string.finished); String defaultText = getResources().getString(R.string.defaultText); int percent = (int) (process / (mWidth - padding - strokeWidth) * 100); Paint.FontMetrics fm = mPaintText.getFontMetrics(); int mTxtWidth = (int) mPaintText.measureText(finishedText, 0, defaultText.length()); int mTxtHeight = (int) Math.ceil(fm.descent - fm.ascent); int x = getWidth() / 2 - mTxtWidth / 2; //文字在畫布中的x坐標(biāo) int y = getHeight() / 2 + mTxtHeight / 4; //文字在畫布中的y坐標(biāo) if (percent < 100) { canvas.drawText(percent + "%", x, y, mPaintText); } else { canvas.drawText(finishedText, x, y, mPaintText); } } //屬性動(dòng)畫 public void start() { ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, mWidth - padding - strokeWidth); valueAnimator.setDuration(duration); valueAnimator.setInterpolator(new DecelerateInterpolator()); valueAnimator.addUpdateListener(animation -> { process = (float) animation.getAnimatedValue(); invalidate(); }); valueAnimator.start(); } private int sp2px(int sp) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics()); } //進(jìn)度條默認(rèn)高度,未設(shè)置高度時(shí)使用 private int defaultHeight() { float scale = getContext().getResources().getDisplayMetrics().density; return (int) (20 * scale + 0.5f * (20 >= 0 ? 1 : -1)); } }
以上就是橫向進(jìn)度條的實(shí)現(xiàn)步驟,整體來說還是比較簡單的,如果你對Android坐標(biāo)系和canvas比較熟悉的話自定義view實(shí)現(xiàn)起來還是很容易的。
到此這篇關(guān)于Android實(shí)現(xiàn)絢麗的自定義進(jìn)度條的文章就介紹到這了,更多相關(guān)Android自定義進(jìn)度條內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android實(shí)現(xiàn)錄音監(jiān)聽動(dòng)畫的示例代碼
在很多app種內(nèi)置了語音助手,也存在各種動(dòng)畫,這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)錄音監(jiān)聽動(dòng)畫的示例代碼,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-12-12在Android中動(dòng)態(tài)添加Panel框架的實(shí)現(xiàn)代碼
項(xiàng)目經(jīng)常會(huì)有這種需求,就是想動(dòng)態(tài)的在某個(gè)界面上添加一個(gè)Panel。比如,有一個(gè)按鈕,點(diǎn)擊后會(huì)顯示下拉菜單式的界面。這種需求,就屬于動(dòng)態(tài)添加一個(gè)Panel。需求多了,就要研究是否可以抽象出通用的框架代碼,以方便開發(fā),所以就有了以下內(nèi)容2013-05-05Convert WebP to PNG using java
本文主要介紹Convert WebP to PNG using java,這里對 WebP 做了詳細(xì)說明,并講解Linux 環(huán)境下WebP 轉(zhuǎn)png格式的示例,有興趣的小伙伴可以參考下2016-08-08AndroidStudio中重載方法@Override的使用詳解
這篇文章主要介紹了AndroidStudio中重載方法@Override的使用詳解,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-04-04Android開發(fā)之串口編程原理和實(shí)現(xiàn)方式
提到串口編程,就不得不提到JNI,不得不提到JavaAPI中的文件描述符類:FileDescriptor;下面我分別對JNI、FileDescriptor以及串口的一些知識(shí)點(diǎn)和實(shí)現(xiàn)的源碼進(jìn)行分析說明,感興趣的朋友可以了解下2013-01-01Android實(shí)現(xiàn)壓縮字符串的方法示例
最近在做Android開發(fā),遇到了需要壓縮字符串的功能,下面這篇文章主要給大家介紹了Android實(shí)現(xiàn)壓縮字符串的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-08-08詳解Android 7.0 Settings 加載選項(xiàng)
本篇文章主要介紹了Android 7.0 Settings 加載選項(xiàng),Android 7.0 Settings頂部多了一個(gè)建議選項(xiàng),多了個(gè)側(cè)邊欄,操作更加便捷了,有興趣的可以了解一下。2017-02-02ListView的Adapter使用 之 初學(xué)ArrayAdapter String
ListView是Android中經(jīng)常會(huì)使用的東西,綁定數(shù)據(jù)對于初學(xué)者來說,尤其是剛接觸編程的人來說,往往會(huì)覺得很難理解,我上大二的時(shí)候?qū)W的java,但是基本上相當(dāng)于沒有學(xué),什么都沒寫過,真正接觸編程就是開始上手學(xué)android,把這些記錄下來,自己可以回頭看下,也可以讓新手更好的理解2013-06-06android dialog背景模糊化效果實(shí)現(xiàn)方法
這篇文章主要為大家詳細(xì)介紹了android dialog背景模糊化效果的實(shí)現(xiàn)方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-04-04