自定义View:播放、暂停按钮优雅的过渡

TicktockMusic 音乐播放器项目相关文章汇总:

最近想写个音乐播放器,偶然看到轻听这款播放器的播放和暂停按钮,在切换过程中的动画很是吸引我。本着造轮子(其实是 github 上边没找到)的想法,就花了点时间撸出来了这个效果。

效果就是下边这个样子:

效果图

下边说下实现方法,中间也踩了一些坑。

测量及初始化

首先要确实View的宽高,在这里由于是圆形按钮,所以设置宽高相等,onMeasure()方法中设置下即可:


        mWidth = MeasureSpec.getSize(widthMeasureSpec);
        mHeight = MeasureSpec.getSize(heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        switch (widthMode) {
            case MeasureSpec.EXACTLY:
                mWidth = mHeight = Math.min(mWidth, mHeight);
                setMeasuredDimension(mWidth, mHeight);
                break;
            case MeasureSpec.AT_MOST:
                float density = getResources().getDisplayMetrics().density;
                mWidth = mHeight = (int) (50 * density); //默认50dp
                setMeasuredDimension(mWidth, mHeight);
                break;
        }

然后画出底部的圆形


 canvas.drawCircle(mWidth / 2, mHeight / 2, mRadius, mPaint);

计算Path

1、初始化完毕后,怎么实现两个竖条到一个三角形的过渡呢?这里首先想到的就是自定义 View 常用的 drawPath 方法,抛开动画不谈,整个 View 变化过程其实就是两个矩形变成两个直角三角形的过程。

就是这个样子。知道大体的思路,怎么搞呢,当然是开车了。

就是 canvas.drawPath();

首先计算暂停时两个矩形的各个坐标位置:


        float distance = mGapWidth;  //暂停时左右两边矩形距离
        float barWidth = mRectWidth / 2 - distance / 2;     //一个矩形的宽度
        float leftLeftTop = barWidth;       //左边矩形左上角

        float rightLeftTop = barWidth + distance;       //右边矩形左上角
        float rightRightTop = 2 * barWidth + distance;  //右边矩形右上角
        float rightRightBottom = rightRightTop; //右边矩形右下角

bottom 的话直接加上矩形的高度即可。


            mLeftPath.moveTo(0, 0);
            mLeftPath.lineTo(leftLeftTop, mRectHeight);
            mLeftPath.lineTo(barWidth, mRectHeight);
            mLeftPath.lineTo(barWidth, 0);
            mLeftPath.close();

            mRightPath.moveTo(rightLeftTop, 0);
            mRightPath.lineTo(rightLeftTop, mRectHeight);
            mRightPath.lineTo(rightRightBottom, mRectHeight);
            mRightPath.lineTo(rightRightTop, 0);
            mRightPath.close();

这样两个竖条就出来了。

2、在一开始写的时候就写了这么多计算的方法,但是这时候矩形的边角会超出 View 的范围,所以后来计算了一波位置:

如上图所示,这样就需要再更改一些参数:

首先定义出来这个矩形,计算下宽高:


        float space = (float) (mRadius / Math.sqrt(2)); 
        mRectLT = (int) (mRadius - space);
        int rectRB = (int) (mRadius + space);
        mRect.top = mRectLT;
        mRect.bottom = rectRB;
        mRect.left = mRectLT;
        mRect.right = rectRB;


然后只用在 确定 path 的路线时更改下坐标就可以了:


            mLeftPath.moveTo(mRectLT, mRectLT);
            mLeftPath.lineTo(leftLeftTop + mRectLT, mRectHeight + mRectLT);
            mLeftPath.lineTo(barWidth + mRectLT, mRectHeight + mRectLT);
            mLeftPath.lineTo(barWidth + mRectLT, mRectLT);
            mLeftPath.close();

            mRightPath.moveTo(rightLeftTop + mRectLT, mRectLT);
            mRightPath.lineTo(rightLeftTop + mRectLT, mRectHeight + mRectLT);
            mRightPath.lineTo(rightRightBottom + mRectLT, mRectHeight + mRectLT);
            mRightPath.lineTo(rightRightTop + mRectLT, mRectLT);
            mRightPath.close();

这时候画出来两个 Path,暂停按钮就完美的呈现了:


        canvas.drawPath(mLeftPath, mPaint);
        canvas.drawPath(mRightPath, mPaint);

如下图这样:

动画实现

画完暂停按钮后,怎么让他动画变成三角形呢?一开始我想根据一些宽高的属性来指定动画的变化值,然后更新过程中再画出来,但是计算过程中发现涉及动画的矩形宽度都是从原始的大小到0过渡的,那统一的使用一个参数确定会不会更好点呢?当然会了,从1倍到0变化即可。

这时候就可以设置动画属性了:


        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0 , 1);
        valueAnimator.setDuration(200);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mProgress = (float) animation.getAnimatedValue();
                invalidate();
            }
        });

然后根据 progress 在更新View的过程中来更改矩形的宽高值:


        float distance = mGapWidth * (1 - mProgress);  //暂停时左右两边矩形距离
        float barWidth = mRectWidth / 2 - distance / 2;     //一个矩形的宽度
        float leftLeftTop = barWidth * mProgress;       //左边矩形左上角

        float rightLeftTop = barWidth + distance;       //右边矩形左上角
        float rightRightTop = 2 * barWidth + distance;  //右边矩形右上角
        float rightRightBottom = rightRightTop - barWidth * mProgress; //右边矩形右下角

这样便可以实现两个矩形到三角形的过渡了,执行动画结束后便是这个样子:

两个矩形变成三角形之后,只需要画布旋转一下,两个暂停按钮到播放按钮的动画已经可以执行了:


canvas.rotate(rotation, mWidth / 2f, mHeight / 2f);

到这里基本上已经结束了,但是写完使用的时候总觉得位置有点不对劲,后来发现确实有问题:

计算过程.png

如图所示,旋转过后 A 和 C 本来是紧靠着圆周的,而 B 距离圆周还有一定的距离。所以需要将其位移 x 的距离,让 OC 的长度等于 BO 的长度。此时圆心O也是三角形的外心。那么此时可以计算出OF的距离,公式如下:

√(( r / √2 ) ^ 2 + OF ^ 2) = √2 * r - OF

得出 OF 的长度为: 3 * √2 * r / 8

那么原矩形宽度的一半减去 OF 的值即为右移的距离,计算可得,右移的距离为 √2 * r / 8 用 Java 表示即

radius * Math.sqrt(2) / 8f 

换算为矩形的高度即

mRectHeight / 8f

然后在画布位移一下即可:


canvas.translate((float) (mRectHeight / 8f * mProgress), 0);

总结

上边几个步骤写完,整体效果已经实现了。后来又设置了一系列自定义的参数方便使用:


    <declare-styleable name="PlayPauseView">
        <attr name="bg_color" format="color"/>
        <attr name="btn_color" format="color"/>
        <attr name="gap_width" format="float"/>
        <attr name="space_padding" format="float"/>
        <attr name="anim_duration" format="integer"/>
        <attr name="anim_direction">
            <enum name="positive" value="1"/>
            <enum name="negative" value="2"/>
        </attr>
    </declare-styleable>

所有代码都已经上传到 我的Github 上边了,点击可查看,希望提出问题相互讨论,随便给个 Star 再好不过了。
有问题交流可加QQ群 661614986 ,欢迎讨论。

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

推荐阅读更多精彩内容