讓View具有彈性效果的動畫——SpringAnimation

xingfeng_coder發表於2019-03-22

SpringAnimation和FlingAnimation一樣,是DynamicAnimation的兩種型別。Spring模擬的是物理世界的彈力,彈彈彈,彈走魚尾紋,,,
先看下效果:

demo示例

在某些引數下,可以看到圖片有來回震盪的效果。

SpringAnimation的基本使用

  1. 新增支援庫
 dependencies {
      implementation 'com.android.support:support-dynamic-animation:28.0.0'
  }
複製程式碼
  1. 程式碼實現
SpringAnimation(img, DynamicAnimation.TRANSLATION_Y, 0f) 
複製程式碼

SpringForce

SpringForce定義著動畫的各種屬性值,其中有兩個重要的屬性:DampingRatio和Stiffness。 DampingRatio可以理解成反彈次數,值越大,反彈次數越少;值為1,則不反彈。
Stiffness可以理解成要恢復成未拉伸狀態所需的時間,值越大,恢復到之前的狀態的時間就越短。

DampingRatio為0將無限震盪,就是無阻尼狀態。這個時候是不能通過skipToEnd()取消動畫的。

Demo中的例子就是調節這兩個屬性,然後就會有不同的效果。
程式碼如下:

class SpringAnimationActivity : AppCompatActivity() {

    lateinit var xSpringAnimation: SpringAnimation
    lateinit var ySpringAnimation: SpringAnimation

    var xDiffLeft: Float? = null
    var yDiffTop: Float? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_spring_animation)

        val springForce = SpringForce(0f).apply {
            setDampingRatio(SpringForce.DAMPING_RATIO_HIGH_BOUNCY)
            setStiffness(SpringForce.STIFFNESS_HIGH)
        }

        dampingRatioGroup.setOnCheckedChangeListener { group, checkedId ->
            when (checkedId) {
                R.id.dampingRatioHigh -> springForce.dampingRatio = SpringForce.DAMPING_RATIO_HIGH_BOUNCY
                R.id.dampingRatioMedium -> springForce.dampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY
                R.id.dampingRatioLow -> springForce.dampingRatio = SpringForce.DAMPING_RATIO_LOW_BOUNCY
                R.id.dampingRatioNo -> springForce.dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY
            }
        }

        stiffnessGroup.setOnCheckedChangeListener { group, checkedId ->
            when (checkedId) {
                R.id.stiffnessHigh -> springForce.stiffness = SpringForce.STIFFNESS_HIGH
                R.id.stiffnesMedium -> springForce.stiffness = SpringForce.STIFFNESS_MEDIUM
                R.id.stiffnesLow -> springForce.stiffness = SpringForce.STIFFNESS_LOW
                R.id.stiffnesVeryLow -> springForce.stiffness = SpringForce.STIFFNESS_VERY_LOW
            }
        }

        xSpringAnimation = SpringAnimation(ivImg, DynamicAnimation.TRANSLATION_X).setSpring(springForce)
        ySpringAnimation = SpringAnimation(ivImg, DynamicAnimation.TRANSLATION_Y).setSpring(springForce)

        ivImg.setOnTouchListener { v, event ->
            val actionCode = event.action
            if (actionCode == MotionEvent.ACTION_DOWN) {
                xDiffLeft = event.rawX - ivImg.x
                yDiffTop = event.rawY - ivImg.y
                xSpringAnimation.cancel()
                ySpringAnimation.cancel()
            } else if (actionCode == MotionEvent.ACTION_MOVE) {
                ivImg.x = event.rawX - xDiffLeft!!
                ivImg.y = event.rawY - yDiffTop!!
            } else if (actionCode == MotionEvent.ACTION_UP) {
                xSpringAnimation.start()
                ySpringAnimation.start()
            }
            true
        }
    }
}
複製程式碼

自定義屬性

可以動畫的不止DynamicAnimation中定義的一些屬性,下面的例子,定義了scale的屬性,起始就是將scaleX和scaleY進行了整合,程式碼如下:

