美文网首页自定义控件
[Android动画]属性动画-小球下落动画实现

[Android动画]属性动画-小球下落动画实现

作者: qiHuang112 | 来源:发表于2020-04-16 16:58 被阅读0次

    属性动画

    属性动画是通过直接改变View属性,实现的动画效果。与补间动画不同的是,属性动画是对象的属性的真实改变,而补间动画仅仅是个动画效果,实际属性没有改变。直观表现是属性动画的点击事件会随着动画的过程有范围性的改变。比如你想做个幸运大轮盘的动画,那你得用属性动画而不是补间动画。

    属性动画中支持对int,float,rbg等属性的支持,安卓给出了他们自定义的估值函数。用于记录动画过程中属性的改变。具体手段是用过实现TypeEvaluator接口,自定义各种属性的evaluate方法。一般情况下,如果你的动画效果比较简单,那么使用sdk中提供的几种估值器就够用了,但是对于比较复杂的场景,需要自己自定义估值函数。

    估值器是什么

    估值器是属性动画中的进阶手段,对于一些sdk中没有预定义的对象给出自己的实现。如果除了属性之外还有其他对象或者自定义的一些属性来控制动画,则需要自己实现估值器。

    估值器怎么用

    使用SDK中自带的Float估值器实现简单的旋转、透明度、缩放效果,下面是代码

    // 0f,1f,0f表示属性的变化过程,具体的变化受FloatEvaluator()的evaluate方法控制
    // 提前剧透一下,FloatEvaluator中的方法是匀速变化的,所以拿到的animatedValue 
    // 先从0变为1再变为0
    val anim = ValueAnimator.ofObject(FloatEvaluator(), 0f, 1f, 0f).apply {
        // 定义了整个动画的时间为1000ms
        duration = 1000
        addUpdateListener {
            // 透明度从 1 变为 0.5 再变为 1
            view_anim.alpha = (2 - it.animatedValue as Float) / 2
            // 旋转角度从 0 变为 180 再变为 0
            view_anim.rotation = it.animatedValue as Float * 180
            // 尺度缩放从 1 变为 0.5 再变为 1
            view_anim.scaleX = (2 - it.animatedValue as Float) / 2
            view_anim.scaleY = (2 - it.animatedValue as Float) / 2
        }
    }
    

    下面是效果:


    估值器的简单使用

    再看下FloatEvaluator的evaluate方法:

    /**
     * This function returns the result of linearly interpolating the start and end values, with
     * <code>fraction</code> representing the proportion between the start and end values. The
     * calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>,
     * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
     * and <code>t</code> is <code>fraction</code>.
     *
     * @param fraction   The fraction from the starting to the ending values
     * @param startValue The start value; should be of type <code>float</code> or
     *                   <code>Float</code>
     * @param endValue   The end value; should be of type <code>float</code> or <code>Float</code>
     * @return A linear interpolation between the start and end values, given the
     *         <code>fraction</code> parameter.
     */
    public Float evaluate(float fraction, Number startValue, Number endValue) {
        float startFloat = startValue.floatValue();
        return startFloat + fraction * (endValue.floatValue() - startFloat);
    }
    

    fraction表示每个阶段动画完成的进度 0~1
    startValue和endValue表示每一个阶段的开始和结束值
    举个例子,0f,1f,0f,1f这个变化过程
    0f-1f阶段
    startValue一直为0f,endValue一直为1f,fraction从0变为1
    1f-0f阶段
    startValue一直为1f,endValue一直为0f,fraction从0变为1
    0f-1f阶段
    startValue一直为0f,endValue一直为1f,fraction从0变为1
    而这一切都是在1000ms中完成的。
    为了方便理解,将这个变化过程画成图像,其中横坐标代表时间,纵坐标表示fraction,startValue和endValue的值:

    各个阶段变化过程
    根据上述变化过程的分析,可以得到evaluate方法的返回值的变化图像:
    startFloat + fraction * (endValue.floatValue() - startFloat)
    evaluate方法返回值随时间变化图像
    将返回值直接用于平移属性:
    val anim = ValueAnimator.ofObject(FloatEvaluator(), 0f, 1f, 0f, 1f).apply {
        duration = 1000
        addUpdateListener {
            view_anim.translationX = it.animatedValue as Float * 100
        }
    }
    

    效果是这样的,可以看到,变化过程与我们画的函数图像完全相符。


    使用估值器实现小球下落动画

    经过上诉的分析,我们已经基本了解了估值器的原理,是时候创建自己的估值器了。我们可以使用估值器模拟一个小球下落的过程。
    代码如下:

    class AnimFragment4 : BaseFragment() {
        override fun getLayoutId() = R.layout.fragment_anim4
    
        override fun initViews() {
    
            // 裁剪为圆球形
            view_anim?.outlineProvider = object : ViewOutlineProvider() {
                override fun getOutline(view: View?, outline: Outline?) {
                    outline?.setOval(0, 0, view_anim?.width ?: 0, view_anim?.height ?: 0);
                }
            }
            view_anim?.clipToOutline = true
    
    
            btn_anim_0.setOnClickListener {
                val status = Status(300.0, -500.0, 0.0, 600.0)
                AnimatorSet().apply {
                    play(getAnim(status)).before(getAnim(status.nextStatus, true))
                }.start()
            }
        }
    
        private fun getAnim(status: Status, end: Boolean = false) =
            ValueAnimator.ofObject(
                TypeEvaluator<Status> { fraction, startValue, _ ->
                    val t = startValue.duration * fraction // 当前经过时间
                    val curWidth = startValue.vx * t
                    val curHigh = 0.5 * Status.a * t * t + startValue.vy * t
                    Status(startValue.vx, startValue.vy + Status.a * t, curWidth, curHigh)
                },
                status,
                status.nextStatus
            ).apply {
                duration = (status.duration * 1000).toLong()
                interpolator = LinearInterpolator()
                addUpdateListener {
                    (it.animatedValue as Status).let { curStatus ->
                        view_anim.translationX = curStatus.width.toFloat()
                        view_anim.translationY = curStatus.high.toFloat()
                    }
                }
                if (!end) {
                    doOnEnd {
                        view_anim.top += status.y.toInt()
                        view_anim.bottom += status.y.toInt()
                        view_anim.left += status.x.toInt()
                        view_anim.right += status.x.toInt()
                    }
                }
            }
    
    
        /**
         * @param vx 横向初速度
         * @param vy 纵向初速度
         * @param width 横向位移
         * @param high 纵向位移
         */
        data class Status(val vx: Double, val vy: Double, val width: Double, val high: Double) {
            val duration: Double by lazy {  // 当前状态的落地时间
                // high = 0.5*a*duration*duration + vy*duration
                Util.getAns(0.5 * a, vy, -high) // 耗时
            }
    
            val nextStatus: Status by lazy {
                Status(vx * decay, -(vy + a * duration) * decay, vx * duration, 0.0)
            }
    
            val x by lazy {
                duration * vx
            }
    
            val y by lazy {
                duration * vy + 0.5 * a * duration * duration
            }
    
            companion object {
                // 衰减系数
                const val decay = 0.8
    
                // 加速度
                const val a = 1000
            }
        }
    }
    

    效果如下:


    横向速度为300,纵向速度为-500 横向速度为300,纵向速度为0

    进阶

    上诉动画仅仅使用估值器实现了小球跳跃两次的动画,自定义了各种状态看起来较复杂,其实使用属性动画中的ObjectAnimator也能完成上诉的功能,而且简单易懂。下篇文章教你。

    相关文章

      网友评论

        本文标题:[Android动画]属性动画-小球下落动画实现

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