android 3d渲染动画效果吗,Android如何实现3D效果
前言
前段時間讀到一篇文章,作者通過自定義View實現了一個高仿小米時鐘,其中的3D效果很是吸引我,于是抽時間學習了一下,現在總結出來,和大家分享。
正文
想要在Android上實現3D效果,其實并沒有想象中那么復雜,我們需要運用兩樣東西:Camera和Matrix,這里的Camera可不是我們平常拍照用的Camera,這里的Camera是位于android.graphics包下的Camera:
android.graphics.Camera我們可以看到它的作用是:計算3D變換,并且生成一個Matrix,可以應用到Canvas上,這句話其實就是實現3D效果的核心原理。
Camera的坐標系是左手坐標系。當手機平整的放在桌面上,X軸是手機的水平方向,Y軸是手機的豎直方向,Z軸是垂直于手機向里的那個方向。
Camera坐標系
Camera位于坐標點(0,0),也就是視圖的左上角。
我們再來了解一下Matrix,Android中的Matrix是一個3 x 3的矩陣,其內容如下:
Matrix
從字面上來看, MSCALE用于處理縮放變換,MTRANS用于處理平移變換,MSKEW用于處理錯切變換。最后一行的MPERSP用于處理透視變換,關于透視變換,官方文檔中并沒有具體的說明,這里也就不再贅述。另外,矩陣是支持旋轉變換的,旋轉變換是通過同時設置MSCALE和MSKEW來實現的(這里邊就是一些數學原理了,筆者也是半壺水,就不在這丟人了,感興趣的同學可以自己研究一下)。另外有同學可能對錯切變換也不是特別理解,筆者當時也是自己查了下才明白,這里簡單說明一下,就免得大家再去百度了:
錯切變換(skew)在數學上又稱為Shear mapping(可譯為“剪切變換”)或者Transvection(縮并),它是一種比較特殊的線性變換。錯切變換的效果就是讓所有點的x坐標(或者y坐標)保持不變,而對應的y坐標(或者x坐標)則按比例發生平移,且平移的大小和該點到x軸(或y軸)的垂直距離成正比。錯切變換,屬于等面積變換,即一個形狀在錯切變換的前后,其面積是相等的。
X軸錯切變換
上圖中,各點的y坐標保持不變,但其x坐標則按比例發生了平移。
Y軸錯切變換
上圖中,各點的x坐標保持不變,但其y坐標則按比例發生了平移。
還有一個不容易理解的地方,Matrix針對每種變換,都提供了set、pre和post三種操作方式。
pre方法表示矩陣前乘,如果變換矩陣為A,原始矩陣為M,pre方法即是 M x A
post方法表示矩陣后乘,如果變換矩陣為A,原始矩陣為M,post方法即是 A x M
之所以需要區分前乘和后乘,是因為矩陣的乘法不滿足交換率,即 A x M != M x A
另外還有比較重要的一點, 在圖像處理中,越靠近右邊的矩陣越先執行
調用一系列set、pre、post方法時,可以理解為將這些操作插入一個隊列:set是清空隊列再添加,pre是在隊首插入,post是在隊尾插入。
舉個栗子:
Matrix m = new Matrix();
m.postTranslate(20, 20);
m.preScale(0.2f, 0.5f);
m.setScale(0.8f, 0.8f);
m.postScale(3f, 3f);
m.preTranslate(0.5f, 0.5f);
執行順序為:preTranslate(0.5f, 0.5f) → setScale(0.8f, 0.8f) → postScale(3f, 3f)
因為setScale(0.8f, 0.8f)會將前面的postTranslate(20, 20)和preScale(0.2f, 0.5f)清除掉,然后再將postScale(3f, 3f)插入隊尾,preTranslate(0.5f, 0.5f)插入隊首。
2018.4.25補充---Canvas的幾何變換:
Canvas的幾何變換方法包括 translate、rotate、scale、skew 幾種,其原理也是運用matrix做幾何變換。
我們以 canvas.rotate(float degrees) 方法舉例:
canvas.rotate(float degrees).png通過官方文檔我們可以了解到,rotate方法其實就是用matrix進行前乘,然后再將matrix應用到當前畫布上。
以下兩段代碼其實是等價的:
canvas.rotate(-degreeZ); // 1
Matrix matrix = new Matrix(); // 2
matrix.reset();
matrix.preRotate(-degreeZ);
canvas.concat(matrix);
由于是前乘,所以canvas的幾何變換方法是倒序的,需要把變換的代碼倒著寫,舉個栗子:
// 如果想要讓canvas先移動 (-centerX, -centerY) 距離,再移動 (centerX, centerY) 距離進行恢復
// 代碼需要倒著寫
canvas.translate(centerX, centerY);
canvas.translate(-centerX, -centerY);
這里再補充一下 canvas.concat(matrix) 方法:
用 Canvas 當前的變換矩陣和 Matrix 相乘,即基于 Canvas 當前的變換,疊加上 Matrix 中的變換。
熟悉了基本的工具,我們就可以開工了,我們先來看一下最終的效果:
3D效果.gif
圓盤跟隨手指的移動而變換角度,呈現出3D的效果,看起來還是很不錯的,我們看看如何來實現這個效果吧:
首先我們需要做一些初始化的工作:
private Paint mWhitePaint;
private Paint mCirclePaint;
private float mCircleStrokeWidth = 2;
private float mMaxRadius = 300;
/* Camera旋轉的最大角度 */
private float mMaxCameraRotate = 15;
/* 我們今天的主角 */
private Matrix mMatrix;
private Camera mCamera;
/* Camera繞X軸旋轉的角度 */
private float mCameraRotateX;
/* Camera繞Y軸旋轉的角度 */
private float mCameraRotateY;
private void init(){
mMatrix = new Matrix();
mCamera = new Camera();
//白色大圓的畫筆
mWhitePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mWhitePaint.setStyle(Paint.Style.FILL_AND_STROKE);
mWhitePaint.setStrokeWidth(mCircleStrokeWidth);
mWhitePaint.setColor(Color.WHITE);
//內部藍色圓環的畫筆
mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mCirclePaint.setStyle(Paint.Style.STROKE);
mCirclePaint.setStrokeWidth(mCircleStrokeWidth);
mCirclePaint.setColor(Color.WHITE);
mCirclePaint.setColor(0xff237EAD);
}
這是重寫的onDraw方法,做了兩件事情:
1.將canvas傳入setCameraRotate方法中
2.再畫幾個圈圈
@Override
protected void onDraw(Canvas canvas) {
setCameraRotate(canvas);
canvas.drawCircle(getWidth() / 2, getHeight() / 2, mMaxRadius, mWhitePaint);
canvas.drawCircle(getWidth() / 2, getHeight() / 2, mMaxRadius / 6 * 5, mCirclePaint);
canvas.drawCircle(getWidth() / 2, getHeight() / 2, mMaxRadius / 6 * 4, mCirclePaint);
canvas.drawCircle(getWidth() / 2, getHeight() / 2, mMaxRadius / 6 * 3, mCirclePaint);
canvas.drawCircle(getWidth() / 2, getHeight() / 2, mMaxRadius / 6 * 2, mCirclePaint);
}
接下來我們看看setCameraRotate方法里面做了什么
private void setCameraRotate(Canvas mCanvas) {
mMatrix.reset();
mCamera.save();
mCamera.rotateX(mCameraRotateX);//繞x軸旋轉
mCamera.rotateY(mCameraRotateY);//繞y軸旋轉
mCamera.getMatrix(mMatrix);//計算對于當前變換的矩陣,并將其復制到傳入的mMatrix中
mCamera.restore();
/**
* Camera默認位于視圖的左上角,故生成的矩陣默認也是以其左上角為旋轉中心,
* 所以在動作之前調用preTranslate將mMatrix向左移動getWidth()/2個長度,
* 向上移動getHeight()/2個長度,
* 使旋轉中心位于矩陣的中心位置,動作之后再post回到原位
*/
mMatrix.preTranslate(-getWidth() / 2, -getHeight() / 2);
mMatrix.postTranslate(getWidth() / 2, getHeight() / 2);
mCanvas.concat(mMatrix);//將mMatrix與canvas中當前的Matrix相關聯
}
以上這段代碼,除了旋轉操作以外,其余的基本屬于固定寫法,這樣寫的原因都在注釋里寫清楚了,可能有點不太好理解,多看幾遍或者自己試著寫一下就明白了。
上面這段代碼中的mCameraRotateX和mCameraRotateY這兩個全局變量的值應該與此時手指觸摸坐標相關聯,所以我們在onTouchEvent方法中動態設置:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//根據手指坐標計算Camera應該旋轉的角度
getCameraRotate(event);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
getCameraRotate(event);
invalidate();
break;
}
return true;
}
我們最后來看看getCameraRotate方法中是如何處理的:
private void getCameraRotate(MotionEvent event) {
float rotateX = -(event.getY() - getHeight() / 2);
float rotateY = (event.getX() - getWidth() / 2);
/**
*為什么旋轉角度要這樣計算:
* 當Camera.rotateX(x)的x為正時,圖像圍繞X軸,上半部分向里下半部分向外,進行旋轉,
* 也就是手指觸摸點要往上移。這個x就會與event.getY()的值有關,x越大,繞X軸旋轉角度越大,
* 以圓心為基準,手指往上移動,event.getY() - getHeight() / 2的值為負,
* 故 float rotateX = -(event.getY() - getHeight() / 2)
* 同理,
* 當Camera.rotateY(y)的y為正時,圖像圍繞Y軸,右半部分向里左半部分向外,進行旋轉,
* 也就是手指觸摸點要往右移。這個y就會與event.getX()的值有關,y越大,繞Y軸旋轉角度越大,
* 以圓心為基準,手指往右移動,event.getX() - getWidth() / 2的值為正,
* 故 float rotateY = event.getX() - getWidth() / 2
*/
/**
* 此時得到的rotateX、rotateY 其實是以圓心為基準,手指移動的距離,
* 這個值很大,不能用來作為旋轉的角度,
* 所以還需要繼續處理
*/
//求出移動距離與半徑之比。mMaxRadius為白色大圓的半徑
float percentX = rotateX / mMaxRadius;
float percentY = rotateY / mMaxRadius;
if (percentX > 1) {
percentX = 1;
} else if (percentX < -1) {
percentX = -1;
}
if (percentY > 1) {
percentY = 1;
} else if (percentY < -1) {
percentY = -1;
}
//將最終的旋轉角度控制在一定的范圍內,這里mMaxCameraRotate的值為15,效果比較好
mCameraRotateX = percentX * mMaxCameraRotate;
mCameraRotateY = percentY * mMaxCameraRotate;
}
到這里,我們要的3D效果就已經實現了。也是費了一番功夫。
結語
寫這篇文章的起因是讀到了猴菇先生的博客高仿小米時鐘 - 使用Camera和Matrix實現3D效果對文中實現的3D效果產生了興趣,但是文中主要的篇幅還是介紹如何自定義View,關于3D效果的實現只有主要代碼和簡單的注釋,所以我又自己從Camera和Matrix的定義開始,將3D效果作為主體重新學習了一遍,便是有了這篇文章。文中的部分代碼也是從猴菇先生的代碼中借鑒的,將代碼進行了簡化,只保留了3D效果的部分,將注釋和說明進行了豐富,從頭開始講解,更加易于學習。
從開始研究到寫完這篇文章,斷斷續續加起來差不多花了2天時間,發現寫文章確實很鍛煉人,以前自己遇到問題,上網隨便搜搜,看個大概,就完事兒了。現在想要寫出來,必須要弄明白、透徹,才敢動手寫,也算是對自己的一種監督吧,我可不愿意誤人子弟,所以經常寫到一半,發現某些地方不是特別清楚,又回過頭去弄明白了再繼續寫,寫完之后,收獲也是大大的。而且之前寫的文章還收到了點贊、關注還有打賞,真的特別開心。
最后,如有錯誤,歡迎指正。
總結
以上是生活随笔為你收集整理的android 3d渲染动画效果吗,Android如何实现3D效果的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何把家庭的路由器的路由功能关掉怎样关闭
- 下一篇: android crop 大图,Andr