Android 關於立方體旋轉效果的研究

來源:互聯網
上載者:User
最終效果:這個效果是通過繼承Animation類並重寫其相關方法來實現的。 

(一)自訂Animation

大家都知道,Android動畫裡面有一種動畫叫Tween Animation,有的大師翻譯成“補間動畫“,即我們指定動畫開始的第一幀的樣子和動畫結束時最後一幀的樣子,然後系統就會自動補上中間缺失的那些幀,形成動畫。

而自訂Animation,則不是由系統而是由我們自己來手動補上中間缺失的那些幀。

我們先來看看官方文檔裡對Animation類的介紹,開啟android.view.animation.Animation,可以看到Animation類有很多的setXxx()和getXxx()方法來設定和擷取一個動畫的諸如期間之類的屬性,還有一些isXxx()和hasXxx()這類的判斷當前動畫狀態的方法。對於現在講到的自訂Animation相關的方法有以下幾個:

1)

2)

3)

 

動畫開始之前會先調用initialize(),其任務是初始化(這是句廢話,請各位無視掉)。我們可以把一些我們需要初始化的工作放在這裡面。

動畫開始後會多次調用getTransformation()方法,每調用一次就會形成動畫的一幀。getTransformation()方法的第一個參數float interpolatedTime,插值時間,在整個動畫過程中其值從0.0變化到1.0,代表了動畫過程中的一個時間點。其具體的值似乎是不確定的:

動畫的期間(duration)越長,interpolatedTime的值就越多越分得越細。每有一個interpolatedTime就會調用一次getTransformation()方法。該方法的第二個參數是一個Transformation對象,這是一個什麼對象呢?

transformation的英文本意是轉換、轉化,官方文檔的解釋是:

定義應用於一個動畫時間點上的轉換。

再看看該類的方法:

Transformation類的構造方法的說明顯示了該類的本質:一個alpha值和一個matrix的合體。

identity matrix,百度翻譯是單位矩陣,有道詞典也是這個意思,還給了個搞笑的例句:

無論如何,Transformation就是一個alpha值與一個matrix合在一起,能夠用於映像變換的一個東西。

這裡又用到一個類,Matrix,矩陣。在ImageView組件的屬性裡,就有一個android:scaleType的屬性,其可取的值裡面就有一個”matrix“。我們看看官方文檔裡的描述(android.graphics.Matrix):

Matrix裡面有一個3乘3的變換座標矩陣。這個類沒有構造器,所以必須顯示地使用reset()方法來初始化一個單位矩陣,或者使用setXxx()方法(如setTranslate,setRotate等)來初始化。

雖然上面剛剛說了”這個類沒有構造器“,不過接著就給出了一個構造器。預設是一個單位矩陣,難道因為單位矩陣”does nothing",所以就不承認它的存在?

 

Matrix類有很多很多方法,我們要用到的主要是setXxx()、preXxx()和postXxx()這三類方法,如setTranslate(float dx,float dy), preScale(float sx,float sy), postSkew(float kx, float ky)

這些方法形成的矩陣可用於對映像進行translate平移、scale縮放、rotate旋轉和skew。

平移和縮放應該很好理解,這裡的旋轉是2D平面的旋轉,預設是繞著映像左上方如時鐘指標那樣轉的。

而skew的概念,詞典上翻譯說是斜交,數學上也的確有斜交這種東西。網上我看到很多地方稱之為傾斜。聽上去就不夠專業。無意中看到有地方提到一個數學詞:錯切。

錯切變換:是指圖形沿某座標方向產生不等量的移動而引起圖形變形的一種變換。分為水平錯切和垂直錯切:

水平錯切:矩形ABCD,保持其內每一點的Y座標不變,X座標不等量增大,某一點的X增量與該點距離X軸的距離(也就是該點的Y座標)相關,離X軸越遠的,增量越大。矩形ABCD經過水平錯切形成平行四邊形A*B*C*D*。

垂直錯切:保持X座標不變,Y座標不等量增大。

Matrix還有個setSinCos()的方法,這個方法的效果和rotate好像差不多,因為matrix矩陣那9個數裡面管旋轉的兩個值就是一個sin值一個cos值,也許我們的rotate方法最終也會被解析成一個sin值和一個cos值。 

 

讓我們回到主題上來講,我們本來是在講什嗎?自訂Animation,然後講了它的initialize(),然後是getTransformation(),裡面扯到了interpolatedTime和Transformation,又在Transformation裡扯到了Matrix。現在讓我們回到getTransformation(),這個方法第二個參數是個Transformation對象,這個Transformation對象不是向這個方法裡傳資料的,相反,是來存這個方法的運算結果的,也就是通過引用參數的方式來傳遞傳回值。每有一個interpolatedTime,就調用一次getTransformation()方法,得到一個Transformation對象,將該對象作用於映像,就會產生一張變換後的映像,多張變換後的映像按順序排列在一起,一張一張的放,就形成了動畫。

 

前面提到Animation類還有一個重要的方法是applyTransformation(float interpolatedTime,Transformation t),可以看出,其參數與getTransformation()是完全一樣的。

 

getTransformation的Helper。子類應當實現本方法,在方法雷根據指定的interpolation value來應用子類想要變換(其實就是叫子類根據interpolatedTime來產生一個Transformation對象,將其copy給參數裡的Transformation對象t),blablabla...

 看了官方的解釋,這個方法是為getTransformation()服務的,所以參數都一樣。至於為什麼要多弄這麼一個方法而不是讓子類直接重寫getTransformation()方法,我也不知道。getTransformation()存取控制還是public的,而applyTransformation()還只是protected的。據說是getTransformation()方法裡面會調用applyTransformation()方法,applyTransformation()方法才是真正產生Transformation對象(alpha+matrix)的地方。而getTransformation()方法可能除了返回一個Transformation對象外,還有其它的動作吧。 囉嗦了這麼多,現在我們就有了自訂Animation的一個架構: 
public class MyAnimation extends Animation {        public MyAnimation() {            }    @Override    public void initialize(int width, int height, int parentWidth,            int parentHeight) {        // TODO Auto-generated method stub        super.initialize(width, height, parentWidth, parentHeight);    }            @Override    protected void applyTransformation(float interpolatedTime, Transformation t) {        // TODO Auto-generated method stub        super.applyTransformation(interpolatedTime, t);    }    } 
(二)Camera回頭看看本篇文章的題目,3D旋轉效果,一個畫面怎樣才會有3D旋轉效果呢?學習過Android的Tween Animation方面內容都知道,一個畫面可以在2D平面上平移,可以縮放,可以像時鐘的指標那樣旋轉。但只是2D的效果,怎麼弄也不會像3D的樣子。一個2D的東西怎麼才能變成3D的東西呢?二維世界跟三維世界的區別是什嗎?是Z軸,呵呵。Android的座標系統,2D的時候我知道左上方是原點,原點往右是X軸,原點往下是Y軸。而3D的時候嘛,好像就不是這個樣子的了,這裡還需要更多的資料,我沒看到。不過肯定是有這麼個Z軸的。這個Z軸的實現,在本篇這個主題下,依託的是一個叫Camera的類的東西。 想像一下,在一個有X軸、Y軸、Z軸的三維世界裡,有一幅畫面:在這個三維世界裡還存在一個照相機,當照相機從不同的角度拍攝這張畫時,我們就可以看來這張畫的不同的樣子。  這裡的Camera指的是android.graphics.Camera而不是android.hardware.Camera,後者指的我們手機的一個硬體(驅動?)。看官方文檔:

Camera執行個體可用於計算3D變換,產生一個可應用於如Canvas之類的Matrix。這句話我的理解就是,它也是一個生產Matrix的東西,而且生產的是3D的Matrix。

 

其中Camera()構造方法產生的Camera對象裡麵包含空的變換(是不是也是一個alpha = 1和一個does nothing的identity matrix?咦?感覺Camera像是3D版的Transformation?好像都是比較窮的類啊,嗯,肯定是哪裡我理解錯了)

 

getLocationX()、getLocationY()、getLocationZ()三個方法,按字面意思是返回這個照樣機的x、y、z座標,我們先實驗一下:

//MyAnimation.java

public class MyAnimation extends Animation {        private final float mFromDegree;//旋轉前的角度    private final float mToDegree;//旋轉後的角度    private Camera mCamera;        public MyAnimation(float fromDegree, float toDegree)    {        mFromDegree = fromDegree;        mToDegree = toDegree;        mCamera = new Camera();    }    @Override    protected void applyTransformation(float interpolatedTime, Transformation t) {        float degree = mFromDegree + ( mToDegree - mFromDegree) * interpolatedTime;        Matrix matrix = t.getMatrix();                mCamera.save();        Log.v("XXXX","" + mCamera.getLocationX() + " " + mCamera.getLocationY() + " " + mCamera.getLocationZ());        mCamera.rotateY(degree);        mCamera.getMatrix(matrix);        mCamera.restore();            }    } 

 

//activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"     >    <ImageView        android:id="@+id/iv"        android:layout_width="300dp"        android:layout_height="200dp"        android:src="@drawable/a"        android:scaleType="fitXY"        android:layout_centerInParent="true"        android:onClick="clickHandler"        /></RelativeLayout>

 

//MainActivity.java

public class MainActivity extends Activity {        ImageView iv;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        iv = (ImageView)findViewById(R.id.iv);    }        public void clickHandler(View view)    {        MyAnimation ma = new MyAnimation(0.0f,360.0f);        ma.setDuration(3000);        ma.setFillAfter(true);        iv.startAnimation(ma);    }} 

運行結果的列印資訊顯示,預設的Camera的座標是:0.0,0.0,-8.0

如果這個Location真的指的是Camera的座標的話,那麼理論上當我增大或者減小LocationZ的時候,畫面應該是有縮放的效果,而事實上沒有,變化倒是有的,有興趣你們可以試一下,如果想明白了這個Location究竟是什麼,請告訴我。

 

我們看看Camera的其它方法:        
rotateX():

    protected void applyTransformation(float interpolatedTime, Transformation t) {        float degree = mFromDegree + ( mToDegree - mFromDegree) * interpolatedTime;        Matrix matrix = t.getMatrix();                mCamera.save();        mCamera.rotateX(degree);        mCamera.getMatrix(matrix);        mCamera.restore();            } 

效果:

畫面繞著其上邊界轉,方向先由裡向外

 

rotateY():

mCamera.rotateY(degree);

畫面繞著其左邊界先由外向裡轉

rotateZ():

mCamera.rotateZ(degree);

畫面繞著其左上方頂點逆時針方向平面旋轉

 

 

translate():

//        mCamera.rotateZ(degree);        mCamera.translate(100.0f, 0, 0); 

 

Click前:

 

Click後:

 

畫面右移了一段距離(平移了多少呢?100.0f?網上給的結果說是向右移了50,也沒說單位,可能是px吧。我在這兒用getX()來擷取前、後的ImageView的X座標,得到的結果是相同的。說明實際上並沒有改變ImageView的座標,只是在繪製畫面的時候將畫面裡的ImageView平移了)

 

mCamera.translate(0, 100.0f, 0); 

類似的,畫面向上平移了一小段。

 

mCamera.translate(0, 0, 100.0f); 
畫面縮小了,就好像我們的鏡頭拉遠了。(原來這才是我們前面想要的照相機效果,那那個setLocation()有什麼用呢?那裡的Location到底指的什麼呢?)這裡最最最需要注意的是,這裡的縮小不是固定畫面中心,然後收縮的,而是左上方頂點固定,然後收縮的。這點很重要。

 

在Camera的世界裡,座標系統到底是什麼樣子的?也許是這樣的:

其中Z軸由外向裡

而Camera的translate()方法裡的參數都是作用於映像的,所以參數X為100.0f時,畫面右移,參數Y為100.0f時,畫面上移,參數Z為100.0f時,畫面縮小。當然,這隻是我的猜測。

 

Camera的save()和restore()你要問我這兩個方法是幹什麼的?為什麼用Camera的時候幾乎都會用到?我說,save()方法儲存目前狀態,變換完成後再restore()將狀態恢複。不用這兩個方法會出問題。然後你心裏面也許就會罵到:cao,你當我不懂英文不認識這兩個詞兒麼!我看到那些這麼解釋的網上文章,心裡就是這麼想的。於是,我其實應該說:對不起,我也不知道。反正大家都在用,你可以試試不用的後果,慘不忍睹。。。有人說可參考Canvas類的save()和restore(),也許吧。

具體這兩個方法裡面發生了什麼,如果你知道,請告訴我。

 

(三)3D旋轉效果

這是我們想要的效果,就好像一個立方體在轉動。我剛剛去研究這個效果時,從網上找到了這方面的東西,以及一些類似的變種。然後很多人還說,請參考sdk\samples\android-17\ApiDemos\src\com\example\android\apis\animation下面的Transition3d.java。嗯,這個例子代碼恐怕是這個效果的始祖了。有興趣可以去看看,反正我當時是沒看懂,現在不知道看不看得懂,已經沒心情看了。看看,這是兩張圖片合在一起的效果。也就是用兩個ImageView實現的。我們稱左邊的為iv1,右邊的ImageView為iv2。第一次寫,寫不下去了,直接上代碼吧

//activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"     >     <ImageView        android:id="@+id/iv1"        android:layout_width="200px"        android:layout_height="200px"        android:src="@drawable/c"        android:scaleType="fitXY"        android:layout_centerInParent="true"        android:onClick="clickHandler"        />      <ImageView        android:id="@+id/iv2"        android:layout_width="200px"        android:layout_height="200px"        android:src="@drawable/e"        android:scaleType="fitXY"        android:layout_centerInParent="true"        android:onClick="clickHandler"        />                </RelativeLayout>

 

//MainActivity.xml

public class MainActivity extends Activity {ImageView iv1,iv2;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);iv1 = (ImageView)findViewById(R.id.iv1);iv2 = (ImageView)findViewById(R.id.iv2);}public void clickHandler(View view){MyAnimation ma2 = new MyAnimation(0.0f,90.0f);ma2.setDuration(800);ma2.setFillAfter(true);iv2.startAnimation(ma2);MyAnimation ma1 = new MyAnimation(-90.0f,0.0f);ma1.setDuration(800);ma1.setFillAfter(true);iv1.startAnimation(ma1);}}

 

