Android UI系統中,Camera充當着相機的角色,不管是系統成像仍是UI繪製。都離不開Camera。可是在Android系統中,存在兩種Camera,一種是視覺成像的(拍照、攝像),另外一種是圖形繪製(遊戲、地圖、3D),實際上兩種也都離不開Matrix,因此本質上能夠理解爲,一個負責對相機之外的物體成像,一個負責Android View的成像。這裏咱們重點來介紹系統UI成像的Camera。android
UI成像須要使用到畫布和座標系。在Android系統中,View最終以二維方式顯示,可是Camera是三維成像,遇到這種問題,咱們這裏須要用到Matrix了,它用來將三維轉爲二維。原理是Matrix矩陣將Camera的投影轉到Canvas上,所以咱們就能看見3D圖形顯示在二維座標系中了。canvas
關於Matrix請參閱:app
android matrix 最全方法詳解與進階(完整篇)ide
Android中利用Camera與Matrix實現3D效果詳解函數
camera的座標系是左手座標系。伸出左手,讓拇指和食指成L形,大拇指向右,食指向上,中指指向前方,這樣咱們就創建了一個左手座標系,拇指,食指,中指的指向分別表明了x,y,z軸的正方向。以下圖所示:post
下面是一些細節點:動畫
1,camera位於座標點(0,0),也就是視圖的左上角;spa
2,camera.translate(10, 20, 30)的意思是把觀察物體右移10,上移20,向前移30(即讓物體遠離camera,這樣物體將會變小);
3,camera.rotateX(45)的意思是繞x軸順時針旋轉45度。舉例來講,若是物體中間線和x軸重合的話,繞x軸順時針旋轉45度就是指物體上半部分向裏翻轉,下半部分向外翻轉;
4,camera.rotateY(45)的意思是繞y軸順時針旋轉45度。舉例來講,若是物體中間線和y軸重合的話,繞y軸順時針旋轉45度就是指物體右半部分向裏翻轉,左半部分向外翻轉;
5,camera.rotateZ(45)的意思是繞z軸順時針旋轉45度。舉例來講,若是物體中間線和z軸重合的話,繞z軸順時針旋轉45度就是指物體上半部分向左翻轉,下半部分向右翻轉;.net
Android座標系是二維的3d
1,camera位於座標點(0,0),也就是視圖的左上角;
2,垂直向下爲y軸正方向
三、垂直向右爲x軸正方向
兩類座標系比較
Camera建立一個沒有任何轉換效果的新的Camera實例
Matrix相關方法以下
API提供了set、post和pre三種操做,下面這個重點看下,以後效果會用到
post是後乘,當前的矩陣乘以參數給出的矩陣。能夠連續屢次使用post,來完成所需的整個變換。
pre是前乘,參數給出的矩陣乘以當前的矩陣。因此操做是在當前矩陣的最前面發生的。
在3D攝影中,導演控制鏡頭位置來呈現不一樣的效果,攝像機在空間的不一樣位置展示出來的效果是不一樣的。因爲咱們沒法直接用眼睛去觀察這一個空間,因此要藉助攝像機採集信息,製成2D影像供咱們觀察。簡單來講,攝像機就是咱們觀察虛擬3D空間的眼睛,而咱們既是導演又是觀衆。咱們在電視上看到的都是三維投影。
注意:攝像機的位置默認是 (0, 0, -576)
三維投影
三維投影是將三維空間中的點映射到二維平面上的方法。因爲目前絕大多數圖形數據的顯示方式還是二維的,所以三維投影的應用至關普遍,尤爲是在計算機圖形學,工程學和工程製圖中。
三維投影通常有兩種,正交投影 和 透視投影。
正交投影就是咱們數學上學過的 「正視圖、正視圖、側視圖、俯視圖」 這些東西。
透視投影則更像拍照片,符合近大遠小的關係,有立體感,咱們此處使用的就是透視投影。
簡單示例
原始圖 | 轉換圖 |
第二張圖其實是攝像機分別向x,y,z移動了(這種效果的轉變,咱們能夠假定在View原圖已經繪製完成的狀況下,拿一個相機去拍攝,而後再次將投影經過Materix轉到Canvas上)
代碼以下:
public class CameraTestView extends View{ private Camera camera; private Matrix matrix; private Paint paint; public CameraTestView(Context context, AttributeSet attrs) { super(context, attrs); camera = new Camera(); matrix = new Matrix(); setBackgroundColor(Color.parseColor("#3f51b5")); paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setStyle(Style.FILL); paint.setColor(Color.parseColor("#ff4081")); } @Override protected void onDraw(Canvas canvas) { matrix.reset(); camera.save(); camera.translate(10, 50, -180); camera.getMatrix(matrix); camera.restore(); canvas.concat(matrix); canvas.drawCircle(60, 60, 60, paint); } }
3D旋轉動畫示例
public class Rotate3dAnimation extends Animation { private final float mFromDegrees; private final float mToDegrees; private final float mCenterX; private final float mCenterY; private final float mDepthZ; private final boolean mReverse; private Camera mCamera; float scale = 1; // /** * 建立一個繞y軸旋轉的3D動畫效果,旋轉過程當中具備深度調節,能夠指定旋轉中心。 * @param context public Rotate3dAnimation(Context context, float fromDegrees, float toDegrees, float centerX, float centerY, float depthZ, boolean reverse) { mFromDegrees = fromDegrees; mToDegrees = toDegrees; mCenterX = centerX; mCenterY = centerY; mDepthZ = depthZ; mReverse = reverse; // 獲取手機像素密度 (即dp與px的比例) scale = context.getResources().getDisplayMetrics().density; } @Override public void initialize(int width, int height, int parentWidth, int parentHeight) { super.initialize(width, height, parentWidth, parentHeight); mCamera = new Camera(); } @Override protected void applyTransformation(float interpolatedTime, Transformation t) { final float fromDegrees = mFromDegrees; float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime); final float centerX = mCenterX; final float centerY = mCenterY; final Camera camera = mCamera; final Matrix matrix = t.getMatrix(); camera.save(); // 調節深度 if (mReverse) { camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime); } else { camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime)); } // 繞y軸旋轉 camera.rotateY(degrees); camera.getMatrix(matrix); camera.restore(); // 修正失真,主要修改 MPERSP_0 和 MPERSP_1 float[] mValues = new float[9]; matrix.getValues(mValues); //獲取數值 mValues[6] = mValues[6]/scale; //數值修正 mValues[7] = mValues[7]/scale; //數值修正 matrix.setValues(mValues); //從新賦值 // 調節中心點 matrix.preTranslate(-centerX, -centerY); matrix.postTranslate(centerX, centerY); } }
固然,以上的實現方式較複雜,咱們可使用Animation或者Animator來實現,經過rotationY動畫。
final float targetVal = 180f; final int width = v.getMeasuredWidth(); v.setPivotX(width/2); v.clearAnimation(); final Drawable before= getResources().getDrawable(R.mipmap.img_cake); final Drawable after = getResources().getDrawable(R.mipmap.img_heart); Animator animator = ObjectAnimator.ofFloat(v,"rotationY",targetVal,360); animator.setDuration(1000); v.setRotationY(180f); v.setBackground(before); final float distance = v.getCameraDistance(); //獲取相機距離 ((ValueAnimator)animator).addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float value = (float) animation.getAnimatedValue(); float fraction = animation.getAnimatedFraction(); if(Math.abs(360+180)/2<=Math.abs(value)){ v.setBackground(after); } float f = (float) Math.abs(Math.sin(Math.toRadians(fraction * targetVal))); Log.i("Animator","value="+value +" ,fraction="+fraction+", f="+f); v.setCameraDistance(distance + f*(distance*width)/2); } }); animator.start(); }
攝像機要求 由近到遠,而後由遠到近,這裏咱們經過三角函數sin來實現
float f = (float) Math.abs(Math.sin(Math.toRadians(fraction * targetVal)));