自定義Drawable實現靈動的紅鯉魚動畫(下篇)

自定義Drawable實現靈動的紅鯉魚動畫(下篇)

自定義Drawable實現靈動的紅鯉魚動畫(下篇)

小魚兒

上篇文章自定義Drawable實現靈動的紅鯉魚動畫(上篇)我們繪製了可以擺動身體的小魚,本篇就分享一下如何讓小魚游到手指點擊的位置。用到的主要技術如下:1)、三階貝塞爾曲線

2)、Path的Measure

一、動畫分析

小魚的行走不是簡單的位移,不難看出在小魚位移的同時身體的角度還隨著前進的方向而變化,所以本篇要解決如下兩部分:

1)、魚身的位移

2)、魚身的旋轉

3)、點擊處的水波紋

二、技術分析

1)、魚身的位移

上篇介紹自定義Drawable的時候分析了Drawable需要作為ImageView的drawable資源或者作為View的background才可以顯示出來,那麼我們就可以通過ImageView.setImageDrawable()將自定義Drawable和ImageView關聯起來,通過位移ImageView來移動小魚。

為了讓魚遊動的軌跡更真實,位移路徑只有直線是不行的,在魚需要轉身的時候行走路線應該是有

弧度的曲線 ,只要涉及到曲線就少不了貝塞爾曲線,涉及到貝塞爾曲線就要涉及到貝塞爾曲線控制點的確定,這裡重點介紹一下控制點的確定問題

自定義Drawable實現靈動的紅鯉魚動畫(下篇)

三階貝塞爾曲線確定過程

上圖對關鍵點都做了簡單標註,控制點確定過程如下:

1):利用頭部圓心、魚身的重心以及點擊點座標來唯一確定一個特徵三角形。

2):確定魚身需要向左還是向右轉彎,這是個很關鍵的問題。我們知道,對於同一目的地,向右轉和向右轉動都可以到達,但是一定有一個最優的方案,假設我們的小魚有魚類智商,那麼能轉45°能到達就肯定不會轉315°,結合這個理論和1)的特徵三角形,可以知道三角形內角AOB就是我們要的轉動的角度,知道轉動的角度那麼轉動方向自然而然就知道了。現在我們只有AOB三點的座標如何求出夾角呢?我們可以 利用向量的夾角公式計算夾角cosAOB = (OA*OB)/(|OA|*|OB|)其中OA*OB是向量的數量積,計算過程如下

OA=(Ax-Ox,Ay-Oy)

OB=(Bx-Ox,By-Oy)

OAOB=(Ax-Ox)(Bx-Ox)+(Ay-Oy)*(By-Oy)

|OA|表示線段OA的模即OA的長度,如果對向量不太瞭解的朋友請自行百度一下。3):知道了向左轉還是向右轉就可以確定曲線的控制點,上圖控制點是我憑經驗和多次實踐確定的比較好的方案第一個控制點就是頭部的圓心處,第二個控制點就是轉動方向的1/2上的一點

好了,上述的控制點確定之後就可以實用A點、A點、C點、M點來確定一條三階貝塞爾曲線了

4):那麼問題來了我們拿到貝塞爾曲線如何讓

ImageView移動呢?我們經常看到各大直播平臺送主播禮物時那些小禮物不規則地向上升是怎麼實現呢?原理都差不多,無非就是讓控件跟著路徑走。傳統的做法是利用自定義估值器來計算出動畫行走路徑,還有一種方法可以不用自定義估值器,LOLLIPOP版本出來之後屬性動畫裡新增了一個路徑動畫,我們只用丟進去一個控件和一條路徑以及模板參數就能讓控件跟著這個路徑走,方法如下

自定義Drawable實現靈動的紅鯉魚動畫(下篇)

需要明確一點,這裡的位移只是平移,也就是說魚的角度不會因為控件轉動而改變,要想讓魚在轉彎的時候沿路徑切線方向轉動請聽我繼續分析.

2)、魚身的旋轉

計算魚身的旋轉角度只用計算出路徑切線方向即可,數學裡的切線和導數是掛鉤的,初代版本我是通過自定義估值器來確定路徑的,自定義估值器的時候可以求出當前時刻三階貝塞爾曲線的導數,那是一個痛苦的過程,公式代碼寫了十幾行,而且效果不好。後來發現一個強大的類PathMeasure,我們可以通過

自定義Drawable實現靈動的紅鯉魚動畫(下篇)

計算出一條Path的總長度,還可以通過

getPosTan(float distance, float pos[], float tan[])