//MyAnimation.java

public class MyAnimation extends Animation {private final float mFromDegree;//旋轉前的角度private final float mToDegree;//旋轉後的角度private Camera mCamera;private int halfWidth;private int halfHeight;public MyAnimation(float fromDegree, float toDegree){mFromDegree = fromDegree;mToDegree = toDegree;mCamera = new Camera();}@Overridepublic void initialize(int width, int height, int parentWidth,int parentHeight) {// TODO Auto-generated method stubsuper.initialize(width, height, parentWidth, parentHeight);halfWidth = width / 2;halfHeight = height / 2;}@Overrideprotected void applyTransformation(float interpolatedTime, Transformation t) {Matrix matrix = t.getMatrix();//當前旋轉的角度值float degree = mFromDegree + ( mToDegree - mFromDegree ) * interpolatedTime;//save()、restore()求解釋mCamera.save();if ( degree >= 82.0f ){//第一次看的時候可先跳過這裡,直接到後面的else子句。
//這裡不是使用mCamera.rotateY(degree);因為由於沒有mCamera.translate(0,0,-halfWidth);畫面要旋轉多少度我們看上去就是轉了多少度(有了那句代碼,畫面旋轉82度時我們看上去已經轉了90度)//如果使用rotateY(degree),那麼實際的效果就是,degree從0度到82度時,我們看到畫面從0度轉到了90度,畫面消失,然後degree 83度時,畫面又是旋轉了83度的樣子,並且是繞著自己的中軸線轉的83度(沒有//mCamera.translate(0,0,-halfWidth);也就沒有了x軸平移,畫面將繞自己的中軸線轉)mCamera.rotateY(90.0f);}else if ( degree <= -82.0f ){mCamera.rotateY(90.0f);}else{//這句代碼的效果是畫面固定左上方縮放:halfWidth值為正,畫面縮小,值越大,畫面越小;值為負,畫面放大,halfWidth值越大(即-halfWidth值越小),畫面越大。//這個值就如同照相機與畫面之間距離的增量。mCamera.translate(0, 0,halfWidth);//值為正,畫面右移等量px;值為負,畫面左移等量px。究竟是照相機在動呢?照相機固定,畫面在動?//mCamera.translate(halfWidth, 0, 0);//值為正,畫面上移等量px;值為負,畫面下移等量px//mCamera.translate(0, halfWidth, 0);//如果只有這一句變換而沒有其它處理的話,畫面繞著其左邊界旋轉。代碼的本意是讓畫面繞Y軸旋轉,由於預設畫面左邊界就在Y軸上,所以效果是畫面繞著其左邊界旋轉。//想像一下,任何一個動畫效果,都是由許許多多張靜態畫面組成的,本函數的作用就是返回一個Matrix來將原來的一張畫面處理成動畫裡的一幀畫面。//任何一個畫面,都是由無數個點組成的。Matrix就是讓這一個一個的點發生位移,位移量與該點所處的座標相關。所有發生位移後的點組成的新畫面就是動畫裡的一幀。//列印矩陣值,從實質上看,這句代碼一方面修改了scale_x值(上面那句translate的縮放是一個與halfWidth相關的定值,新的scale_x值是用上面那個定值乘以degree的餘弦值。要實現旋轉效果,x軸縮放應該是隨著degree的變化而變化的)//另一方面,這句代碼產生了一個隨著degree變化的左下角值,這個值與畫面的錯切效果相關(x軸縮放與垂直錯切相結合,就是繞Y軸旋轉效果)mCamera.rotateY(degree);//這句代碼的本意是把畫面放大。神奇的是,即是沒有前面的mCamera.translate(0, 0,halfWidth);這句縮小代碼,依然會有立方體旋轉效果,只是畫面變大了。//應該注意的是這裡的變大不是把最初的原畫面變大,也不是把旋轉後的畫面在三維空間裡把寬高拉回到原來的大小;要知道,我們這裡的3D是假3D。這句代碼就是把旋轉後的畫面——我們看到的那個2D的側面畫面,//按比例放大了,因此他不是mCamera.translate(0, 0,halfWidth);的逆過程//同理而言,前面那句mCamera.rotateY(degree);其實也不是真的讓畫面在三維世界裡轉動,其是繞Y軸轉動效果只是映像x軸縮放與錯切相結合產生的人眼幻覺。//為什麼要先縮小再旋轉然後再放大呢?如果沒有這兩句縮放代碼,畫面始終是繞著自己的中軸線在轉。如果列印每一句代碼後矩陣的9個值,會發現,//mCamera.translate(0, 0, -halfWidth );不僅會產生縮放,還會產生x軸的平移。這一點平移是立方體效果的關鍵所在。//為什麼前面那句mCamera.translate(0, 0,halfWidth);沒有產生x軸的平移呢?也許前面有平移,只是平移量是0.在rotateY之前,矩陣左下角那個值是0.0;隨著degree的不同,左下角的值也不//同,也許這個值與translate的x軸平移量也有關係。//列印矩陣9個值,除了x軸平移外,本句代碼還修改了scale_x。mCamera.translate(0, 0,halfWidth);產生的scale_x是一個定值,該值小於1;而mCamera.rotateY(degree);產生的scale_x是從定值到0.0。//這意味著如果沒有本句代碼,我們將會看到畫面一開始就瞬間縮小了(比如瞬間變為原來的一半),沒有過渡。本句代碼會修改scale_x,使其值從1.0到0.0,實現平滑的縮放。//這句代碼還會引起當degree等於82度時畫面就看上去已經旋轉了90度,超過82度後,畫面又從後面轉回來,所以前面有if...else...代碼來將82度後的旋轉單獨處理。其實代碼也可以有另一種寫法,見最後的注釋1//如果把本句代碼的第三個參數減小(絕對值變大),比如 -halfWidth * 2,我們看到畫面變大旋轉,並且原來形成立方體效果的兩個view之間的空白距離拉大,出現類似旋轉木馬的效果mCamera.translate(0, 0, -halfWidth);}//注意這句代碼,這句代碼是以set的方式直接用mCamera裡的matrix值將參數matrix覆蓋掉,所以如果除了mCamera變換外還有其它變換,請放在這句之後(比如後面的變換中心點調整)。mCamera.getMatrix(matrix);mCamera.restore();//當只有這一句變換代碼時,畫面左移halfWidth,上移halfHeight,從而畫面的中心與原來的左上方重合。而左上方,是預設的變換中心點。//每一種變換,無論是縮放還是旋轉,都是相對於一個中心點而言的。映像上的每一點根據自身與中心點的距離來決定自己變換的量//這句代碼可以讓映像好像是以自己的中心為變換中心來變換一樣(比如不是繞左邊界轉而是繞自己的中軸線轉),其實是讓自己的中心與變換中心重合了而已。//由於這句代碼的存在,前面mCamera裡的那些變換的變換中心都將不僅是原位置的左上方,也是畫面本身的中心,這兩個中心重合在一起了。matrix.preTranslate(-halfWidth, -halfHeight);//當只有這一句變換代碼時,畫面右移halfWidth,下移halfHeight.//這句話的存在是因為上一句代碼把映像中心平移到左上方去了。要是沒有這句,我們看到的畫面就是立方體在原先位置的左上方轉了。matrix.postTranslate(halfWidth, halfHeight);Log.v("XXXX","" + degree + " " + matrix.toString());}}/*注釋1protected void applyTransformation(float interpolatedTime, Transformation t) {Matrix matrix = t.getMatrix();float degree = mFromDegree + ( mToDegree - mFromDegree ) * interpolatedTime;mCamera.save();mCamera.translate(0, 0,halfWidth);if ( degree >= 82.0f ){mCamera.rotateY(90.0f);}else if ( degree <= -82.0f ){mCamera.rotateY(90.0f);}else{mCamera.rotateY(degree);mCamera.translate(0, 0, -halfWidth );}mCamera.getMatrix(matrix);mCamera.restore();matrix.preTranslate(-halfWidth, -halfHeight);matrix.postTranslate(halfWidth, halfHeight);}}*/

為什麼是82度?因為列印各個插值時間點的矩陣值發現在82度時會有奇怪的變化,比如82度前是增長的,82度後會遞減之類的。

 

 

 

 

 

 

 

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.