Android launcher 的平滑和立體翻頁效果

來源:互聯網
上載者:User

我們這裡把 Android launcher 程式的 Workspace 相關的代碼抽取出來,以一個比較簡單的代碼來展示 launcher 程式是如何?多頁以及不同
頁面之間的轉場效果。本範例程式碼在 SDK 2.1 中運行,設定的是 WVGA 的螢幕大小。
首先我們來看一下程式啟動並執行效果來一些感性的認識。





接著我們來看一下程式 UI(即 View 和 ViewGroup)的布局,Activity 的 ContentView 是 layout 中的 main.xml。它的內容如下:


其中 FlatWorkspace 的基類是 Workspace,它繼承自 ViewGroup,是一個容器類,其中包含三個子 View,子 View 是 ImageView。三個
ImageView 就是三個頁面。這三個 ImageView 的建立是在 WorkspaceActivity 的 onCreate 函數中調用 Workspace 的 initScreens 函數完成
的,代碼如下:
清單 2
ViewGroup.LayoutParams p = new iewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,ViewGroup.LayoutParams.FILL_PARENT); for (int i = 0; i < 3; i++) { this.addView(new ImageView(this.getContext()), i, p); } ((ImageView)this.getChildAt(0)).setImageResource(R.drawable.image_search); ((ImageView)this.getChildAt(1)).setImageResource(R.drawable.image_system); ((ImageView)this.getChildAt(2)).setImageResource(R.drawable.image_top);


為了讓三個頁面達到的視窗布局,我們對 Workspace 的 onMeasure 和 onLayout 函數進行了重載,重點在 onLayout 代碼中。onLayout 
函數調用 layoutScreens 函數完成布局,FlatWorkspace 中的 layoutScreens 實現如下:
清單 3
protected void layoutScreens() { int childLeft = 0; final int count = getChildCount(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != View.GONE) { final int childWidth = child.getMeasuredWidth(); child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight()); childLeft += childWidth; } } }

上面 child.layout 部分的代碼把三個頁面分別布局到了 X 和 Y 座標系中的((0,0)-(ScreenWidth,ScreenHeight))和((ScreenWidth,
0)-(2*ScreenWidth,ScreenHeight))以及((2*ScreenWidth,0)-(3*ScreenWidth,ScreenHeight))三個矩形地區中,這裡用矩形區
域的左上方頂點座標和右下角的頂點座標來表示矩陣。
至此我們已經完成了整個視窗頁面的布局,視窗頁面的布局大小是實際可視螢幕寬度的三倍,所以要顯示所有頁面需要讓頁面滾動。

下面來看使用者 touch move 的時候程式如何讓頁面進行滑動,並且繪製他們。
頁面的滑動可以調用 View 的 scrollBy 或 ScrollTo 函數,在 Workspace 的 onTouchEvent 函數中取得使用者的手指移動的距離,然後調用 
scrollBy(它的參數就是 X 和 Y 軸上需要移動的距離)來讓 Workspace 這個 View(也是 ViewGroup)移動使用者手指移動的距離,當然 View 
移動之前得判斷一下使用者手指移動的距離和速度是否足夠才進行移動,以此減少使用者的誤操作。這部分代碼簡單就不進行深入分析了,請大家
自己看看代碼。
當 Workspace 這個 View 調用 scrollBy 進行 View 的滾動時,必然導致這個 View 無效,從而被系統重新繪製,所以它的 dispatchDraw 函數會
被調用來進行子 View(ImageView)的繪製,它本身沒有什麼東西要繪製,所以就不用關心 Workspace 的 onDraw 函數了。dispatchDraw 函
數會調用 drawScreens(canvas) 來對子 View 進行繪製。我們來看一下 FlatWorkspace 的實現:
清單 4
protected void drawScreens(Canvas canvas) { final long drawingTime = getDrawingTime(); final int count = getChildCount(); for (int i = 0; i < count; i++) { drawChild(canvas, getChildAt(i), drawingTime); } }

這裡的 canvas 寬高就是螢幕可視範圍的大小(如 HVGA 螢幕的 320 × 480 大小),而三個子 ImageView 的布局要超出螢幕的範圍,不在螢幕
可視範圍之內的部分是不會被繪製的。這個繪製三個子 ImageView 的函數很重要,是製作立方體翻頁等特效的關鍵地方,FlatWorkspace 實現
的是平滑滑動效果,所以我們直接繪製三個子 ImageView。如果要實現立方體的效果,在繪製三個子 ImageView 的時候就要讓它們被繪製的
時候有立體感,這個在 android 中我們可以通過上文提到的 Camera 類沿 Y 軸旋轉一定的角度實現。
程式讓使用者進行 touch move 操作的目的是讓使用者選擇一個頁面,如果按照上面的實現,當使用者最後抬起手指時,頁面切換不會很徹底,而是象
圖 1 一樣停留在兩個頁面之間。所以當使用者抬起手指時程式需判斷一下移動到下一個完整的頁面還有多大距離,然後讓 Workspace 這個 View 
再移動這個距離一遍完整的切換到下一頁。在這個移動的過程中,為了給使用者一個平滑的感覺,不能一下就移動這個距離,而是需要給一定的
時間間隔,在這個時間段裡逐漸的移動到位,所以這裡我們使用 Scroller 類的方法實現逐漸的移動。具體過程是在 Workspace 的 
onTouchEvent 函數中檢測到使用者 touch up(抬起手指)時進行應該調整到哪個頁面的判斷,然後調用 snapToScreen(targetScreen) 跳轉到需
要目的頁面,然後它調用 scrollToScreen(screen) 讓 Workspace 這個 View 進行需要的滾動,這個函數在 FlatWorkspace 中的實現如下:
清單 5
public void scrollToScreen(int screen) { final int newX = screen * getWidth(); final int deltaX = newX - getScrollX(); Log.e("FlatWorkspace","scrollToScreen call mScroller.startScroll"); mScroller.startScroll(getScrollX(), getScrollY(), deltaX, getScrollY(), Math.abs(deltaX) * 2); invalidate(); }

