2

SpecialProgressBar

今天这篇文章要介绍的是一个酷炫的进度条的设计和实现,在进度的文字内容、颜色以及切换的图片等都可以自由设置。我们先看下效果 (创意受Dribbble的启发):

SpecialProgressBar

SpecialProgressBar

整体效果还是不错的吧,哈哈,我自己还是比较满意的~项目地址已上传至 github ,欢迎star、fork。那么下面我们就开始从无到有实现一下这个酷炫的进度效果吧。
项目地址SpecialProgressBar

实现思路

仔细观察下这个效果,它有不同的动态效果和不同的进度状态组成,那么实现的思路就用效果切换的不同状态来进行切换绘制,对应数值的变化用到值动画、Path、贝塞尔曲线、Camera与Matrix等相关工具,因为涉及的地方比较多,这里我就主要说下大体的实现思路以及相关注意点。

主要分五点来进行分析:

一、利用值动画变换数值然后invalidate刷新界面,形成动画效果。
二、在不同的临界值切换不同的状态
三、利用PathMeasure与Path来实现进度效果。
四、利用阻尼动画实现进度条回弹效果。
五、利用Camera和Matrix实现进度框翻转效果。

一、利用值动画变换数值然后invalidate刷新界面,形成动画效果

对位置、大小、颜色的切换主要使用的ValueAnimator来进行变化,看下代码片段:

 ValueAnimator va = ValueAnimator.ofInt((int)(Math.min(getWidth(), getHeight())- mBgPaint.getStrokeWidth()*2)/2,(int) mBgPaint.getStrokeWidth());
            va.setInterpolator(new AnticipateInterpolator());
            va.setDuration(800);
            va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    int value = (Integer) animation.getAnimatedValue();
                    radiu = value;
                    center_scaleX = (1 - animation.getAnimatedFraction());
                    center_scaleY = (1 - animation.getAnimatedFraction());
                    invalidate();
                }
            });
            va.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {
                }
                @Override
                public void onAnimationEnd(Animator animation) {
                    state = STATE_READY_CHANGEING;//准备阶段
                    mBgPaint.setStyle(Paint.Style.STROKE);
                    mBgPaint.setColor(Color.BLACK);
                    changeStateReadyChanging();
                }
                @Override
                public void onAnimationCancel(Animator animation) {
                }
                @Override
                public void onAnimationRepeat(Animator animation) {
                }
            });
            va.start();

通过不同Interpolator插值器实现不同的运动效果,在AnimatorUpdateListener中改变数值,然后调用invalidat()方法,之后onDraw()方法会被调用,我们改变的数值在界面是就可以看到产生的动态效果了。

二、在不同的临界值切换不同的状态

考虑到动画中涉及的动画效果还是比较多的,可以用不同的状态表示不同的动画区间,在动画结束时切换不同的状态,然后在invalidate方法中根据不同的状态进行绘制,我们来看下吧:

    private static final int STATE_READY = 0;
    private static final int STATE_READY_CHANGEING = 1;
    private static final int STATE_READYING = 2;
    private static final int STATE_ERROR = 3;
    private static final int STATE_STARTING = 4;
    private static final int STATE_SUCCESS = 5;
    private static final int STATE_BACK = 6;
    private static final int STATE_BACK_HOME = 7;
    private static final int DONE = 8;

这里定义了九种状态,代表不同的动画效果区间,根据这些状态来进行动态切换绘制:

switch (state) {
            case STATE_BACK_HOME:
            case STATE_READY:
                p.reset();
                mBgPaint.setStyle(Paint.Style.FILL);

                p.addCircle(getWidth() / 2, getHeight() / 2, radiu, Path.Direction.CCW);
                canvas.drawPath(p, mBgPaint);
                matrix.reset();
                matrix.setScale(center_scaleX, center_scaleY);
                matrix.preTranslate(0,0);
                matrix.postTranslate(getWidth() / 2 - downloadBitmap.getWidth() / 2*Math.max(center_scaleX,center_scaleY), getHeight() / 2 - downloadBitmap.getHeight() / 2*Math.max(center_scaleX,center_scaleY));

                canvas.drawBitmap(downloadBitmap, matrix, mBgPaint);
                break;
            case STATE_READY_CHANGEING:
                p.reset();
                p.moveTo(startX, startY);
                p.lineTo(endX, endY);
                canvas.drawPath(p, mBgPaint);
                break;
                
                ...
          }

这里的状态切换可以说是整个动画切换的核心,通过对不同状态的切换,然后对应切换不同的值动画,实现整个效果的动态衔接。

三、利用PathMeasure与Path来实现进度效果

