Android仿抖音加载框之两颗小球转动控件

Android仿抖音加载框之两颗小球转动控件

本篇文章已授权微信公众号 hongyangAndroid(鸿洋)独家发布。

效果图

安卓版抖音v2.5加载框:


抖音加载框

本控件效果图:


本控件

使用方法

源码地址:Android仿抖音加载框之两颗小球转动控件

1、xml引用:

         

2、java使用:

    @BindView(R.id.dy1)
    DYLoadingView dy1;

    @OnClick(R.id.b1)
    void start() {
        dy1.setXXXXX; //设置属性(可选)
        dy1.start(); //开始动画
    }

    @OnClick(R.id.b2)
    void stop() {
        dy1.stop(); //停止动画
    }

就酱。

可用属性

名称 对应xml属性 对应java方法 默认值
球1半径 radius1 setRadius() 6dp
球2半径 radius2 setRadius() 6dp
两球间隔 gap setRadius() 0.8dp
球1颜色 color1 setColors() 0XFFFF4040
球2颜色 color2 setColors() 0XFF00EEEE
叠加色 mixColor setColors() 0XFF000000
从右往左移动时小球最大缩放倍数 rtlScale setScales() 0.7f
从左往右移动时小球最大缩放倍数 ltrScale setScales() 1.3f
一次移动动画时长 duration setDuration() 350ms
一次移动动画后停顿时长 pauseDuration setDuration() 80ms
动画进度在[0,scaleStartFraction]期间,小球大小逐渐缩放 scaleStartFraction setStartEndFraction() 0.2f
动画进度在[scaleEndFraction,1]期间,小球大小逐渐恢复 scaleEndFraction setStartEndFraction() 0.8f

(rtl = right to left, ltr = left to right)

部分属性说明

  • color格式为32位ARGB
  • scaleStartFraction范围[0,0.5];scaleEndFraction范围[0.5,1]
  • 假设ltrScale = 1.3,scaleStartFraction = 0.2,scaleEndFraction = 0.8;那么实际效果就是一颗小球从左边开始向右移动期间,进度在0%--20%时半径逐渐从1倍放大到1.3倍,在20%--80%期间大小保持1.3倍,在80%--100%时半径逐渐从1.3倍恢复至1倍

实现思路

要让小球动,当然要有一个动画,通过动画来获得一个进度百分比fraction,然后小球在动画过程中的坐标、大小就可以通过这个值来计算:

    private void initAnim() {
        fraction = 0.0f;

        stop();

        anim = ValueAnimator.ofFloat(0.0f, 1.0f);
        anim.setDuration(duration);
        if (pauseDuration > 0) {
            anim.setStartDelay(pauseDuration);
            anim.setInterpolator(new AccelerateDecelerateInterpolator());
        } else {
            anim.setRepeatCount(ValueAnimator.INFINITE);
            anim.setRepeatMode(ValueAnimator.RESTART);
            anim.setInterpolator(new LinearInterpolator());
        }
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                fraction = animation.getAnimatedFraction();
                invalidate();
            }
        });
        anim.addListener(new AnimatorListenerAdapter() {

            @Override
            public void onAnimationStart(Animator animation) {
                isLtr = !isLtr;
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
                isLtr = !isLtr;
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                isAnimCanceled = true;
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                if (!isAnimCanceled) {
                    anim.start();
                }
            }
        });
    }

代码中看到,如果小球一次移动后不需要停顿(pauseDuration = 0),那么直接通过anim.setRepeatCount(ValueAnimator.INFINITE)让动画无限循环,否则的话就要通过anim.setStartDelay(pauseDuration)来设置停顿时间,然后在监听的onAnimationEnd里重启动画,以此实现每一次移动后小球能停顿一定时间。在onAnimationUpdate里,我们记录了当前动画百分比fraction,然后通过invalidate()重绘,在之后的onDraw里将通过该值画出小球。另外,每次动画开始时(或是重复时),会将isLtr取反,这个标志位的作用是标明当前哪颗球在【从左往右】移动,因为两颗球的颜色、初始半径是不一样的嘛,onDraw里画小球时是需要这个标志位协助的。

有了动画进度fraction和标志位isLtr后,就可以在onDraw里画出小球了。

首先要计算小球当前的坐标
y坐标永远是固定的,x坐标随着fraction的变化而变化。两颗球之间最远距离为球1半径+球2半径+两球间隔,即distance = gap + radius1 + radius2;,这个值就是两颗球的移动范围,由此可计算出,当前【从左往右】移动的小球的x坐标和当前【从右往左】移动的小球x坐标分别为:

float ltrX = getMeasuredWidth() / 2.0f - distance / 2.0f;
ltrX = ltrX + (distance * fraction);