這裡的重點是 mScroler.startScroll 部分的代碼,它讓 Workspace view 在時間段 Math.abs(deltaX) * 2 裡移動下一個目標頁面可視化需要移動
的距離 deltaX(及目的頁面的座標減去目前已經移動的距離),大家請好好看一下這個 deltaX 的計算,這裡不細說了。這個 
mScroller.startScroll 並不會導致 Workspace 立即進行移動,它只會導致當前 View 無效,從而重新繪製,在 Workspace 被它的父親 View 
調用繪製的時候,它的 computeScroll 函數會被調用,所以會在這個函數中讓 Workspace 調用 scrollTo 函數進行實際的移動。代碼如下:
清單 6
public void computeScroll() { if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); //postInvalidate(); } else if (mNextScreen != INVALID_SCREEN) { mCurrentScreen = mNextScreen; mNextScreen = INVALID_SCREEN; } }

至此,我們對 Workspace 的整個運行機制和平滑移動的效果是如何?的已經介紹完成了。下面我們來具體談談立體翻頁效果是如何?的。
通過前面的分析可知,立體翻頁效果可以在平滑翻頁效果的基礎上通過改寫三個子 ImageView 的繪製來完成。同時可知,翻頁時使用者操作過程
分為三步:放下手指觸控螢幕幕,移動手指,抬起手指。手指觸控螢幕幕表示頁面之間的滑動要開始了;移動手指的時候頁面應該跟著使用者手指的
移動距離進行對應距離的移動,同時系統會根據頁面的移動位置對 Workspace 裡面的三個子 View(即頁面)進行繪製;抬起手指的時候判斷
應該移動到哪個頁面,還需要移動多少距離,然後平滑的移動需要的距離來跳轉到目的頁面上。
為了顯示立體效果,對每個子 ImageView 的繪製時得想辦法讓它沿 Y 軸旋轉一定的角度,前面已經提到 android 通過 Camera 這個類提供了
這個功能,不需要使用 opengl ES 的東西,當然如果要做出更好的 3D 效果,我們就需要 opengl ES 的強大功能了。既然要旋轉一定的角度,
那這個角度怎麼計算呢?我們把這個角度和使用者手指移動的距離關聯起來。因為這個立方體只會沿著 Y 軸旋轉,我們只看這三個面的立方體的
頂部就夠了,它的頂部沿著 Y 軸的往其箭頭指示的方向看是一個等邊三角形,每個面相對於手機螢幕的沿著 Y 軸旋轉的角度的計算方法如
所示:

為螢幕 1 沿 Y 軸旋轉 45 讀後其他兩個螢幕需要沿 Y 軸旋轉的角度。

這個變換的部分請看代碼 CubeWorkspace 中函數 drawScreen 的代碼,如下:
清單 7
protected void drawScreen(Canvas canvas, int screen, long drawingTime) { final int width = getWidth(); final int scrollWidth = screen * width; final int scrollX = this.getScrollX(); if(scrollWidth > scrollX + width || scrollWidth + width < scrollX) { return; } final View child = getChildAt(screen); final int faceIndex = screen; final float faceDegree = currentDegree - faceIndex * preFaceDegree; if(faceDegree > 90 faceDegree < -90) { return; } final float centerX = (scrollWidth < scrollX)?scrollWidth + width:scrollWidth; final float centerY = getHeight()/2; final Camera camera = mCamera; final Matrix matrix = mMatrix; canvas.save(); camera.save(); camera.rotateY(-faceDegree); camera.getMatrix(matrix); camera.restore(); matrix.preTranslate(-centerX, -centerY); matrix.postTranslate(centerX, centerY); canvas.concat(matrix); drawChild(canvas, child, drawingTime); child.setBackgroundColor(Color.TRANSPARENT); canvas.restore(); }

上面函數中的 currentDegree 變數是變化的,不是一個固定的值,改變這個變數值的方法比較隱蔽,在 AngelBaseWorkspace 的 scrollTo 函數
中。AngelBaseWorkspace 中的 scrollTo 函數把 View 類中的函數重載了,這個函數會被 View 中的 scrollBy 函數調用,所以每次 touch 螢幕
並且 move 的時候 AngelBaseWorkspace 中的 scrollTo 函數會被調用(onTouchEvent 調用 scrollBy,scrollBy 調用 scrollTo),它會根據用
戶 touch move 移動的距離來更改當前頁面的角度,即變數 currentDegree 的值。具體請看如下代碼:
清單 8
public void scrollTo(int x, int y) { if (getScrollX() != x || getScrollY() != y) { int oldX = getScrollX(); int oldY = getScrollY(); super.scrollTo(x, y); //x is the touch action X direction move distance currentDegree = x * degreeOffset; onScrollChanged(x, y, oldX, oldY); invalidate(); } }

這個立方體特效部分的代碼介紹到這裡。
本文介紹了 Android launcher 的平滑和立體翻頁效果實現,可以協助開發人員深入理解 Android 的動畫架構原理,從而能夠充分利用 android 
現有架構來做出夠眩、夠酷的動畫效果。

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.