Material Component 動畫基礎—Spring Animation

不論是在Android Material Design,仍是Flutter中,Google都在嘗試統一動畫的行爲和實現,在Google看來,動畫基本都分爲兩種,即模擬動畫和物理動畫,本篇將介紹物理動畫,這個概念在Android開發中,涉及的很是少,同時文檔也最少,但倒是實現不少優雅動畫的基礎,特別是MDC中封裝的一些動畫,在不少細節的處理上,都使用到了物理動畫的概念。android

彈性與阻尼

物理動畫,顧名思義是基於物理學定律基礎的動畫效果,它實際上參考的就是彈簧的形變過程,即胡克定律,這種動畫類型,一般被稱爲Spring Animation。官網上其實在很不起眼的小角落有一篇很是詳細的文檔,以下所示。web

https://developer.android.com/guide/topics/graphics/spring-animation#add-support-libraryspring

對於Spring Animation來講,一般有兩種經常使用的場景,即彈性和阻尼,彈性定義的是物體恢復到某個狀態下的非線性過程,而阻尼,則定義的是拖動物體的非線性阻力。api

關於Spring的設計定義,你們能夠參考下這篇文章 https://zhuanlan.zhihu.com/p/127266926。微信

彈性

首先,咱們來看下彈性的實現。首先,須要引入Google的Spring實現庫,代碼以下所示。app

api 'androidx.dynamicanimation:dynamicanimation:1.0.0'

要使用Spring Animation其實很是簡單,定義SpringAnimation便可,甚至不用設置參數,下面就經過一個最簡單的示例,來演示下如何使用Spring Animation,代碼以下所示。編輯器

class MainActivity : AppCompatActivity() {
    var offsetX: Float = 0f
    var offsetY: Float = 0f

    @SuppressLint("ClickableViewAccessibility")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        test.setOnTouchListener { v, event ->
            when (event.action) {
                MotionEvent.ACTION_DOWN -> {
                    offsetX = event.rawX
                    offsetY = event.rawY
                }
                MotionEvent.ACTION_MOVE -> {
                    test.translationX = event.rawX - offsetX
                    test.translationY = event.rawY - offsetY
                }
                MotionEvent.ACTION_UP -> {
                    SpringAnimation(test, DynamicAnimation.TRANSLATION_Y).apply {
                        spring = SpringForce().apply {
//                            dampingRatio = DAMPING_RATIO_NO_BOUNCY
//                            stiffness = SpringForce.STIFFNESS_VERY_LOW
                        }
                        animateToFinalPosition(0f)
                    }
                    SpringAnimation(test, DynamicAnimation.TRANSLATION_X).apply {
                        spring = SpringForce().apply {
//                            dampingRatio = DAMPING_RATIO_NO_BOUNCY
//                            stiffness = SpringForce.STIFFNESS_VERY_LOW
                        }
                        animateToFinalPosition(0f)
                    }
                }
            }
            true
        }
    }
}

上面的代碼定義了一個能夠拖動並改變位置的View,當執行ACTION_UP的時候,就會執行定義的SpringAnimation,SpringAnimation須要幾個參數,即TargetView、執行動畫的屬性、以及最終的屬性值,而動畫,則能夠經過調用start(),或調用animateToFinalPosition()方法來啓動,它們的區別就是是否設置了finalPosition,效果如圖所示。ide

slide-min

很簡單的幾行代碼就實現了彈性的效果。函數

哪些屬性

基於Spring特性的動畫能夠更改屏幕上的View的實際屬性,從而爲View添加動畫效果。系統中支持了下面這些屬性。佈局

  • ALPHA:表示視圖的 Alpha 透明度。該值默認爲 1(不透明),值爲 0 則表示徹底透明(不可見)。
  • TRANSLATION_X、TRANSLATION_Y 和 TRANSLATION_Z:這些屬性用於控制視圖所在的位置,值爲視圖的佈局容器所設置的左側座標、頂部座標和高度的增量。
  • TRANSLATION_X 表示左側座標。
  • TRANSLATION_Y 表示頂部座標。
  • TRANSLATION_Z 表示視圖相對於其高度的深度。
  • ROTATION、ROTATION_X 和 ROTATION_Y:這些屬性用於控制視圖圍繞軸心點進行的 2D(rotation屬性)和 3D 旋轉。
  • SCROLL_X 和 SCROLL_Y:這些屬性分別表示視圖距離源左側和頂部邊緣的滾動偏移量(以像素爲單位)。它還以頁面滾動的距離來表示位置。
  • SCALE_X 和 SCALE_Y:這些屬性用於控制視圖圍繞其軸心點進行的 2D 縮放。X、Y 和 Z:這些是基本的實用屬性,用於描述視圖在容器中的最終位置。
  • X 是左側值與 TRANSLATION_X 的和。
  • Y 是頂部值與 TRANSLATION_Y 的和。
  • Z 是高度值與 TRANSLATION_Z 的和。

實際上與屬性動畫的默認屬性值基本是一致的,其本質,也是藉助了屬性動畫來實現Spring效果。

監聽

與屬性動畫同樣,Spring Animation一樣能夠監聽其動畫的生命週期,系統提供了OnAnimationUpdateListener和OnAnimationEndListener,來監聽動畫。

經過addUpdateListener來設置,代碼以下所示。

anim.addUpdateListener { _, value, _ -> anim.animateToFinalPosition(value) }

與之對應的,系統提供了removeUpdateListener() 和 removeEndListener()方法來移除監聽。

SpringForce

對應一個彈性系統來講,SpringForce是描述該彈性系統的各類參數的封裝。

SpringForce:定義動畫具備的彈簧特徵。其中有四個關鍵的參數:

  • finalPosition:靜止位置。

  • stiffness:即彈簧常數,物體的彈性係數。

  • dampingRatio:阻尼比。描述系統擾動後的振盪衰減過程。

  • velocity:運動的初速度。

在這四個參數中,finalPosition用於描述動畫最終中止的位置,而velocity則是描述物體運動的初速度,默認狀況下,velocity爲0,系統提供了setStartVelocity()方法來改變這個初速度,在大部分狀況下,咱們都不用修改。

剩下兩個屬性stiffness和dampingRatio,則是Spring Animation的核心配置參數。

阻尼比dampingRatio

阻尼比用於描述彈簧振動逐漸衰減的情況。經過使用阻尼比,能夠定義振動從一次彈跳到下一次彈跳所衰減的速度有多快。

  • 當阻尼比大於 1 時,會出現過阻尼現象。它會使對象快速地返回到靜止位置。
  • 當阻尼比等於 1 時,會出現臨界阻尼現象。這會使對象在最短期內返回到靜止位置。
  • 當阻尼比小於 1 時,會出現欠阻尼現象。這會使對象屢次通過並越過靜止位置,而後逐漸到達靜止位置。
  • 當阻尼比等於零時,便會出現無阻尼現象。這會使對象永遠振動下去。

通常來講,首先須要調用getSpring()方法來獲取當前的參數,再經過調用setDampingRatio()方法設置要增長到彈簧上的阻尼比。

系統同時也定義了一些經常使用的dampingRatio。

DAMPING_RATIO_HIGH_BOUNCY效果:

high_bounce-min

DAMPING_RATIO_MEDIUM_BOUNCY效果:

medium_bounce-min

DAMPING_RATIO_LOW_BOUNCY效果:

low_bounce-min

DAMPING_RATIO_NO_BOUNCY效果:

no_bounce-min

相信你們經過GIF,就能很快明白其含義了。

剛度stiffness

剛度定義了用於衡量彈簧強度的彈簧常量。經過setStiffness()方法來設置剛度值,相似的,系統也定義了一些默認的剛度常量。

STIFFNESS_HIGH效果:

high_stiffness-min

STIFFNESS_MEDIUM效果:

medium_stiffness-min

STIFFNESS_LOW效果:

low_stiffness-min

STIFFNESS_VERY_LOW效果:

very_low_stiffness-min

阻尼

物理動畫的另外一個經常使用場景,則是建立拉動的阻尼效果,相比生硬的控制,經過阻尼設置拉動效果,動畫會更加符合物理定律,讓動畫更加優雅。不過,設置阻尼動畫,其實並不須要Google的Spring Animation,咱們經過一個函數,便可完成阻尼效果的實現,其實,所謂的阻尼,即在拉動過程當中,將線性的拉動距離,經過一個函數變換,轉換爲非線性的遞減函數,遞減函數的斜率,即爲阻尼的力度,這樣的函數有不少,這裏介紹其中一種。

阻尼效果

private fun doDamping(value: Float): Float {
    return if (value < 0)
        -sqrt((100f * abs(value)).toDouble()).toFloat()
    else
        sqrt((100f * value).toDouble()).toFloat()
}

經過這樣一個變換,就能夠實現阻尼效果。

例如咱們將第一個場景中的拉動效果來增長阻尼效果,只須要在Move的過程當中,不斷改變偏移量便可,代碼以下所示。

MotionEvent.ACTION_MOVE -> {
    test.translationX = doDamping(event.rawX - offsetX)
    test.translationY = doDamping(event.rawY - offsetY)
}

那麼藉助這樣一個函數,就很方便的實現了變換效果,如圖所示。

zuni-min

其它

除了前面的示例外,這裏還給你們提供了一些其它屬性的使用示例。

旋轉動畫

設置ROTATION屬性,代碼以下所示。

test.setOnTouchListener { view, event ->
    val centerX = view.width / 2.0
    val centerY = view.height / 2.0
    val x = event.x
    val y = event.y

    fun updateRotation() {
        currentRotation = view.rotation + Math.toDegrees(atan2(x - centerX, centerY - y)).toFloat()
    }

    when (event.actionMasked) {
        MotionEvent.ACTION_DOWN -> updateRotation()
        MotionEvent.ACTION_MOVE -> {
            previousRotation = currentRotation
            updateRotation()
            val angle = currentRotation - previousRotation
            view.rotation += angle
        }
        MotionEvent.ACTION_UP -> SpringAnimation(
            test,
            DynamicAnimation.ROTATION
        ).animateToFinalPosition(0f)
    }
    true
}

效果以下所示。

rotate-min

縮放

設置SCALE_X和SCALE_Y屬性,代碼以下所示。

scaleGestureDetector = ScaleGestureDetector(this,
    object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
        override fun onScale(detector: ScaleGestureDetector): Boolean {
            scaleFactor *= detector.scaleFactor
            test.scaleX *= scaleFactor
            test.scaleY *= scaleFactor
            return true
        }
    })
test.setOnTouchListener { _, event ->
    if (event.action == MotionEvent.ACTION_UP) {
        SpringAnimation(test, DynamicAnimation.SCALE_X).animateToFinalPosition(1f)
        SpringAnimation(test, DynamicAnimation.SCALE_Y).animateToFinalPosition(1f)
    } else {
        scaleGestureDetector.onTouchEvent(event)
    }
    true
}

效果如圖所示。

scale-min

位移

設置TRANSLATION_Y來實現View出現的動畫效果,代碼以下所示。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    test.translationY = 1600f
    SpringAnimation(test, SpringAnimation.TRANSLATION_Y, 0f).apply {
        spring.stiffness = SpringForce.STIFFNESS_VERY_LOW
        spring.dampingRatio = SpringForce.DAMPING_RATIO_LOW_BOUNCY
        setStartVelocity(-2000f)
        start()
    }
}

效果以下所示。

up-min

KTX

在KTX中,Google還基於Spring Animation,提供了一些拓展函數,來進一步簡化Spring的使用,地址以下所示。

implementation "androidx.dynamicanimation:dynamicanimation-ktx:1.0.0-alpha03"

雖然還沒Release,雖然也並無什麼用,但爲了文章的完整性,仍是提一下吧。


本文分享自微信公衆號 - Android羣英傳(android_heroes)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索