大家新年好!
前面我们讲了矩阵在平面上的一些常用的变换,再前面我们讲了关于Android中属性动画的一些应用,不过这些变换都是发生在平面上的,是二维的,没有立体效果。立体效果的产生,是透视的效果,远处的东西看起来小,而近处的东西看起来大,大小的变化,再加上一些光影的处理,就形成了一种立体的感观,比如下面的立方体:
在Android中,实现3D的效果,可以通过OpenGL/ES,Camera,也可以通过Matrix的PolyToPoly来实现。OpenGL比较复杂麻烦,但是其实现的效果也最好,在游戏制作中利用的比较多,而Camera是Android中提供的一个包装好的类,模拟了一个观察点,通过改变这个观察点,从而实现纵深的变化,达到3d的效果。
我们前两篇文章都在讲Matrix,那么这一章我们也通过Matrix,再通过属性动画中ValueAnimator的应用, 来简单实现一个推拉门的效果吧。
下面是效果图:
这是一个很简单的推拉门效果,但是却是理解Matrix中SetPolyToPoly的一个很好的例子。
在当前的Activity上面,上面是一张图片,下面是一个按钮。当点击按钮的时候,图片的右上角和右下角会慢慢往后移动,从而形成一个推的效果。
下面我们结合代码来讲一下实现的思路。
1)在MainActivity中,创建一个ValueAnimator,设置一个从0 到 1变化的比例因子,叫rotateFactor,在变化的同时去更新PolyToPolyView。
PolyToPolyView是一个自定义的View。
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f,1f);
valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator arg0) {
float rotateFactor = (Float)arg0.getAnimatedValue();
polyView.setRotateFactor(rotateFactor);
}
});
...
btnRotate.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
valueAnimator.start();
}
});
2)创建自定义的View的时候,首先获得图片的宽高(即右上角和右下角的坐标),并初始化两个数组,一个是图片原点的几个角的坐标点,一个是图片在新的位置的坐标点。
private void init(){
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.photo1);
w = bitmap.getWidth();
h = bitmap.getHeight();
srcPoly = new float[POINTS_NUMBER];
dstPoly = new float[POINTS_NUMBER];
matrix = new Matrix();
}
3)根据每次传进来的rotateFactor,计算新的图片右上角,右下角所处的位置(右上角的位置要比原来的低,右下角的点要比原来的高,从而形成一种透视的效果),然后利用Matrix.setPolyToPoly方法来将原来的点映射上新的点上。
public void calculateMatrix(){
shouldDraw = true;
if(rotateFactor == 1){
shouldDraw = false;
return;
}
translateFactor = 1 - rotateFactor;
float translateWidth = w * translateFactor;//移动的距离
float translateWidthPerfoldsquare = translateWidth * translateWidth;
float deepth = (float)Math.sqrt(w * w - translateWidthPerfoldsquare);//利用勾股定理算出移动时,深度的变化
float scaleFactor = DEPTH_CONSTANT / (DEPTH_CONSTANT + deepth);//以这个深度来计算缩放的一个比例
float scaleWidth = w * translateFactor;//在图片右边往后移动的时候,在平面看去,图片的宽也会慢慢缩小。
float scaleHeight = h * scaleFactor;//高也是一样,离得越远,高度就会越小
float topScalePoint = (h - scaleHeight) / 2.f;//求出新的右上角的点
float bottomScalePoint = topScalePoint + scaleHeight;//求出新的右下角的点
srcPoly[0] = 0;
srcPoly[1] = 0;
srcPoly[2] = 0;
srcPoly[3] = h;
srcPoly[4] = w;
srcPoly[5] = 0;
srcPoly[6] = w;
srcPoly[7] = h;
dstPoly[0] = 0; //左上角跟左下角的点是不动的
dstPoly[1] = 0;
dstPoly[2] = dstPoly[0];
dstPoly[3] = h;
dstPoly[4] = scaleWidth; //右上角跟右下角的点是会慢慢往后移动的
dstPoly[5] = topScalePoint;
dstPoly[6] = dstPoly[4];
dstPoly[7] = bottomScalePoint;
if(dstPoly[4] <= dstPoly[0]){
shouldDraw = false;
return;
}
matrix.reset();
matrix.setPolyToPoly(srcPoly, 0, dstPoly, 0, POINTS_NUMBER / 2);//将原来的坐标映射到新的点上面
}
在这里,我们看一下setPolyToPoly这个方法。
/**
* Set the matrix such that the specified src points would map to the
* specified dst points. The "points" are represented as an array of floats,
* order [x0, y0, x1, y1, ...], where each "point" is 2 float values.
*
* @param src The array of src [x,y] pairs (points)
* @param srcIndex Index of the first pair of src values
* @param dst The array of dst [x,y] pairs (points)
* @param dstIndex Index of the first pair of dst values
* @param pointCount The number of pairs/points to be used. Must be [0..4]
* @return true if the matrix was set to the specified transformation
*/
public boolean setPolyToPoly(float[] src, int srcIndex,
float[] dst, int dstIndex,
int pointCount) {
这里我们看到pointCount这个参数,最大必须是4,也就是说,matric支持的映射点数最多就是4个点,而src和dst里面则是我们定义的坐标点,以[x0,y0,x1,y1,...]的形式存在,每两位为一个点,而srcIndex和dstIndex,则是表明我们要从哪个点开始映射的。在ApiDemo中,有提供一个PolyToPoly的例子,大家有兴趣可以去看一下。
在这里,我们就会利用这个方法,将坐标点的变换放到一个Matrix中。
3)当Animator在进行的时候,rotateFactor也会一直变化,相对应的,图片右上角和右下角的位置会一直被重新计算并刷新,就形成了往后推的效果。
4)在Canvas中,要去应用上面计算好的matrix,这里是利用了canvas.concat的方法,这是一个先乘(也即右乘),也就是实现这个matrix的变换。
public void onDraw(Canvas canvas){
calculateMatrix();
if(shouldDraw){
canvas.concat(matrix);
canvas.drawBitmap(bitmap, 0,0, null);
}
}
总的实现思路大概就是这样。方法总比问题多,像我们如果要形成3d效果,方法其实还是有各种各样的,我们可能多学习一点,从而对android的应用也多了解一点。