根據傳入的長度計算出路徑的某點座標和切線方向,簡直就是為我們量身定做的。其中參數distance就是我們需要計算切線的點距Path的起點的距離,通過在AnimatorUpdateListener中獲取Animator的當前進度,再與路徑總長度相乘,就得到了當前動畫已行走的路徑長度distance,接下來傳入兩個長度>=2的非空數組pos和tan數據就可以得到座標和切線角度的相關參數了。

pos數組的前兩個值就是x,y的座標值

tan前兩個值就是所求角的對邊和臨邊的相對長度值(也有可能是絕對長度,因為無法看到native源碼,但是不管是相對的還是絕對的這兩個值的比例知道就可以求出對應的角度了)

3)、點擊處的水波紋

水波紋效果比較簡單,只需改變圓環的大小和透明度即可,代碼部分會詳細說明。

分析完位移旋轉

,做一個效果圖看看大家就更清楚了。為了讓大家更清晰地看出效果我把ImageView背景設置成藍色,可以看出藍色的ImageView只負責平移並沒有旋轉,旋轉效果是Drawable中的小魚執行的。

自定義Drawable實現靈動的紅鯉魚動畫(下篇)

ImageView的平移

三、代碼實現

文章只貼出主要代碼,完整代碼文末提供鏈接

最重要的特徵三角夾角計算代碼

注意點:

1)、變量abc是向量ab和ac的數量積

2)、angleCos是弧度值表示的,真正的角度需要通過Math.toDegrees轉換成角度制

自定義Drawable實現靈動的紅鯉魚動畫(下篇)

三階貝塞爾曲線生成代碼

其中:

1)、fishMiddle 是確定魚身重心

2)、fishHead獲取魚頭圓心

3)、angle即通過夾角計算方法計算出特徵三角形的夾角

4)、delta是魚身的角度,angle/2+delta就可以得出特徵三角形夾角中線跟x軸正方向的角度了

有了起點fishMiddle,轉動的長度1.6R以及轉動的角度(angle/2+delta)就可以通過(上篇)的calculatPoint()方法計算出控制點的座標了,有了控制點就可以通過cubicTo函數得到三階貝塞爾曲線了

自定義Drawable實現靈動的紅鯉魚動畫(下篇)

魚身轉動代碼

其中:

1)、tan數組變量就是我們存取正切值的兩個邊的信息數組,通過public static native double atan2(double y, double x);得到切角的弧度值,轉換為角度即可算出轉動角度。細心的朋友發現Math.atan2(-tan[1], tan[0])中的y值前邊有一個負號“-”,這是為了適配Android座標Y的正方向和自然直角左邊系Y軸方向相反的情況。

2)、因為我們用不到座標點信息所以在getPosTan(float distance, float pos[], float tan[])中傳入的pos數組是null

3)、在動畫監聽回調中獲取到實時角度angle = (float) (Math.toDegrees(Math.atan2(-tan[1], tan[0])))

自定義Drawable實現靈動的紅鯉魚動畫(下篇)

水波紋代碼

代碼比較簡單,需要注意的是ofFloat中的“radius”關鍵字,我們知道默認的屬性動畫關鍵字有"alpha"、"scaleX"、"scaleY"、"rotationX"、"rotationY"、"Y"等等,唯獨沒有“radius”關鍵字,對的我們自己定義的,ObjectAnimator的ofFloat(Object target, String propertyName, float... values)方法會通過反射技術在參數target中尋找關鍵字對應的set方法,即我們需要在“this”類中定義一個setRadius(參數)方法,其中的參數是我們定義的浮點數0~1中的過程值,通過setRadius方法改變水波紋的alpha和半徑值,形成水波紋擴散和漸隱的效果

自定義Drawable實現靈動的紅鯉魚動畫(下篇)

最後需要注意一點如上代碼都是寫在一個繼承了RelativeLayout的自定義ViewGroup中,ViewGroup中onDraw的觸發和View中不一樣,需要在繪製前寫上一句setWillNotDraw(false)來打開強制繪製功能,否則水波紋無法顯示。

四、結語

(上篇)得到了很多朋友的支持,非常感動,謝謝大家給予我的鼓勵。 動畫是個很靈活的事情其實大家可以找找不同的思路來實現,本篇小魚的轉動並不完美,但是我還沒找到更好的轉彎方法,希望有有更好思路的朋友多多交流。

-----點擊上方關注持續收聽面試乾貨

自定義Drawable實現靈動的紅鯉魚動畫(下篇)

私信 “666” 索要Android高級視頻(Flutter進階,插件化,熱修復技術)


分享到:


相關文章: