使用属性动画实现直播送花效果

2017-06-13  本文已影响151人  Veneto_2022

直播点赞送花,双击666,这种效果的使用场景越来越普遍。实现的方法可以使用自定义控件或者属性动画,这次本人使用后者来实现了送花效果,主要是考虑到属性动画的强大和简便的使用性。不多说,先上图看下效果。

demo.gif

下面来分析一下本人实现的代码:

    private PointF startPoint;
    private PointF endPoint;
    private Drawable[] drawables;
    private Interpolator[] interpolators;
    private FrameLayout rootView;

startPoint 作为屏幕下方的动画起始点,endPoint是屏幕最上方的动画终点,为了使点赞花朵样式能够多样化,生动化,我们定义了drawables作为存放所有花朵的Drawable图片数组,同理,为了使鲜花的运动轨迹更随机,我们定义了interpolators作为是一个存放所有插值器的数组。插值器在Android的属性动画中是一个非常重要的类,它控制了动画运动过程中的变化情况,比如加速,加速,先加速后减速等。父容器选用FrameLayout 而不是 LinearLayout,原因是在花运行到屏幕上方将其删除时,不会由于布局的变化而发生闪屏的现象。

下面问题来了,这个动画的重点是如何才能使每朵花按照随机的曲线轨迹进行运动,这里要利用估值器和贝塞尔曲线的知识。我们先定义一个估值器,然后在实现它的evaluate方法,在里面利用贝塞尔曲线的公式来计算出fraction时刻,曲线的运动坐标PointF 。

class MyTypeEvaluator implements TypeEvaluator<PointF> {

        private PointF pointF1, pointF2;

        public MyTypeEvaluator(PointF pointF1, PointF pointF2) {
            this.pointF1 = pointF1;
            this.pointF2 = pointF2;
        }

        @Override
        public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
            //三阶贝塞尔曲线
            //B(t) = P0 * (1-t)^3 + 3 * P1 * (1-t)^2 + 3 * P2 * t^2 * (1-t) + P3 * t^3  ,其中 0 <= t <= 1
            float timeLeft = 1.0f - fraction;
            PointF pointF = new PointF();//结果
            pointF.x = timeLeft * timeLeft * timeLeft * (startValue.x)
                    + 3 * timeLeft * timeLeft * fraction * (pointF1.x)
                    + 3 * timeLeft * fraction * fraction * (pointF2.x)
                    + fraction * fraction * fraction * (endValue.x);

            pointF.y = timeLeft * timeLeft * timeLeft * (startValue.y)
                    + 3 * timeLeft * timeLeft * fraction * (pointF1.y)
                    + 3 * timeLeft * fraction * fraction * (pointF2.y)
                    + fraction * fraction * fraction * (endValue.y);
            return pointF;

        }
    }

evaluate方法中的参数fraction代表,物体做曲线运动时的某一时刻所占全部时间的百分比,例如刚开始运动时fraction等于0,运动到终点时fraction等于1。因此通过重写evaluate方法,我们就可以计算出某一时刻,物体在屏幕上的运动位置,并将该运动位置储存在pointF中。

然而光有运动路径上的点还是不够的,我们还需要将其变成动画,新手往往遇到一个复杂的动画时感觉无从下手,其实不用担心,任何复杂的动画都是可以通过分解成若干的基础动画,比如透明度渐变,缩放,旋转,平移等。下面我们来分析一下这个送花的动画由哪几部分动画组成的。

    private void startAnin(final ImageView flower) {
        final AnimatorSet animatorSet = new AnimatorSet();

        ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(flower, "alpha", 1.0f, 0.3f);
        alphaAnim.setDuration(400);

        ObjectAnimator scaleAnimX = ObjectAnimator.ofFloat(flower, "scaleX", 0.4f, 1.0f);
        ObjectAnimator scaleAnimY = ObjectAnimator.ofFloat(flower, "scaleY", 0.4f, 1.0f);
        scaleAnimX.setDuration(1800);
        scaleAnimY.setDuration(1800);

        final ValueAnimator animator = ValueAnimator.ofObject(new MyTypeEvaluator(getPoint(0), getPoint(1)), startPoint, endPoint);
        animator.setDuration(4000);
        animator.setInterpolator(new AccelerateDecelerateInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                PointF pointF = (PointF) animation.getAnimatedValue();
                flower.setX(pointF.x);
                flower.setY(pointF.y);
            }
        });

        //  animator.start();
        animatorSet.play(animator);
        animatorSet.play(scaleAnimX).with(scaleAnimY).before(alphaAnim);
        animatorSet.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                rootView.removeView(flower);
            }
        });
        animatorSet.start();
    }

1)花从不透明变成透明是第一个动画,我们定义为 alphaAnim
2)花从小变成大是第二个动画,我们定义为 scaleAnimX ,scaleAnimY
3)花本身的曲线位移是第三个动画,我们定义为 animator

以上是开启动画的代码,给 animator 设置监听事件,在 回调方法中onAnimationUpdate 中,我们将花的坐标进行变换。这里详细补充一点,属性动画的原理究竟是什么?通过阅读源码,属性动画本质上是通过反射来更改View的属性值,如TranslationX,alpha等,由于属性动画是实实在在得修改了View的属性,因此在动画过程中依然能够相应点击事件。在之后系统会不断去调用View的invalidate来重绘屏幕界面(16ms为间隔)形成动画。由于属性动画需要使用反射并且需要不断修改View的属性,在性能上比起补间动画和帧动画会有些许损耗。16ms是系统的刷新时间,因此如果在16ms内未完成View的重绘,就会造成丢帧,使画面显得十分卡顿。

@Override
            public void onAnimationUpdate(ValueAnimator animation) {
                PointF pointF = (PointF) animation.getAnimatedValue();
                flower.setX(pointF.x);
                flower.setY(pointF.y);
            }

同时为了提高性能,在花运行到终点的时候,将其从父容器中删除。

 animatorSet.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                rootView.removeView(flower);
            }
        });

在父容器中添加一朵花并开启动画,初始化花的位置

    /**
     * 添加花朵
     */
    private void addFlower() {
        ImageView flower = new ImageView(this);
        flower.setLayoutParams(new ViewGroup.LayoutParams(100, 100));
        flower.setBackground(drawables[new Random().nextInt(drawables.length)]);
        flower.setX(startPoint.x);
        flower.setY(startPoint.y);
        rootView.addView(flower);
        startAnin(flower);
    }

最后需要在在按钮被点击时,在父容器增加一朵花,并开启动画,整个代码就完成了。

 btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                addFlower();
            }
        });

完成代码参见 : https://github.com/pengzee/FlowerAnim

上一篇下一篇

猜你喜欢

热点阅读