float rtlX = getMeasuredWidth() / 2.0f + distance / 2.0f;
rtlX = rtlX - (distance * fraction);

接下来要计算小球当前的大小
小球的大小也是随着动画进度变化的,上面已经说明了scaleStartFractionscaleEndFraction属性的含义。
以当前小球【从左往右】移动为例,当动画进度为0时,小球大小为初始1倍大小;当动画进度到scaleStartFraction时,小球大小将缩放到ltrScale倍,当动画进度为[scaleStartFraction,scaleEndFraction]范围时,小球大小保持ltrScale倍,当动画进度到[scaleEndFraction,1]范围时,小球则从ltrScale倍逐渐恢复至1倍。

为了便于计算,首先将[0,scaleStartFraction]转换为[0,1.0]的真实百分比,根据y = kx + b(就这么个入门公式。。我都要在纸上算一遍T-T),可以得出:float scaleFraction = 1.0f / scaleStartFraction * fraction;,有了这个真实百分比,那么该区间里小球当前半径就好计算了:

ltrBallRadius = ltrInitRadius * (1 + (ltrScale - 1) * scaleFraction);
rtlBallRadius = rtlInitRadius * (1 + (rtlScale - 1) * scaleFraction);

应该好懂的吧。另外[scaleEndFraction,1]区间里小球当前半径计算思路是一样的。

最后就是要画出小球
这里主要是如何画出实现两球叠加的部分的颜色。
思路1:通过xfermode方式实现:

xfermode

从上图可以看出用Darken、Lighten、Screen模式可以做到让叠加处上色,但是颜色不能自定义。
思路2:通过path的OP操作实现(API19):

op

(图片摘自http://www.gcssloop.com/customview/Path_Over)



因此,可以先构造两个小球的Path,然后再将这两个Path进行INTERSECT操作,即可获得叠加处的Path,这样就可以做到自定义叠加处的颜色了。
onDraw完整代码:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        float centerY = getMeasuredHeight() / 2.0f;

        float ltrInitRadius, rtlInitRadius;
        Paint ltrPaint, rtlPaint;

        //确定当前【从左往右】移动的是哪颗小球
        if (isLtr) {
            ltrInitRadius = radius1;
            rtlInitRadius = radius2;
            ltrPaint = paint1;
            rtlPaint = paint2;
        } else {
            ltrInitRadius = radius2;
            rtlInitRadius = radius1;
            ltrPaint = paint2;
            rtlPaint = paint1;
        }


        float ltrX = getMeasuredWidth() / 2.0f - distance / 2.0f;
        ltrX = ltrX + (distance * fraction);//当前从左往右的球的X坐标

        float rtlX = getMeasuredWidth() / 2.0f + distance / 2.0f;
        rtlX = rtlX - (distance * fraction);//当前从右往左的球的X坐标

        //计算小球移动过程中的大小变化
        float ltrBallRadius, rtlBallRadius;
        if (fraction <= scaleStartFraction) { //动画进度[0,scaleStartFraction]时,球大小由1倍逐渐缩放至ltrScale/rtlScale倍
            float scaleFraction = 1.0f / scaleStartFraction * fraction; //百分比转换 [0,scaleStartFraction]] -> [0,1]
            ltrBallRadius = ltrInitRadius * (1 + (ltrScale - 1) * scaleFraction);
            rtlBallRadius = rtlInitRadius * (1 + (rtlScale - 1) * scaleFraction);
        } else if (fraction >= scaleEndFraction) { //动画进度[scaleEndFraction,1],球大小由ltrScale/rtlScale倍逐渐恢复至1倍
            float scaleFraction = (fraction - 1) / (scaleEndFraction - 1); //百分比转换,[scaleEndFraction,1] -> [1,0]
            ltrBallRadius = ltrInitRadius * (1 + (ltrScale - 1) * scaleFraction);
            rtlBallRadius = rtlInitRadius * (1 + (rtlScale - 1) * scaleFraction);
        } else { //动画进度[scaleStartFraction,scaleEndFraction],球保持缩放后的大小
            ltrBallRadius = ltrInitRadius * ltrScale;
            rtlBallRadius = rtlInitRadius * rtlScale;
        }

        ltrPath.reset();
        ltrPath.addCircle(ltrX, centerY, ltrBallRadius, Path.Direction.CW);
        rtlPath.reset();
        rtlPath.addCircle(rtlX, centerY, rtlBallRadius, Path.Direction.CW);
        mixPath.op(ltrPath, rtlPath, Path.Op.INTERSECT);

        canvas.drawPath(ltrPath, ltrPaint);
        canvas.drawPath(rtlPath, rtlPaint);
        canvas.drawPath(mixPath, mixPaint);
    }

欢迎star,3Q

你可能感兴趣的:(Android仿抖音加载框之两颗小球转动控件)