如何在Android中實現折紙動畫
受到iOS版的啟發,在最近的項目中,我們決定在打開列表元素時實現一個類似風格的動畫效果。起初,我們試圖使用一個現有的實現——android-flip, 通過OpenGL渲染動畫——在***的Android版本中,它只做到了在屏幕上顯示比較明顯的工件(圖片閃爍)。另外,需要對這個類庫進行改動,因為它 是為了滑動列表的元素設計的,但我們的工程需要在打開列表元素的時候有動畫效果。在下面的演示視頻中可以看出區別:android-flip實現的是折疊 列表,而我們實際需要的是展開詳情。
考慮到這種場景,我們決定自己實現這種效果。因為應用程序支持的最小Android版本為4.0,所以沒使用OpenGL,而是用了標準Android SDK中的方法:View.setRotationX(),View.setScaleX()等。當硬件加速啟用時(如果目標API級別>=14,默認啟用硬件加速),這些方法可以非常高效地利用GPU。
結果看起來很***,所以決定分享我們的做法。由于本文只是介紹了基本的實現要點,您可以從GitHub上下載所有的實際代碼:FoldableLayout。
布局的實現
設計的***個元素是可以對折的布局。我們的做法相當大膽:主布局(FoldableItemLayout)只包含一個特定的布局(在baselayout)。在動畫中,BaseLayout將它的內容寫入到緩存中,這是一個根據原始布局的尺寸專門創建的Bitmap對象。
- class FoldableItemLayout extends FrameLayout {
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- Bitmap cacheBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
- mBaseLayout.setCacheCanvas(new Canvas(cacheBitmap));
- }
- }
- class BaseLayout extends FrameLayout {
- private Canvas mCacheCanvas;
- private void setCacheCanvas(Canvas cacheCanvas) {
- mCacheCanvas = cacheCanvas;
- }
- @Override
- public void draw(Canvas canvas) {
- mCacheCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
- super.draw(mCacheCanvas);
- }
- }
此外,還需要兩個額外的視圖(PartView)——用于圖像的上、下兩半部分。它們將在緩存中顯示對應的數據,這些數據代表了該圖像(Bitmap)的上半部和下半部。兩個視圖填充了主布局的整個區域,但只顯示所需的部分。為了達到這種效果,我們計算了位圖的界限——在onDraw()方法中,我們讓畫布通過[drawBitmap (Bitmap bitmap, Rect src, RectF dst, Paint paint)](http://developer.android.com/reference/android/graphics/Canvas.html#drawBitmap(android.graphics.Bitmap, android.graphics.Rect, android.graphics.RectF, android.graphics.Paint))方法來繪制所需的部分。
然后通過setRotationX()方法設置相應的角度,設法旋轉這些額外的視圖,從而實現圖像上半部和下半部的獨立旋轉。為了實現這個功能,我們為FoldableItemLayout添加了一個名為FoldRotation的新參數。
FoldRotation參數范圍是(-180,180]:
- FoldRotation=0:兩個部分都不旋轉。在這種情況下,我們可以跳過位圖緩存,實時的顯示原始的布局。
- 0 <FoldRotation<90:下層的部分旋轉到 FoldRotation角度;上層部分不旋轉。
- -90<FoldRotation<0:只有上層部分旋轉。
- 90≤FoldRotation<180:下層部分不再顯示。在這種情況下,包含下一布局的FoldableItemLayout應該覆蓋當前的FoldableItemLayout。
- -180<FoldRotation≤-90:上層部分不再顯示。在這種情況下,包含先前的布局FoldableItemLayout應該覆蓋當前的FoldableItemLayout。
- FoldRotation=180:兩個部分都隱藏。
現在有了一個二層布局,能夠“折疊”它包含的元素,這樣就可以做出一個FoldableListLayout——一個類似列表視圖的布局,它創建列表元素,并通過使用BaseAdapter將其封裝成FoldableItemLayout。在這種情況下,我們還使用了FoldRotation參數用來確定元素在列表中的位置。
例如,FoldRotation= 30,列表***個元素(FoldableItemLayout)的FoldRotation值為30,而第二個元素——FoldRotation= 150,最多可以同時顯示不超過2個元素。FoldRotation參數值的范圍依賴于元素的數量:如果列表包含一個元素,那么取值范圍就會是 [0,0],2——[0,180],3——[0,360]等。
打開動畫
在學會了在幾個元素之間使用折疊動畫滾動之后,我們解決了這個重大的挑戰:從任意的起點生成元素的打開動畫。利用已經實現的 FoldableListLayout, 并使其在兩個元素之間進行切換:封面布局和詳情布局。這兩種元素都應該顯示在屏幕上,但詳情元素應該是隱藏的。當用戶點擊一個封面元素時,應用程序會記住 當時的位置,并使用相同大小的空占位符視圖取代它(以免破壞屏幕上的其他元素),并移動蓋元素的下半專門創建的布局。以后這種布局將被用作 FoldableListLayout的***個元素。第二個元素將被替換為空占位視圖,與封面元素的方式相同。
對齊
眼見封面元素已經從屏幕上的最初位置展開,因此在動畫過程中我們的FoldableListLayout需要在封面元素和詳情元素的位置之間移動。這就是為什么要記住在動畫初始化過程中每個元素的初始位置和大小的原因。由于封面和詳情的大小有可能不同,我們需要在動畫期間同時擴展它們,以便使它們的寬度一致。
我們基本已經完工,只差一件事:縮放后,封面的高度可能看起來比下半部分的詳情小。這意味著你需要隱藏圖像的剩余部分。
下面的屏幕截圖顯示了在動畫的開始部分有一個灰色區域,下層的詳情部分與封面的大小不符。
我們為FoldableItemLayoutm引入一個額外的RollingDistance參數解決了這個問題。這個參數負責從折線垂直移動圖像。使用這個參數可以在動畫的***部分中的不知不覺中轉換詳情的一部分,然后在第二部分中全尺寸展開。
動畫現在就可以運行了,余下的工作就是為逼真的效果添加一些暗化,或為平滑的樣子添加些陰影。本類庫在GitHub上可以隨意使用,在那里你還可以找到一個使用例子。一如既往的歡迎pull請求。
原文鏈接: azoft