Android實現絢麗的自定義進度條

前言

進度條是在Android項目中很常用的組件之一,如果想要理解它是怎麼實現的,首先還需要瞭解自定義view和Android坐標系相關的知識,下面我來詳細地介紹一下自定義進度條的實現過程。

本項目源碼:https://gitee.com/tu_erhongjiang/android-progress-bar

效果圖

實現步驟

1.繪制背景圓形矩形

首先要畫出一個圓形矩形,RectF裡面傳遞的是矩形左上角和右下角的xy坐標,這是用來確定矩形的位置和大小,然後在矩形內部畫出一個圓形矩形。

核心代碼:canvas.drawRoundRect()

private void drawBackground(Canvas canvas){
   //圓角矩形
    RectF rectF = new RectF(padding, padding, mWidth - padding, mHeight - padding);
    canvas.drawRoundRect(rectF, round, round, mPaintRoundRect);
}

2.繪制進度

其實裡面的進度條也是圓形矩形,隻不過進度條的畫筆是實心的。內部進度條矩形的大小需要略小於外面的矩形,這樣就可以實現上面的這種效果。如果進度條矩形大於或等於背景矩形大小的話那就完全壓住背景中的邊框,顯示出來的隻是一個沒有邊框的進度條,所以這裡需要減掉strokeWidth。

private void drawProgress(Canvas canvas){
        if (process!=0){
            RectF rectProgress = new RectF(padding + strokeWidth, padding + strokeWidth, process, mHeight - padding - strokeWidth);//內部進度條
            canvas.drawRoundRect(rectProgress, round, round, mPaint);
        }
    }

3.繪制文字

下面來看看怎麼居中文字:

getWidth() / 2 得到的結果是中間位置的x坐標,但是從這裡開始繪制文字的話不能實現居中的效果。所以還需要計算出文字的長度,然後把文字整體左移。mTxtWidth / 2 是文字的中心位置,也就是說文字的中心位置移到矩形的中心位置就可以實現居中的效果。

    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坐標
        int y = getHeight() / 2 + mTxtHeight / 4; //文字在畫佈中的y坐標
        if (percent < 100) {
            canvas.drawText(percent + "%", x, y, mPaintText);
        } else {
            canvas.drawText(finishedText, x, y, mPaintText);
        }
    }

4.加入動畫 

最後就是加入動畫效果瞭,讓進度條動起來。我這裡用到的是屬性動畫中的valueAnimator。這種動畫不能直接修改view,它是類似於timer,需要我們傳遞一個數值范圍和執行時間。比如說3秒內從1加到100。然後在接口回調時拿到當前的進度,執行view的invalidate()方法,刷新UI。

 //屬性動畫
    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);//設置畫筆樣式
        mPaintRoundRect.setStrokeWidth(strokeWidth);//設置畫筆寬度
 
        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;
        }
        //設置控件實際大小
        round = mHeight / 2;//圓角半徑
        setMeasuredDimension(mWidth, mHeight);
    }
 
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawBackground(canvas);//繪制背景矩形
        drawProgress(canvas);//繪制進度
        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);//內部進度條
            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坐標
        int y = getHeight() / 2 + mTxtHeight / 4; //文字在畫佈中的y坐標
        if (percent < 100) {
            canvas.drawText(percent + "%", x, y, mPaintText);
        } else {
            canvas.drawText(finishedText, x, y, mPaintText);
        }
    }
 
    //屬性動畫
    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());
    }
 
    //進度條默認高度,未設置高度時使用
    private int defaultHeight() {
        float scale = getContext().getResources().getDisplayMetrics().density;
        return (int) (20 * scale + 0.5f * (20 >= 0 ? 1 : -1));
    }
 
}

以上就是橫向進度條的實現步驟,整體來說還是比較簡單的,如果你對Android坐標系和canvas比較熟悉的話自定義view實現起來還是很容易的。

到此這篇關於Android實現絢麗的自定義進度條的文章就介紹到這瞭,更多相關Android自定義進度條內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: