Camera与Matrix
Android UI系统中,Camera充当着相机的角色,无论是系统成像还是UI绘制。都离不开Camera。但是在Android系统中,存在两种Camera,一种是视觉成像的(拍照、摄像),另一种是图形绘制(游戏、地图、3D),实际上两种也都离不开Matrix,所以本质上可以理解为,一个负责对相机以外的物体成像,一个负责Android View的成像。这里我们重点来介绍系统UI成像的Camera。
UI成像需要使用到画布和坐标系。在Android系统中,View最终以二维方式显示,但是Camera是三维成像,遇到这种问题,我们这里需要用到Matrix了,它用来将三维转为二维。原理是Matrix矩阵将Camera的投影转到Canvas上,因此我们就能看见3D图形显示在二维坐标系中了。
关于Matrix请参阅:
Camera坐标系与Android坐标系
camera的坐标系是左手坐标系。伸出左手,让拇指和食指成L形,大拇指向右,食指向上,中指指向前方,这样我们就建立了一个左手坐标系,拇指,食指,中指的指向分别代表了x,y,z轴的正方向。如下图所示:
下面是一些细节点:
1,camera位于坐标点(0,0),也就是视图的左上角;
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度就是指物体上半部分向左翻转,下半部分向右翻转;
Android坐标系是二维的
1,camera位于坐标点(0,0),也就是视图的左上角;
2,垂直向下为y轴正方向
3、垂直向右为x轴正方向
两类坐标系比较
Camera与Matrix API
Camera创建一个没有任何转换效果的新的Camera实例
applyToCanvas(Canvas canvas) 根据当前的变换计算出相应的矩阵,然后应用到制定的画布上
getLocationX() 获取Camera的x坐标
getLocationY() 获取Camera的y坐标
getLocationZ() 获取Camera的z坐标
getMatrix(Matrixmatrix) 获取转换效果后的Matrix对象
restore() 恢复保存的状态
rotate(float x, float y, float z) 沿X、Y、Z坐标进行旋转
rotateX(float deg)
rotateY(float deg)
rotateZ(float deg)
save() 保存状态
setLocation(float x, float y, float z)
translate(float x, float y, float z)沿X、Y、Z轴进行平移
Matrix相关方法如下
setTranslate(floatdx,floatdy):控制Matrix进行平移
setSkew(floatkx,floatky,floatpx,floatpy):控制Matrix以px,py为轴心进行倾斜,kx,ky为X,Y方向上的倾斜距离
setRotate(floatdegress):控制Matrix进行旋转,degress控制旋转的角度
setRorate(floatdegress,floatpx,floatpy):设置以px,py为轴心进行旋转,degress控制旋转角度
setScale(floatsx,floatsy):设置Matrix进行缩放,sx,sy控制X,Y方向上的缩放比例
setScale(floatsx,floatsy,floatpx,floatpy):设置Matrix以px,py为轴心进行缩放,sx,sy控制X,Y方向上的缩放比例
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)));