Android 使用cos和sin繪制復(fù)合曲線動畫
前言
前兩周在開發(fā)新需求的時(shí)候,設(shè)計(jì)給了一份類似這樣的動畫:

看著不難,即使一遍看不懂,嘿嘿,不還有設(shè)計(jì)稿。

作為一個(gè)平時(shí)很少寫動畫的 Android 開發(fā)仔,看到一段段的緩入緩出曲線的設(shè)計(jì)稿時(shí),我的心情是這樣的:

雖然,Android 動畫默認(rèn)的插值器 AccelerateDecelerateInterpolator 有這樣緩入緩出的效果:

我總不能一整個(gè)動畫給它拆成4段動畫來寫,還別說,我第一次寫的代碼還真的是這么干的。
第一次分析
本著能少寫一行絕不多寫一字的原則,詢問了大佬同事的意見,大佬大手一揮:PathInterpolator(后證實(shí)有問題)。
簡單看了一下使用方式,需要使用 Path,再看了一眼,好家伙,有可能會用到貝塞爾曲線,放棄~
為了能夠快速的解決問題,就使用了上面談到的方案:
private fun animateTagView(tagView: TextView) {
// [0,200]區(qū)間的動畫
val valueAnimatorOne = ValueAnimator.ofInt(0, 200)
valueAnimatorOne.addUpdateListener {
val per = it.animatedValue as Int / 200f
tagView.rotation = 4 * per
tagView.scaleX = (1 - 0.1 * per).toFloat()
tagView.scaleY = (1 - 0.1 * per).toFloat()
}
valueAnimatorOne.duration = 200
// [200,560]區(qū)間的動畫
val valueAnimatorTwo = ValueAnimator.ofInt(200, 560)
valueAnimatorTwo.addUpdateListener {
val per = (it.animatedValue as Int - 200) / 360f
tagView.rotation = 3 - 11 * per
tagView.scaleX = (0.9 + 0.1 * per).toFloat()
tagView.scaleY = (0.9 + 0.1 * per).toFloat()
}
valueAnimatorTwo.duration = 360
// [560,840]區(qū)間的動畫
val valueAnimatorThree = ValueAnimator.ofInt(560, 840)
valueAnimatorThree.addUpdateListener {
val per = (it.animatedValue as Int - 560) / 280f
tagView.rotation = -8 + 12 * per
tagView.scaleX = (1 - 0.2 * per).toFloat()
tagView.scaleY = (1 - 0.2 * per).toFloat()
}
valueAnimatorThree.duration = 280
// [840,1000]的動畫
val valueAnimatorFour = ValueAnimator.ofInt(840, 1000)
valueAnimatorFour.addUpdateListener {
val per = (it.animatedValue as Int - 840) / 160f
tagView.rotation = 4 - 4 * per
tagView.scaleX = (0.8 + 0.2 * per).toFloat()
tagView.scaleY = (0.8 + 0.2 * per).toFloat()
}
valueAnimatorFour.duration = 160
// 使用AnimatorSet串行執(zhí)行動畫
val animationSet = AnimatorSet()
animationSet.playSequentially(valueAnimatorOne, valueAnimatorTwo, valueAnimatorThree, valueAnimatorFour)
tagView.post {
tagView.pivotX = 0f
tagView.pivotY = ad_tag_two.measuredHeight.toFloat()
animationSet.start()
}
}
整個(gè)動畫被我拆成了[0,200]、[200,560]、[560,840]和[840,1000]四段屬性動畫,因?yàn)楫a(chǎn)品說只需要播放一次,所以使用 AnimatorSet 將動畫組裝起來,就可以解決問題。
第二次分析
第一次得到的方案雖然能夠解決問題,如果遇到循環(huán)播放,AnimatorSet 就不行了,有沒有其他方案呢?
趁著周末的時(shí)間,學(xué)了一下 PathInterpolator,發(fā)現(xiàn)這個(gè)玩意也解決不了問題,或者說不好解決問題,雖然可以用三階貝塞爾曲線分段畫出上述曲線,但 PathInterpolator 要求起點(diǎn)和終點(diǎn)分別在 (0,0) 和 (1,1)。
既然插值器不行,可以試試估值器,但一個(gè)估值器也解決不了旋轉(zhuǎn)和縮放兩種動畫,看來得靠 AnimatorUpdateListener 去解決問題。
回頭想一下,插值器是將均勻的時(shí)間片段轉(zhuǎn)化成加速或者減速的行為,我們也可以將均勻的時(shí)間片段轉(zhuǎn)化成對應(yīng)的曲線,只要做好兩點(diǎn):
使用線性的插值器 LinearInterpolator。
將上面的曲線拆分,通過不同的 sin 或者 cos 方法表達(dá)。
以旋轉(zhuǎn)動畫為例,拆成的 sin 函數(shù):

另外一段動畫的函數(shù)可以參考代碼:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val tvContent = findViewById<TextView>(R.id.tv_content)
val valueAnimatorOne = ValueAnimator.ofFloat(0.0f, 1.5f)
valueAnimatorOne.addUpdateListener {
// 通過對應(yīng)的sin和cos設(shè)置rotation和scale
val per = it.animatedValue as Float
var rotation: Float = 0f
var scale: Float = 0f
if(per >= 0 && per < 0.2f){
rotation = sin((per / 0.2f) * Math.PI.toFloat() - Math.PI.toFloat() / 2) * 1.5f + 1.5f
scale = cos(per / 0.2f * Math.PI.toFloat()) * 0.05f + 0.95f
}
if(per >= 0.2f && per < 0.56f){
rotation = sin(Math.PI.toFloat() / 2 + Math.PI.toFloat() * ( per - 0.2f) / 0.36f) * 5.5f - 2.5f
scale = cos((per - 0.2f) / 0.36f * Math.PI.toFloat() + Math.PI.toFloat()) * 0.05f + 0.95f
}
if(per >= 0.56f && per < 0.84f){
rotation = sin(Math.PI.toFloat() * (per - 0.56f) / 0.28f - Math.PI.toFloat() / 2) * 6f - 2f
scale = cos((per - 0.56f) / 0.28f * Math.PI.toFloat()) * 0.1f + 0.9f
}
if(per in 0.84f..1f){
rotation = sin(Math.PI.toFloat() / 2 + Math.PI.toFloat() * (per - 0.84f) / 0.16f ) * 2f + 2f
scale = cos((per - 0.84f) / 0.16f * Math.PI.toFloat() + Math.PI.toFloat()) * 0.1f + 0.9f
}
// 設(shè)置停止時(shí)間
if(per > 1f && per <= 1.5f){
rotation = 0f
scale = 1.0f
}
tvContent.rotation = rotation
tvContent.scaleX = scale
tvContent.scaleY = scale
}
// 設(shè)置線性插值器
valueAnimatorOne.interpolator = LinearInterpolator()
// 動畫時(shí)間
valueAnimatorOne.duration = 1500
// 無線循環(huán)
valueAnimatorOne.repeatCount = -1
tvContent.post {
// 設(shè)置中心點(diǎn)
tvContent.pivotX = 0f
tvContent.pivotY = tvContent.measuredHeight.toFloat()
valueAnimatorOne.start()
}
}
整個(gè)代碼還是比較簡單的,旋轉(zhuǎn)動畫曲線由 sin 得出,縮放由 cos 得出,最后改一下中心點(diǎn)。
總結(jié)
本次的動畫案例不難,在面對復(fù)合緩入緩出曲線的情形,我們可以拆成一段段,用 sin 或者 cos 去描述,這樣的好處是可以只使用一個(gè)屬性動畫,且可以循環(huán)播放。
如果你有更好的方案,歡迎評論區(qū)交流。
以上就是Android 使用cos和sin繪制復(fù)合曲線動畫的詳細(xì)內(nèi)容,更多關(guān)于Android 繪制復(fù)合曲線動畫的資料請關(guān)注腳本之家其它相關(guān)文章!
- Android實(shí)現(xiàn)動畫效果的自定義下拉菜單功能
- Android自定義view仿QQ的Tab按鈕動畫效果(示例代碼)
- Android自定義view之圍棋動畫效果的實(shí)現(xiàn)
- Android動畫系列之屬性動畫的基本使用教程
- android實(shí)現(xiàn)加載動畫對話框
- Android動畫系列之幀動畫和補(bǔ)間動畫的示例代碼
- Android實(shí)現(xiàn)長按圓環(huán)動畫View效果的思路代碼
- android自定義環(huán)形統(tǒng)計(jì)圖動畫
- android實(shí)現(xiàn)截圖并動畫消失效果的思路詳解
- Android 自定義加載動畫Dialog彈窗效果的示例代碼
- Android自定義帶動畫效果的圓形ProgressBar
- Android實(shí)現(xiàn)美團(tuán)外賣底部導(dǎo)航欄動畫
相關(guān)文章
Android應(yīng)用中圖片瀏覽時(shí)實(shí)現(xiàn)自動切換功能的方法詳解
這篇文章主要介紹了Android應(yīng)用中圖片瀏覽時(shí)實(shí)現(xiàn)自動切換功能的方法,文中還講解了一個(gè)觸摸大圖進(jìn)行圖片切換的深入功能,需要的朋友可以參考下2016-04-04
Android存儲字符串?dāng)?shù)據(jù)到txt文件
這篇文章主要為大家詳細(xì)介紹了Android存儲字符串?dāng)?shù)據(jù)到txt文件,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-10-10
Android之ScrollView嵌套ListView和GridView沖突的解決方法
由于ListView,GridView本身都繼承于ScrollView,一旦在ScrollView中嵌套ScrollView,在ScrollView中嵌套使用ListView或者GridView,ListView只會顯示一行多一點(diǎn)。兩者進(jìn)行嵌套,即會發(fā)生沖突2013-09-09
Android App調(diào)用MediaRecorder實(shí)現(xiàn)錄音功能的實(shí)例
這篇文章主要介紹了Android App調(diào)用MediaRecorder實(shí)現(xiàn)錄音功能的實(shí)例,MediaRecorder非常強(qiáng)大,不僅能夠用來錄制音頻還可以錄制視頻,需要的朋友可以參考下2016-04-04
Android仿考拉全局滑動返回及聯(lián)動效果的實(shí)現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于Android仿考拉全局滑動返回及聯(lián)動效果的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08
Android實(shí)現(xiàn)退出界面彈出提示對話框
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)退出界面彈出提示對話框,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12
Android實(shí)現(xiàn)文字動態(tài)高亮讀取進(jìn)度效果
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)文字動態(tài)高亮讀取進(jìn)度效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-05-05
Android之自定義實(shí)現(xiàn)BaseAdapter(通用適配器一)
這篇文章主要為大家詳細(xì)介紹了Android之自定義實(shí)現(xiàn)BaseAdapter通用適配器第一篇,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-08-08

