使用 Kotlin DSL 实现一个超级简洁的 Android 动画框架

89 阅读2分钟

一句话描述:

使用 Kotlin DSL (Domain-Specific Language,领域特定语言)和 ValueAnimator 实现一个动画框架,以 DSL 的方式来方便、快速的实现对 view 的各种属性动画。

通过 Kotlin DSL 的方式来组合针对 view 的多个属性的动画,用于快速、简单的实现复杂的组合动画效果。简单又好用------使用示例:

fun animateTest(view: View) {
    val controller: Animators.Controller = quickAnimate {
        together {
            play {
                targets = listOf(view)
                duration = 300L
                interpolator = LinearInterpolator()
                translationX(0f, 100f)
            }
            play {
                targets = listOf(view)
                duration = 300L
                interpolator = LinearInterpolator()
                translationY(0f, 100f)
            }
        }
        onStart = {}
        onEnd = {}
    }
    // 开始动画
    controller.start()
}

Animators 类: quickAnimate { } 代码块中的 receiver 实现类。通过 封装 Controller 的方式对外提供控制动画的接口。

fun quickAnimate(init: Animators.() -> Unit): Animators.Controller

SingleAnimator 类: 具体的 动画动作 封装类 ,也就是 play { } 代码块中的receiver 实现类。负责完成单个属性动画。

fun play(anim: SingleAnimator.() -> Unit)

同时也支持多个动画的组合动画,通过 together 包裹的 动画会同时执行。否则,默认所有动画都是 串行执行。

fun together(init: Animators.() -> Unit)

最后,提供一个控制类 Animators.Controller ,用于 控制 动画的 开始 、取消,暂停 等操作。

API 接口设计如下:

fun quickAnimate(init: Animators.() -> Unit): Animators.Controller

class Animators {

	class Controller {
		fun start()
				
		fun cancel()
				
		fun pause()
				
		fun resume()
				
		fun isRunning(): Boolean
	}
		
	fun play(anim: SingleAnimator.() -> Unit)
		
	fun together(init: Animators.() -> Unit)
		
	class SingleAnimator {
		
	}
}

对于 具体的View 的属性动画,主要利用以下API 来实现:

public static <T> ObjectAnimator ofFloat(T target, Property<T, Float> property, float... values)

ObjectAnimator 是 Android 动画框架中的一个类,专门用于执行属性动画。ObjectAnimator 可以对目标对象的某个属性执行动画,例如透明度、旋转角度、位置等。

Property<T, Float> property: 目标对象的属性。Property 是一个属性的抽象表示,这里可以传入以下属性:

View.ALPHA
View.ROTATION
View.TRANSLATION_X
View.TRANSLATION_Y
......

float... values: 动画的关键帧值序列。可以传入多个值,动画将依次在这些值之间进行插值。

对于单个动画,可以提供以下配置属性,用于配置动画的 view对象、动画时长、插值器、repeatMode 等参数。

var duration: Long = 0L
var targets: List<View?> = emptyList()
var interpolator: TimeInterpolator? = null
var repeatMode: Int = ValueAnimator.RESTART
var repeatCount: Int = 0

单个动画使用示例:

play {
	targets = listOf(view)
	duration = 300L
	interpolator = LinearInterpolator()
	repeatMode = ValueAnimator.RESTART
	repeatCount = 0
	translationY(0f, 100f)
}

注意:这里只是完成了动画的配置,并没有真正开始动画,需要通过 Animators.Controller 来统一来控制动画的 开始 、暂停、继续、取消等。

完整源码:

package com.example.helloworld

import android.animation.*
import android.util.Log
import android.util.Property
import android.view.View
import android.view.animation.LinearInterpolator

fun quickAnimate(init: Animators.() -> Unit): Animators.Controller {
    val animators = Animators().apply(init)
    val animSet = AnimatorSet()
    animSet.addListener(object : Animator.AnimatorListener {
        override fun onAnimationStart(animation: Animator?) {
            animators.onStart?.invoke()
        }
        override fun onAnimationEnd(animation: Animator?) {
            animators.onEnd?.invoke()
        }
        override fun onAnimationCancel(animation: Animator?) {
        }
        override fun onAnimationRepeat(animation: Animator?) {
        }
    })
    Log.i("xcc", "quick anim: ${animators.getAnimatorList()}")
    animSet.playSequentially(animators.getAnimatorList())
    return Animators.Controller(animSet)
}

class Animators {

    var onStart: (() -> Unit)? = null

    var onEnd: (() -> Unit)? = null

    private val anims: MutableList<Animator> = mutableListOf()

    fun getAnimatorList() = anims.toList()

    fun play(anim: SingleAnimator.() -> Unit) {
        val animator = SingleAnimator().apply(anim).createAnimator()
        Log.i("xcc", "add anim: $animator")
        anims.add(animator)

    }

    fun together(init: Animators.() -> Unit) {
        val animSet = Animators().apply(init).createAnimator()
        Log.i("xcc", "add animSet: $animSet")
        anims.add(animSet)
    }

    private fun createAnimator(): Animator {
        val animSet = AnimatorSet()
        animSet.addListener(object : Animator.AnimatorListener {
            override fun onAnimationStart(animation: Animator?) {
                onStart?.invoke()
            }
            override fun onAnimationEnd(animation: Animator?) {
                onEnd?.invoke()
            }
            override fun onAnimationCancel(animation: Animator?) {
            }
            override fun onAnimationRepeat(animation: Animator?) {
            }
        })
        animSet.playTogether(anims.toList())
        return animSet
    }

    class Controller(private val animator: Animator) {
        fun start() {
            animator.start()
        }

        fun cancel(){
            animator.cancel()
        }

        fun pause() {
            animator.pause()
        }

        fun resume() {
            animator.resume()
        }

        fun isRunning(): Boolean {
            return animator.isRunning
        }

    }

    class SingleAnimator {

        var duration: Long = 0L
        var targets: List<View?> = emptyList()
        var interpolator: TimeInterpolator? = null
        var repeatMode: Int = ValueAnimator.RESTART
        var repeatCount: Int = 0

        private val animList: MutableList<Animator> = mutableListOf()

        fun createAnimator(): Animator {
            val animSet = AnimatorSet()
            animSet.duration = this.duration
            animSet.interpolator = this.interpolator
            animSet.playSequentially(animList.toList())
            return animSet
        }

        fun translationY(
            vararg values: Float,
            onUpdate: ((animation: ValueAnimator) -> Unit)? = null
        ) {
            anim(property = View.TRANSLATION_Y, values = values, onUpdate = onUpdate)
        }

        fun translationX(
            vararg values: Float,
            onUpdate: ((animation: ValueAnimator) -> Unit)? = null
        ) {
            anim(property = View.TRANSLATION_X, values = values, onUpdate = onUpdate)
        }

        fun alpha(
            vararg values: Float,
            onUpdate: ((animation: ValueAnimator) -> Unit)? = null
        ) {
            anim(property = View.ALPHA, values = values, onUpdate = onUpdate)
        }

        fun rotate(
            vararg values: Float,
            onUpdate: ((animation: ValueAnimator) -> Unit)? = null
        ) {
            anim(property = View.ROTATION, values = values, onUpdate = onUpdate)
        }

        // add more view property here...

        private fun anim(
            property: Property<View, Float>,
            vararg values: Float,
            onUpdate: ((animation: ValueAnimator) -> Unit)? = null
        ) {
            targets.forEach {
                if (it == null) {
                    return@forEach
                }
                val anim = ObjectAnimator.ofFloat(it, property, *values)
                anim.addUpdateListener { animation ->
                    onUpdate?.invoke(animation)
                }
                anim.duration = this.duration
                anim.interpolator = this.interpolator
                anim.repeatMode = this.repeatMode
                anim.repeatCount = this.repeatCount
                animList.add(anim)
            }
        }

    }
}

var controller: Animators.Controller? = null

fun animateTest(view: View) {
    controller = quickAnimate {
        together {
            play {
                targets = listOf(view)
                duration = 300L
                interpolator = LinearInterpolator()
                translationX(view.translationX, view.translationX + 100f)
            }
            play {
                targets = listOf(view)
                duration = 300L
                interpolator = LinearInterpolator()
                translationY(view.translationY, view.translationY + 100f) {
                    Log.i("xcc", "on update: ${it.animatedValue}")
                }
            }
        }
        play {
            targets = listOf(view)
            duration = 300L
            interpolator = LinearInterpolator()
            alpha(0f, 1f)
        }
        play {
            targets = listOf(view)
            duration = 300L
            interpolator = LinearInterpolator()
            rotate(0f, 360f)
        }
        onStart = {
            Log.i("xcc", "onStart")
        }
        onEnd = {
            Log.i("xcc", "onEnd")
        }
    }
    controller?.start()
}