从无到有打造一个炫酷的进度条效果

SpecialProgressBar

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

SpecialProgressBar
SpecialProgressBar

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

实现思路

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

主要分五点来进行分析:

一、利用值动画变换数值然后invalidate刷新界面,形成动画效果。
二、在不同的临界值切换不同的状态
三、利用PathMeasure与Path来实现进度效果。
四、利用阻尼动画实现进度条回弹效果。
五、利用Camera和Matrix(如果不了解可以看我上一篇文章:Android中利用Camera与Matrix实现3D效果详解)实现进度框翻转效果。

一、利用值动画变换数值然后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方法进行旋转,具体可以看我上篇文章:Android中利用Camera与Matrix实现3D效果详解

看下具体代码:

                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/zhangke3016/SpecialProgressBar】如果喜欢,欢迎star、fork。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,468评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,019评论 4 62
  • 直播营销是从视频营销发展起来的,视频营销以其图文并茂的特点,使广告更生动,更能吸引读者眼球,这是商家非常喜欢的一种...
    铁牛哥阅读 4,060评论 0 1
  • 望离眷阅读 133评论 0 0
  • 在上一篇文章中我们谈到了此次智能革命对几乎各个行业的影响,那么为什么这一次的技术革命会有如此巨大的影响呢? 如果让...
    不知所然并卵阅读 222评论 0 2