簡述
本文探討下如何在 Android 上實現(xiàn)阻尼動畫涧衙,首先 wiki 下阻尼的定義:是指任何振動系統(tǒng)在振動中嗅定,由于外界作用(如流體阻力碍彭、摩擦力等)和/或系統(tǒng)本身固有的原因引起的振動幅度逐漸下降的特性癣疟,也就是阻尼系統(tǒng)由兩個子系統(tǒng)組成(振子系統(tǒng)、阻力系統(tǒng))惕虑,先前寫過一篇 如何優(yōu)雅地在Android上實現(xiàn)iOS的圖片預覽
坟冲,就是一套阻尼動畫的實現(xiàn)
不知道阻尼動畫效果的同學可以去玩玩 iOS,iOS 的 UI 系統(tǒng)內置一套物理引擎溃蔫,幾乎所有的滾動界面都有阻尼動畫效果(越界回彈)
在討論阻尼動畫之前健提,我先講一下本人編寫動畫的歷程,分為 3 個階段(位置突變動畫伟叛,位置不突變動畫私痹,速度不突變的動畫)
位置突變動畫
此動畫是 Android 里面最簡單的動畫,我們平常寫的 ValueAnimator 動畫、xml 動畫紊遵,其實都是位置突變的動畫雹锣,為什么叫它位置突變的動畫呢,比如我要把一個 View 漸變出來癞蚕,那么 alpha 數(shù)值應該是從 0 -> 1,但是假如在漸變動畫還沒有結束的時候(假如剛好播放到 0.5)辉哥,我又想把 View 給漸隱掉桦山,此時一般會播放一個 1 -> 0 的動畫,這里就產生了位置突變效應(從 0.5 突變成了 1)醋旦,造成的視覺效果就是視圖閃動(等同于游戲里面的跳幀)
位置不突變動畫
在上述動畫的基礎上恒水,對于第二段的漸隱動畫,我不從 1 -> 0 開始變化饲齐,我在播放漸隱動畫前先獲取 View 的透明度(0.5)钉凌,然后讓動畫從 0.5 -> 0 開始變化(視覺上沒有了閃動現(xiàn)象)
速度不突變動畫
位置不突變動畫一般都能滿足要求了(系統(tǒng)自帶的 ViewPropertyAnimator
就是位置不突變動畫),但是為什么 iOS 的動畫就看起來這么舒服捂人,而安卓的動畫看起來有點死板呢御雕?這邊就存在一個速度突變的問題
速度突變動畫↓
速度不突變動畫↓
圖片來自 谷歌動畫文檔
我想,應該可以很明顯的看出這兩者的動畫區(qū)別滥搭,當動畫的目標位置發(fā)生變化之后酸纲,動畫1 給人一種非常突兀的感覺,而動畫2 給人一種絲一般的順滑
速度不突變最理想的編寫方式就是引入一套物理引擎瑟匆,然后基于這套物理引擎開發(fā)動畫闽坡,但是成本很大,這也是為什么 iOS 的動畫這么絲滑的原因(內置物理引擎)
實現(xiàn)
谷歌大佬也發(fā)現(xiàn)了這個問題的愁溜,所以它在 support 26 版本中加入了 DynamicAnimation
疾嗅,這是一套基于物理狀態(tài)的動畫系統(tǒng),顧明思議冕象,他內置了一套簡單的物理引擎代承,它有默認的兩個子類實現(xiàn) SpringAnimation
(彈簧動畫)、FlingAnimation
(滑動動畫)交惯,下面我就用這兩個動畫來實現(xiàn)我想要的阻尼動畫
先看一個簡單的 demo
思考一下次泽,如何實現(xiàn)這個動畫,最容易想到的就是用系統(tǒng)的 OverScroller席爽,但是此類的定制性比較差意荤,和阻尼效果差別很大。另一種方式就是分段動畫只锻,滾動的時候用 Scroller 實現(xiàn)玖像,越界的時候用 ValueAnimator 來實現(xiàn),這種方式首先編碼很麻煩:至少要分成 3 段動畫(滑動 -> 越界 -> 回彈),其次它是一個速度突變的動畫捐寥,表現(xiàn)上來說總會給人一種違和感
自定義阻尼系統(tǒng)
此方案就是自己模擬物理引擎笤昨,理論上可以實現(xiàn)物理引擎所支持的動畫,但是需要了解一定的物理知識和數(shù)學知識握恳,代碼實現(xiàn)繁瑣瞒窒,但是很有意思,有興趣的同學可以看我之前寫的一篇文章 如何優(yōu)雅地在Android上實現(xiàn)iOS的圖片預覽
借用 DynamicAnimation
這個是谷歌為了解決物理動畫而出的一個 support 庫乡洼,對它的描述叫 Physics-based motion崇裁。它的其中一個子類 SpringAnimation
其實就是彈簧動畫,也就是我想要的阻尼系統(tǒng)束昵。但是要實現(xiàn) demo 中的動畫拔稳,只有 SpringAnimation
是不夠的,還需要一個 fling 動畫锹雏,用于滑動巴比,剛好 DynamicAnimation
還有一個子類就是 FlingAnimation
那么接下來的問題就是如何結合這兩個動畫,擼出一個 SpringFlingAnimation
物理動畫的實現(xiàn)方式礁遵,無非就是逐幀計算轻绞,每一幀根據(jù)當前的 (位置、速度榛丢、加速度)計算出下一幀的(位置铲球、速度、加速度)晰赞,所以我只需要在越界的時候用 SpringAnimation
的邏輯去計算稼病,非越界的時候用 FlingAnimation
的邏輯去計算,就可以達到我們想要的效果
例:
如圖所示掖鱼,黑框是父布局然走,可以看做一個 ScrollView,橘色的框為內部的子 View戏挡,1 的時候上滑采用
FlingAnimation
去計算幀芍瑞,2 的時候因為越界了所以用SpringAnimaiton
的邏輯來計算幀,3 的時候繼續(xù)采用FlingAnimation
思路理清楚后是不是感覺很簡單褐墅,其實實現(xiàn)方式也很簡單拆檬,SpringAnimation
和 FlingAnimation
里面的代碼本來就沒多少,阻尼系統(tǒng)的邏輯運算都封裝在 SpringForce
里面了妥凳,我們只需要繼承自 DynamicAnimation
竟贯,復合一下兩個動畫的計算幀邏輯就可以了
PS:
DynamicAnimation
的虛方法都是包權限的,也就是只有包權限的類才可以繼承
具體實現(xiàn)可以看我的 Github 項目 SpringFlingAnimation.java逝钥,上述 demo 的簡單實現(xiàn)代碼 SampleDampNestedScrollView.java