Android實(shí)現(xiàn)絢麗的自定義進(jìn)度條
前言
進(jìn)度條是在Android項(xiàng)目中很常用的組件之一,如果想要理解它是怎么實(shí)現(xiàn)的,首先還需要了解自定義view和Android坐標(biāo)系相關(guān)的知識(shí),下面我來(lái)詳細(xì)地介紹一下自定義進(jìn)度條的實(shí)現(xiàn)過(guò)程。
本項(xiàng)目源碼:https://gitee.com/tu_erhongjiang/android-progress-bar
效果圖

實(shí)現(xiàn)步驟
1.繪制背景圓形矩形

首先要畫(huà)出一個(gè)圓形矩形,RectF里面?zhèn)鬟f的是矩形左上角和右下角的xy坐標(biāo),這是用來(lái)確定矩形的位置和大小,然后在矩形內(nèi)部畫(huà)出一個(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)度條也是圓形矩形,只不過(guò)進(jìn)度條的畫(huà)筆是實(shí)心的。內(nèi)部進(jìn)度條矩形的大小需要略小于外面的矩形,這樣就可以實(shí)現(xiàn)上面的這種效果。如果進(jìn)度條矩形大于或等于背景矩形大小的話那就完全壓住背景中的邊框,顯示出來(lái)的只是一個(gè)沒(méi)有邊框的進(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.繪制文字

下面來(lái)看看怎么居中文字:
getWidth() / 2 得到的結(jié)果是中間位置的x坐標(biāo),但是從這里開(kāi)始繪制文字的話不能實(shí)現(xiàn)居中的效果。所以還需要計(jì)算出文字的長(zhǎng)度,然后把文字整體左移。mTxtWidth / 2 是文字的中心位置,也就是說(shuō)文字的中心位置移到矩形的中心位置就可以實(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; //文字在畫(huà)布中的x坐標(biāo)
int y = getHeight() / 2 + mTxtHeight / 4; //文字在畫(huà)布中的y坐標(biāo)
if (percent < 100) {
canvas.drawText(percent + "%", x, y, mPaintText);
} else {
canvas.drawText(finishedText, x, y, mPaintText);
}
}
4.加入動(dòng)畫(huà)
最后就是加入動(dòng)畫(huà)效果了,讓進(jìn)度條動(dòng)起來(lái)。我這里用到的是屬性動(dòng)畫(huà)中的valueAnimator。這種動(dòng)畫(huà)不能直接修改view,它是類(lèi)似于timer,需要我們傳遞一個(gè)數(shù)值范圍和執(zhí)行時(shí)間。比如說(shuō)3秒內(nèi)從1加到100。然后在接口回調(diào)時(shí)拿到當(dāng)前的進(jìn)度,執(zhí)行view的invalidate()方法,刷新UI。
//屬性動(dòng)畫(huà)
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();
}
//初始化畫(huà)筆
private void init(){
mPaintRoundRect = new Paint();//圓角矩形
mPaintRoundRect.setColor(getResources().getColor(R.color.back));//圓角矩形顏色
mPaintRoundRect.setAntiAlias(true);// 抗鋸齒效果
mPaintRoundRect.setStyle(Paint.Style.STROKE);//設(shè)置畫(huà)筆樣式
mPaintRoundRect.setStrokeWidth(strokeWidth);//設(shè)置畫(huà)筆寬度
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,最大尺寸,只要不超過(guò)父控件允許的最大尺寸即可,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; //文字在畫(huà)布中的x坐標(biāo)
int y = getHeight() / 2 + mTxtHeight / 4; //文字在畫(huà)布中的y坐標(biāo)
if (percent < 100) {
canvas.drawText(percent + "%", x, y, mPaintText);
} else {
canvas.drawText(finishedText, x, y, mPaintText);
}
}
//屬性動(dòng)畫(huà)
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)步驟,整體來(lái)說(shuō)還是比較簡(jiǎn)單的,如果你對(duì)Android坐標(biāo)系和canvas比較熟悉的話自定義view實(shí)現(xiàn)起來(lái)還是很容易的。
到此這篇關(guān)于Android實(shí)現(xiàn)絢麗的自定義進(jìn)度條的文章就介紹到這了,更多相關(guān)Android自定義進(jìn)度條內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android Studio IDE升級(jí)4.1以后Start Failed
這篇文章主要介紹了Android Studio IDE升級(jí)4.1以后Start Failed,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
Kotlin之自定義 Live Templates詳解(模板代碼)
這篇文章主要介紹了Kotlin之自定義 Live Templates詳解(模板代碼),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-03-03
android防止提交事件時(shí)觸發(fā)多個(gè)表單中的按鈕
這篇文章主要介紹了android防止提交事件時(shí)觸發(fā)多個(gè)表單中的按鈕,2015-05-05
解析android res 運(yùn)行錯(cuò)誤的問(wèn)題
本篇文章是對(duì)android中res運(yùn)行錯(cuò)誤的問(wèn)題進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06
新版Android studio導(dǎo)入微信支付和支付寶官方Demo問(wèn)題解決大全
這篇文章主要為大家詳細(xì)介紹了新版Android studio導(dǎo)入微信支付和支付寶官方Demo問(wèn)題的解決大全,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-07-07
Android四大組件之Service服務(wù)詳細(xì)講解
Android的服務(wù)是開(kāi)發(fā)Android應(yīng)用程序的重要組成部分。不同于活動(dòng)Activity,服務(wù)是在后臺(tái)運(yùn)行,服務(wù)沒(méi)有接口,生命周期也與活動(dòng)Activity非常不同。通過(guò)使用服務(wù)我們可以實(shí)現(xiàn)一些后臺(tái)操作,比如想從遠(yuǎn)程服務(wù)器加載一個(gè)網(wǎng)頁(yè)等,下面來(lái)看看詳細(xì)內(nèi)容,需要的朋友可以參考下2022-07-07
android判斷動(dòng)畫(huà)已結(jié)束示例代碼
添加一個(gè)動(dòng)畫(huà)效果,發(fā)現(xiàn)動(dòng)畫(huà)沒(méi)執(zhí)行完 就直接跳轉(zhuǎn)或者finish掉,添加動(dòng)畫(huà)監(jiān)聽(tīng)事件即可,示例代碼如下2014-10-10
Android自定義控件之水平圓點(diǎn)加載進(jìn)度條
這篇文章主要為大家詳細(xì)介紹了Android自定義控件之水平圓點(diǎn)加載進(jìn)度條,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-06-06
Android Gradle Build Error:Some file crunching failed, see l
這篇文章主要介紹了Android Gradle Build Error:Some file crunching failed, see logs for details的快速解決方法的相關(guān)資料,需要的朋友可以參考下2016-10-10

