FlingAnimation/SpringAnimation实现

作者: 小草凡 | 来源:发表于2019-08-19 18:55 被阅读12次

    You have to believe in yourself. That's the secret of success.

    之前的QQ版本里面有个大表情的动画很炫酷,之前就想着怎么实现的。最近看了DynamicAnimation,发现利用它很容易实现类似效果,自己最近也在看Kotlin,因此就用Kotlin写个demo练练手。

    实现效果

    等我实现了效果,发现QQ已经把该功能去掉了T_T。原版的效果看不到了,直接放我实现的效果。

    在这里插入图片描述

    实现原理

    窗口的左右抖动效果

    借助SpringAnimation 可以实现,顾名思义该动画库能实现弹簧般的抖动效果。与弹簧相关的两个重要物理属性分别是Damping ratio(阻尼比)和Stiffness(刚性)。

    • Damping ratio(阻尼比)

    阻尼比描述了弹簧振荡过程中逐渐减小的速度。 通过阻尼比,可以调整振荡从一次反弹到下一次反弹的衰减速度。

    官方用法示例:

    
    findViewById<View>(R.id.imageView).also { img ->
    
        SpringAnimation(img, DynamicAnimation.TRANSLATION_Y).apply {
    
            …
    
            //Setting the damping ratio to create a low bouncing effect.
    
            spring.dampingRatio = SpringForce.DAMPING_RATIO_LOW_BOUNCY
    
            …
    
        }
    
    }
    
    

    动画库已经提供了四种类型的阻尼比,官方也提供了相对应的效果图

    DAMPING_RATIO_HIGH_BOUNCY

    在这里插入图片描述

    DAMPING_RATIO_MEDIUM_BOUNCY

    在这里插入图片描述

    DAMPING_RATIO_LOW_BOUNCY

    在这里插入图片描述

    DAMPING_RATIO_NO_BOUNCY

    在这里插入图片描述
    • Stiffness(刚性)

    刚性指弹簧产生形变时,产生的弹力大小。相应的系统也提供了四种值。

    STIFFNESS_HIGH

    STIFFNESS_MEDIUM

    STIFFNESS_LOW

    STIFFNESS_VERY_LOW

    Stiffness的值越大,弹簧动画看起来的抖动也就越不明显,就像我们施加相同的力给粗细不同的铁丝弹簧,施加的力取消后,越粗的弹簧刚性越大,它的抖动就没有细的弹簧明显。

    官方用法示例:

    
    findViewById<View>(R.id.imageView).also { img ->
    
        SpringAnimation(img, DynamicAnimation.TRANSLATION_Y).apply {
    
            …
    
            //Setting the spring with a low stiffness.
    
            spring.stiffness = SpringForce.STIFFNESS_LOW
    
            …
    
        }
    
    }
    
    
    • SpringForce自定义弹簧动画属性

    如果想将Damping ratio/Stiffness 应用在多个弹簧动画中,可以通过SpringForce 来设置。

    
    SpringForce force = new SpringForce();
    
    force.setDampingRatio(DAMPING_RATIO_LOW_BOUNCY).setStiffness(STIFFNESS_LOW);
    
    setSpring(force);
    
    

    有了上面的知识,可以很容易的实现窗口左右抖动的效果。

    
    private fun startWindowAnimation() {
    
            val springAnimation = createSpringAnimation(window.decorView, SpringAnimation.TRANSLATION_X);
    
            springAnimation.start();
    
        }
    
    fun createSpringAnimation(view: View, property: DynamicAnimation.ViewProperty): SpringAnimation {
    
            val animation = SpringAnimation(view, property)
    
            animation.setStartVelocity(4000f);
    
            val spring = SpringForce(0f)
    
            spring.stiffness = SpringForce.STIFFNESS_MEDIUM
    
            spring.dampingRatio = SpringForce.DAMPING_RATIO_HIGH_BOUNCY
    
            animation.spring = spring
    
            animation.setMinimumVisibleChange(DynamicAnimation.MIN_VISIBLE_CHANGE_PIXELS)
    
            return animation
    
        }
    
    
    在这里插入图片描述

    弹球动画

    弹球动画比较麻烦点,拆分成三个步骤。

    • 动态添加view

    每点击一次button,就动态添加一个弹球view,然后在表情下落时我们添加一个变淡的动画,在动画结束时在将该view删除掉。

    • X轴上的动画

    弹球的动画拆分成X轴跟Y轴,在分别实现以此来降低处理难度。弹球在X轴没有重力影响,只有空气阻力跟碰撞引起的速度衰减,因此很容易通过FlingAnimation动画来模拟,为了进一步简化操作,当弹球碰撞到左右屏幕边缘时,只是将速度取反,不考虑碰撞损失的速度。

    FlingAnimation动画可以通过setFriction方法设置一个速度衰减因子,通过该值就可以模拟处X轴受到的空气阻力。

    
    private fun startBallAnimation(movingView: View) {
    
            val flingXAnimation = createFlingAnimation(movingView, DynamicAnimation.TRANSLATION_X);
    
            flingXAnimation.addUpdateListener { animation, value, velocity ->
    
                if (velocity > 0 && movingView.x > screenW - movingView.width) {
    
                    flingXAnimation.setStartVelocity(-Math.abs(velocity))
    
                } else if (velocity < 0 && movingView.x < 0) {
    
                    flingXAnimation.setStartVelocity(Math.abs(velocity))
    
                }
    
                animation.start()
    
            }
    
            flingXAnimation.setFriction(0.2f)
    
            flingXAnimation.start();
    
    }
    
    fun createFlingAnimation(view: View, property: DynamicAnimation.ViewProperty): FlingAnimation {
    
            val animation = FlingAnimation(view, property)
    
            animation.setStartVelocity(-3000f).setFriction(0.01f)
    
            animation.setMinimumVisibleChange(DynamicAnimation.MIN_VISIBLE_CHANGE_PIXELS)
    
            return animation
    
        }
    
    
    在这里插入图片描述
    • Y轴上的动画

    Y轴上的动画继续分成两个部分,初次上抛时通过FlingAnimation来模拟,当弹球碰到屏幕的顶端后,转换成属性动画,先取消掉Y轴上的FlingAnimation,在通过给属性动画设置BounceInterpolator插值器来模拟,同时也设置上变淡的动画。在属性动画结束时移除掉弹球。

    
    private fun startBallAnimation(movingView: View) {
    
            //X轴方向的动画
    
            val flingXAnimation = createFlingAnimation(movingView, DynamicAnimation.TRANSLATION_X);
    
            flingXAnimation.addUpdateListener { animation, value, velocity ->
    
                if (velocity > 0 && movingView.x > screenW - movingView.width) {
    
                    flingXAnimation.setStartVelocity(-Math.abs(velocity))
    
                } else if (velocity < 0 && movingView.x < 0) {
    
                    flingXAnimation.setStartVelocity(Math.abs(velocity))
    
                }
    
                animation.start()
    
            }
    
            flingXAnimation.setFriction(0.2f)
    
            flingXAnimation.start();
    
            //Y轴方向的动画
    
            val flingYAnimation = createFlingAnimation(movingView, DynamicAnimation.TRANSLATION_Y);
    
            flingYAnimation.addUpdateListener { animation, value, velocity ->
    
                if (velocity < 0 && movingView.y < 0) {
    
                    flingYAnimation.setStartVelocity(Math.abs(velocity)).setFriction(0.3f)
    
                } else if (velocity > 0) {
    
                    flingYAnimation.cancel();
    
                    val holder1 = PropertyValuesHolder.ofFloat(View.ALPHA, 1f, 1f, 0f)
    
                    val holder2 = PropertyValuesHolder.ofFloat(
    
                        View.Y,
    
                        movingView.getY(),
    
                        (getScreenHeight(this) - 200 - movingView.height).toFloat()
    
                    )
    
                    val animator = ObjectAnimator.ofPropertyValuesHolder(movingView, holder1, holder2)
    
                    animator.setDuration(3000);
    
                    val bounceInterpolator = BounceInterpolator()
    
                    animator.setInterpolator(bounceInterpolator);
    
                    animator.addListener(object : AnimatorListenerAdapter() {
    
                        override fun onAnimationEnd(animation: Animator?) {
    
                            super.onAnimationEnd(animation)
    
                            rootView?.removeView(movingView)
    
                        }
    
                    })
    
                    animator.start()
    
                }
    
            }
    
            flingYAnimation.start();
    
        }
    
        fun createFlingAnimation(view: View, property: DynamicAnimation.ViewProperty): FlingAnimation {
    
            val animation = FlingAnimation(view, property)
    
            animation.setStartVelocity(-3000f).setFriction(0.01f)
    
            animation.setMinimumVisibleChange(DynamicAnimation.MIN_VISIBLE_CHANGE_PIXELS)
    
            return animation
    
        }
    
    

    通过以上步骤,就实现了弹球效果。

    完整代码点击这里

    总结

    最近刚在学习kotlin,demo代码看起来结构不太清晰,有疑问或者更好的实现方案欢迎交流。

    相关文章

      网友评论

        本文标题:FlingAnimation/SpringAnimation实现

        本文链接:https://www.haomeiwen.com/subject/kliesctx.html