PathMeasure作为一个辅助工具,在对Path路径进行处理时是很方便的,我们来看下它吧:

setPath(Path path, boolean forceClosed)关联一个Path
isClosed() 是否闭合
getLength() 获取Path的长度
nextContour() 跳转到下一个轮廓
getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)截取路径片段
getPosTan(float distance, float[] pos, float[] tan)获取指定长度的位置坐标及该点切线值
getMatrix(float distance, Matrix matrix, int flags)获取指定长度的位置坐标及该点Matrix

我们这里重点关注getSegment和getPosTan方法,

getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo),相关参数:startD 开始截取位置距离 Path 起点的长度,stopD 结束截取位置距离 Path 起点的长度,dst 截取的 Path 将会添加到 dst 中,startWithMoveTo 起始点是否使用 moveTo。

在进度条变化的时候我们就使用这个方法动态的截取从开始位置到当前位置的Path值,截取成功dst中就用截取路径的值,然后调用drawPath方法绘制出进度效果。

需要注意的是:在安卓4.4或者之前的版本,在默认开启硬件加速的情况下,更改 dst 的内容后可能绘制会出现问题,请关闭硬件加速或者给 dst 添加一个单个操作,例如: dst.rLineTo(0, 0)。

getPosTan (float distance, float[] pos, float[] tan)
相关参数:
distance 距离 Path 起点的长度
pos 该点的坐标值
tan 该点的正切值

这里我们用这个方法来获取当前点的坐标值,如果获取成功,pos中就有坐标值了,通过这个坐标值来动态改变进度框和进度文字的位置。

四、利用阻尼动画实现进度条回弹效果

开始进度前进度条有个回弹的效果,这里我们使用的阻尼效果,主要用设置插值器动态改变二阶贝塞尔曲线的定点,定点位置的改变,形成整个路径效果的改变。
阻尼插值器参考网上的实现,我们看下主要实现:

public DampingInterpolator(int count, float overshoot) {
        setOverShootCount(count);
        setOverShootPercent(overshoot);
    }

    public void setOverShootCount(int count) {
        mCount = Math.max(1, count);
        mRegion = (float) (Math.PI * 2 * (mCount - 1) + Math.PI / 2 * 3);
        mOvershootModulus = (float) Math.pow(mOvershootPercent, mRegion
                / Math.PI);
    }

    public void setOverShootPercent(float overshoot) {
        mOvershootPercent = Math.max(0, Math.min(1, overshoot));
       /*
        * 当 t * mRegion = Math.PI 的时候,达到第一次过冲的峰值, 则 t = Math.PI / mRegion 。 且此时
        * mOvershootModulus^t = mOvershootPercent , 所以 mOvershootModulus =
        * Math.pow(mOvershootPercent, 1 / t) , 即 mOvershootModulus =
        * Math.pow(mOvershootPercent, mRegion / Math.PI) 。
        */
        mOvershootModulus = (float) Math.pow(mOvershootPercent, mRegion
                / Math.PI);
    }
     @Override
    public float getInterpolation(float t) {
        if (t <= 0) {
            return 0;
        }
        if (t >= 1) {
            return 1;
        }
        return (float) (1 - Math.pow(mOvershootModulus, t)
                * Math.cos(mRegion * t));
    }

将阻尼插值器设置给我们要开启的值动画,改变二阶贝塞尔曲线的定点,定点的来回回弹,最终形成曲线的来回回弹。

五、利用Camera和Matrix实现进度框翻转效果

在失败和成功时,进度框有个沿X轴和沿Y轴旋转的效果,如果这里单单使用matrix不能实现效果,仅仅是在平面沿Z轴旋转的。为了实现整个效果,我们使用Camera和Matrix来进行实现,调用camera的roateX和roateY方法进行旋转。

看下具体代码:

                camera.save();
                camera.rotateY(rotateY);
                camera.getMatrix(cameraMatrix);
                camera.restore();

                cameraMatrix.preTranslate(0, -loadingBitmap.getHeight() / 2);
                cameraMatrix.postTranslate(POS[0], POS[1] - loadingBitmap.getHeight() / 2);
                canvas.drawBitmap(loadingBitmap, cameraMatrix, mBgPaint);

我们在调用rotateY方法后获取到Matrix,然后调用canvas的drawBitmap方法来动态改变进度框的位置和文字的位置,一个动态效果就出来啦~

这里主要把主要的难点和主题思路缕了一下,如果要关注具体细节,可查看源码。
【github地址:https://github.com/zhangke301...如果喜欢,欢迎star、fork。


zhangke3016
53 声望2 粉丝

热爱编程,乐于分享,不断学习。一枚android开发攻城狮。