Android带动画进度条简单实现

一、前言

最近在使用一个打卡软件时,发现它使用的打卡记录的进度条效果挺不错的,进度条会从0走到当前的完成进度,这中间有一个平缓的动画效果。然后,试着自己也简单的实现了一个,先上效果图。
在这里插入图片描述
实现主要分为三部分:

  1. 绘制边框,该部分是固定
  2. 绘制边框内的进度值,从0增加到指定值
  3. 为进度值的改变添加动画效果

二、代码实现

1、自定义属性

在resouces\values路径下新建attrs.xml,在这里我们只是简单的添加了控件宽度、控件高度、边框距内部填充距离这三个属性。

attrs.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="MyProgressbar">

        <attr name="progressbar_width" format="dimension"/>
        <attr name="progressbar_height" format="dimension"/>
        <attr name="progressbar_ProgressToFrameWidth" format="dimension"/>

    </declare-styleable>

</resources>

2、实现MyProgressbar类

(1)新建MyProgressbar类继承View。在构造函数中获取自定义控件属性值。

public MyProgressbar(Context context, AttributeSet attrs) {
    super(context, attrs);
    TypedArray array = getContext().obtainStyledAttributes(attrs, R.styleable.MyProgressbar);
    mProgressbarWidth = (int)array.getDimension(R.styleable.MyProgressbar_progressbar_width, 100);
    mProgressbarHeight = (int)array.getDimension(R.styleable.MyProgressbar_progressbar_height, 10);
    mProgressToFrameWidth = array.getDimension(R.styleable.MyProgressbar_progressbar_ProgressToFrameWidth, 6f);
}

(2)重写onMeasure方法

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(mProgressbarWidth, mProgressbarHeight);
}

(3)重写onDraw方法
对控件的绘制逻辑都是在该方法中完成的。首先初始化画笔,将Style设置为非填充,并设置画笔宽度,用来实现边框效果。通过RectF设置边框范围,调用Canvas的drawRoundRect()方法进行绘制。该方法中间的两个参数代表四个圆角的x轴和y轴半径长度。这里需要注意,当画笔宽度大于1时,如果绘制的矩形是紧贴控件边框的,会发现边框外的一半不见了。需要将边框的绘制范围设置为控件范围往里缩半个画笔宽度,用来正好显示整个边框。

RectF frameRectF = new RectF(mPaintWidth / 2, mPaintWidth / 2, mProgressbarWidth - mPaintWidth / 2, mProgressbarHeight - mPaintWidth / 2);
canvas.drawRoundRect(frameRectF, 15, 15, mPaint);

接着来实现中间的进度填充部分,该部分为一个实心的圆角矩形。和边框的区别为绘制该部分时,画笔Style应为FILL填充。因为填充部分不需要体现出边框宽度的效果,重新设置画笔宽度为1即可。这样做的好处是减少计算量,不然还需要像上面计算边框范围一样,考虑画笔带来的影响。绘制这个实心的圆角矩形,其中left、top、bottom这三个属性值是固定的。随着动画效果,进度条在增长,也就是right属性值在变化。在初始时,进度从0开始,圆角矩形的长度为0,这时right值等于left值。随着传入进度值的改变,计算所占百分比,right值开始变化,通过不断地重绘,完成进度条改变的动画效果。

float percent = (float) mPercent / 100f;
RectF progressRectF = new RectF(mPaintWidth + mProgressToFrameWidth, mPaintWidth + mProgressToFrameWidth, (mPaintWidth + mProgressToFrameWidth) + percent * (mProgressbarWidth - 2 * mPaintWidth - 2 * mProgressToFrameWidth), mProgressbarHeight - mPaintWidth - mProgressToFrameWidth);
canvas.drawRoundRect(progressRectF, 15, 15, mPaint);

下图是对该控件各部分范围的一个直观图,为了可以更清晰的表现出来,图有些夸张,不太像进度条。其中图片边界为控件边界。红色边框外部紧贴控件边框,画笔位置为图中黑线部分。内部为填充为蓝色部分,对应画笔为图中粉色部分。

(4)定义setProgress()方法
该方法用来完成对传入进度值的动画添加及监听,并发出重绘界面的请求。这里用到了ValueAnimator类,该类用来对值做动画。在ofInt()方法中,设置初始值和结束值。为该动画添加addUpdateListener,完成对值变化的监听,getAnimatedValue()方法可以获取到实时的变化值,该值也正是我们所需要的。得到变化值后发出重绘界面的请求。

animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator valueAnimator) {
        mPercent = (int)valueAnimator.getAnimatedValue();
        invalidate();
    }
});

注意,外部传入的进度值可能并不是以100作为最大值的(比如一个星期有7天,过了3天,相当于过了3/7天,不是3/100天),需要在这里做一个转换。

int percent = progress * 100 / maxProgress;//得出当前progress占最大进度值百分比(0-100)
if (percent < 0) {
    percent = 0;
}
if (percent > 100) {
    percent = 100;
}

3、测试

最后,进行简单的测试。在MainActivity中添加该控件和一个Button。单击Button调用setProgress()方法即可。

progressbar = findViewById(R.id.my_progress);
Button start_Button = findViewById(R.id.start_button);
start_Button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        progressbar.setProgress(76, 100);
    }
});

三、源码

宇宙惯例,贴上源码,代码中有不妥之处欢迎大家指出。

MyProgressbar.java:

package com.example.progressbar;

import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateInterpolator;

public class MyProgressbar extends View {

    private Paint mPaint;//画笔

    private float mPaintWidth = 6f;//初始画笔宽度

    private int mProgressbarWidth;//控件外边框宽度

    private int mProgressbarHeight;//控件外边框高度

    private float mProgressToFrameWidth;//内部填充进度距内边框距离

    private int mPercent = 0;//已转化为0至100范围的当前进度,随动画时间改变而改变

    public MyProgressbar(Context context) {
        super(context);
    }

    @SuppressLint("Recycle")
    public MyProgressbar(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray array = getContext().obtainStyledAttributes(attrs, R.styleable.MyProgressbar);
        mProgressbarWidth = (int)array.getDimension(R.styleable.MyProgressbar_progressbar_width, 100);
        mProgressbarHeight = (int)array.getDimension(R.styleable.MyProgressbar_progressbar_height, 10);
        mProgressToFrameWidth = array.getDimension(R.styleable.MyProgressbar_progressbar_ProgressToFrameWidth, 6f);
    }

    public MyProgressbar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(mProgressbarWidth, mProgressbarHeight);
    }

    @Override
    @SuppressLint("DrawAllocation")
    protected void onDraw(Canvas canvas) {
        //绘制进度条外边框
        initPaint();
        RectF frameRectF = new RectF(mPaintWidth / 2, mPaintWidth / 2, mProgressbarWidth - mPaintWidth / 2, mProgressbarHeight - mPaintWidth / 2);
        canvas.drawRoundRect(frameRectF, 15, 15, mPaint);
        //填充内部进度
        mPaint.setPathEffect(null);
        mPaint.setColor(Color.BLUE);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setAntiAlias(true);
        //内部进度填充长度,随动画时间改变而改变
        float percent = (float) mPercent / 100f;
        RectF progressRectF = new RectF(mPaintWidth + mProgressToFrameWidth, mPaintWidth + mProgressToFrameWidth, (mPaintWidth + mProgressToFrameWidth) + percent * (mProgressbarWidth - 2 * mPaintWidth - 2 * mProgressToFrameWidth), mProgressbarHeight - mPaintWidth - mProgressToFrameWidth);
        canvas.drawRoundRect(progressRectF, 15, 15, mPaint);
    }

    private void initPaint(){
        mPaint = new Paint();
        mPaint.setColor(Color.BLACK);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(mPaintWidth);
    }

    public void setProgress(int progress, int maxProgress) {

        int percent = progress * 100 / maxProgress;//得出当前progress占最大进度值百分比(0-100)
        if (percent < 0) {
            percent = 0;
        }
        if (percent > 100) {
            percent = 100;
        }

        ValueAnimator animator = ValueAnimator.ofInt(0, percent);
        animator.setDuration(1000);
        animator.setInterpolator(new AccelerateInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                mPercent = (int)valueAnimator.getAnimatedValue();
                invalidate();
            }
        });
        animator.start();
    }

}

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <com.example.progressbar.MyProgressbar
        android:id="@+id/my_progress"
        android:layout_marginTop="50dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        app:progressbar_width="240dp"
        app:progressbar_height="45dp"/>

    <Button
        android:id="@+id/start_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp"
        android:layout_gravity="center"
        android:text="开始"/>

</LinearLayout>

MainActivity.java:

package com.example.progressbar;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    private MyProgressbar progressbar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        progressbar = findViewById(R.id.my_progress);
        Button start_Button = findViewById(R.id.start_button);
        start_Button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                progressbar.setProgress(76, 100);
            }
        });
    }
    
}

项目地址:Github

发布了10 篇原创文章 · 获赞 19 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/songkai0825/article/details/89367666