SpringAnimation(it, object : FloatPropertyCompat<View>("scale") {
                    override fun setValue(view: View?, scale: Float) {
                        view!!.scaleX = scale
                        view!!.scaleY = scale
                    }

                    override fun getValue(view: View?): Float {
                        return view!!.scaleX
                    }
                }).setSpring(SpringForce(2f)
                        .setDampingRatio(SpringForce.DAMPING_RATIO_HIGH_BOUNCY)
                        .setStiffness(SpringForce.STIFFNESS_LOW))
                        .setMinimumVisibleChange(0.05f)
                        .start()
複製程式碼

需要呼叫setMinimumVisibleChange()方法,設定最小可見變化,不然沒有震盪的效果。
關於setMinimumVisibleChange()的原則,參考https://developer.android.com/guide/topics/graphics/fling-animation?hl=zh-cn#setting-minimum-visible-change
效果如下:

自定義屬性
具體程式碼如下:

var changed = false
        val sForce = SpringForce()
                .setDampingRatio(SpringForce.DAMPING_RATIO_HIGH_BOUNCY)
                .setStiffness(SpringForce.STIFFNESS_LOW)
        val propertyCompat = object : FloatPropertyCompat<View>("scale") {
            override fun setValue(view: View?, scale: Float) {
                view!!.scaleX = scale
                view!!.scaleY = scale
            }

            override fun getValue(view: View?): Float {
                return view!!.scaleX
            }
        }
        ivImg2.setOnClickListener {
            if (!changed) {
                SpringAnimation(it, propertyCompat).setSpring(sForce.setFinalPosition(2f))
                        .setMinimumVisibleChange(0.00390625f)
                        .start()
            } else {
                SpringAnimation(it, propertyCompat).setSpring(sForce.setFinalPosition(1f))
                        .setMinimumVisibleChange(0.00390625f)
                        .start()
            }
            changed = !changed
        }
複製程式碼

Chained Spring效果

曾經做過一個需求,點選發布按鈕,彈出幾個釋出選項,有一個彈簧效果,下面仿寫了這個效果,如下圖:

Chained Spring效果
可以看到聯動的效果,最左邊的帶動中間,中間再帶動最右邊的。
實現主要是通過addUpdateListener()以及startToFinalPosition()實現的。
程式碼如下:

class ChainedSpringActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_chained_spring)

        val springForce = SpringForce(-600f).apply {
            dampingRatio = SpringForce.DAMPING_RATIO_HIGH_BOUNCY
            stiffness = SpringForce.STIFFNESS_LOW
        }
        val tvTextSpringAnimation = SpringAnimation(tvPublishText,
                DynamicAnimation.TRANSLATION_Y)
        val tvVideoSpringAnimation = SpringAnimation(tvPublishVideo,
                DynamicAnimation.TRANSLATION_Y).apply {
            addUpdateListener { animation, value, velocity ->
                tvTextSpringAnimation.animateToFinalPosition(value)
            }
        }
        val tvPicSpringAnimation = SpringAnimation(tvPublishPic,
                DynamicAnimation.TRANSLATION_Y).apply {
            spring = springForce
            addUpdateListener { dynamicAnimation, value, velocity ->
                tvVideoSpringAnimation.animateToFinalPosition(value)
            }
        }
        fab.setOnClickListener {
            tvPicSpringAnimation.start()
        }
        
    }
}
複製程式碼

取消動畫

  • cancel():立即停止動畫
  • skipToEnd():恢復到最終位置並停止動畫。需要注意的是,在無阻尼的情況下,不能呼叫該方法,為了安全,可以先呼叫canSkipToEnd()進行判斷,有阻尼的情況下返回true,否則返回false

一般來說,skipToEnd()會有跳躍的效果。

總結

SpringAnimation主要是通過設定SpringForce進行動畫的控制,SpringForce的DampingRatio和Stiffness分別表示阻尼係數和生硬度,DampingRatio越大,反彈次數越少,Sniffness越大,達到最終位置的時間就越短。

參考文章

關注我的技術公眾號,不定期會有技術文章推送,不敢說優質,但至少是我自己的學習心得。微信掃一掃下方二維碼即可關注:

二維碼

相關文章