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

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

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

效果图

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

本控件效果图:
本控件

使用方法

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

1、xml引用:

        <com.douyinloadingview.DYLoadingView
            android:id="@+id/dy3"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="#000000"
            app:color1="#FF00EEEE"
            app:color2="#FFFF4040"
            app:....(其他可选属性) /> 

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半径radius1setRadius()6dp
球2半径radius2setRadius()6dp
两球间隔gapsetRadius()0.8dp
球1颜色color1setColors()0XFFFF4040
球2颜色color2setColors()0XFF00EEEE
叠加色mixColorsetColors()0XFF000000
从右往左移动时小球最大缩放倍数rtlScalesetScales()0.7f
从左往右移动时小球最大缩放倍数ltrScalesetScales()1.3f
一次移动动画时长durationsetDuration()350ms
一次移动动画后停顿时长pauseDurationsetDuration()80ms
动画进度在[0,scaleStartFraction]期间,小球大小逐渐缩放scaleStartFractionsetStartEndFraction()0.2f
动画进度在[scaleEndFraction,1]期间,小球大小逐渐恢复scaleEndFractionsetStartEndFraction()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

  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值