Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 開發入門 >> Android 動畫框架詳解,第 2 部分

Android 動畫框架詳解,第 2 部分

日期:2016/3/13 17:13:14      編輯:開發入門

簡介: 這是由兩部分組成的 Android 動畫框架詳解的第二部分實例篇。在閱讀本篇之前,建議您首先閱讀本系列的第一部分 Android 動畫框架詳解之原理篇。原理篇詳細介紹了 Android 動畫框架的實現原理,同時介紹了一個繞 Y 軸旋轉的動畫示例。本篇是在原理篇的基礎上介紹一個較復雜的 android launcher 的平滑和立體翻頁效果動畫的實現。

  

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

我們這裡把 android launcher 程序的 Workspace 相關的代碼抽取出來,以一個比較簡單的代碼來展示 launcher 程序是如何實現多頁以及不同頁面之間的切換效果。本示例代碼在 SDK 2.1 中運行,設置的是 WVGA 的屏幕大小。

首先我們來看一下程序運行的效果來一些感性的認識。


圖 1:平滑移動效果
圖 1:平滑移動效果 

圖 2:立體翻頁效果
圖 2:立體翻頁效果 

窗口頁面的布局

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


清單 1.
清單 1. 

其中 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); 

圖 3:Workspace 和頁面布局圖
圖 3:Workspace 和頁面布局圖

為了讓三個頁面達到上圖的窗口布局,我們對 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 軸旋轉的角度的計算方法如下圖所示:


圖 4:初始屏幕位置示意圖
圖 4: 初始屏幕位置示意圖 

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


圖 5:旋轉 45 度後屏幕位置示意圖
圖 5: 旋轉 45 度後屏幕位置示意圖

這個變換的部分請看代碼 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 現有框架來做出夠眩、夠酷的動畫效果。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved