自定义View—利用Camera实现3D翻转动画
实现效果图
做这个效果时,开始觉得很懵逼,无从下手,但冷静下来想一想,就是两张图片同时在执行旋转动画,那么我就从一张图片的旋转动画开始摸索。
好,现在就从画一张图开始
public class Roll3DView extends View {
private Bitmap bitmap;
private Paint paint;
private Camera camera;
private Matrix matrix;
private int viewWidth;
private int viewHeight;
private ValueAnimator animator;
private float degree;
public Roll3DView(Context context) {
this(context,null);
}
public Roll3DView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
bitmap = ((BitmapDrawable)(getResources().getDrawable(R.drawable.img1))).getBitmap();
camera = new Camera();
matrix = new Matrix();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
viewWidth = getMeasuredWidth();
viewHeight = getMeasuredHeight();
bitmap = scaleBitmap(bitmap);
}
/**
* 根据给定的宽和高进行拉伸
*
* @param origin 原图
* @return new Bitmap
*/
private Bitmap scaleBitmap(Bitmap origin) {
if (origin == null) {
return null;
}
int height = origin.getHeight();
int width = origin.getWidth();
float scaleWidth = ((float) viewWidth) / width;
float scaleHeight = ((float) viewHeight) / height;
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);// 使用后乘
Bitmap newBM = Bitmap.createBitmap(origin, 0, 0, width, height, matrix, false);
return newBM;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
camera.getMatrix(matrix);
canvas.drawBitmap(bitmap,matrix,paint);
canvas.restore();
}
自定义Roll3DView,初始化Paint,Camera,Matrix。在onMeasure里对View进行测量,然后对图片进行了适当的伸缩处理。然后就是onDraw方法画出这张图。效果图先不看了,就是一张图。
下面呢,我想让这张做旋转动画,围绕x轴旋转90度,因此我写了startAnimation方法
public void startAnimation(){
animator = ValueAnimator.ofFloat(0,90);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
degree = (float) valueAnimator.getAnimatedValue();
invalidate();
}
});
animator.setDuration(2000);
animator.start();
}
当我点击图片的时候开始执行startAnimation,变量degree从0到90度变化,动画执行时间为2s,那么onDraw方法要修改
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
camera.save();
camera.rotateX(degree);
camera.getMatrix(matrix);
camera.restore();
canvas.drawBitmap(bitmap,matrix,paint);
canvas.restore();
}
调用Camera的rotateX()方法对图片旋转,看下效果图
这时我们的旋转轴的坐标就是图片的上边沿,图片是围绕着上边沿旋转的。而仔细去看3D效果的Vertical&toNext效果,第一张图的旋转时围绕着图的下边沿旋转的。那么我们就要先将轴线平移到图片的下边沿,然后再旋转的过程中再慢慢回移。修改onDraw
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
camera.save();
camera.rotateX(degree);
camera.getMatrix(matrix);
camera.restore();
matrix.preTranslate(-viewWidth/2,-viewHeight);
matrix.postTranslate(viewWidth/2,(1-rate)*viewHeight);
canvas.drawBitmap(bitmap,matrix,paint);
canvas.restore();
}
再看看效果,
就是这个效果,可以这么理解。图片是围绕着下边沿进行旋转的,图片的初始角度是0,先移动轴线到图片的下边沿,让图片围绕着下边沿旋转,然后在旋转的过程中将轴线从y=viewHeight位置慢慢移动到坐标y=0。
那么同理,接着看3D翻转Vertical&toNext效果,第二张图翻上来的动画,第二张图片是围绕上图片的上边沿进行旋转的,图片的初始角度是-90度,轴线不用改变,在旋转的过程中将轴线从y=viewHeight位置移动到y=0。
使用同一个animator, degree还是从0到90度变化,代码如下
canvas.save();
camera.save();
camera.rotateX(degree-90);
camera.getMatrix(matrix);
camera.restore();
matrix.preTranslate(-viewWidth/2,0);
matrix.postTranslate(viewWidth/2,(1-rate)*viewHeight);
canvas.drawBitmap(bitmap,matrix,paint);
canvas.restore();
看效果
两个单独的效果实现了,所以下面就将两个效果合起来,画两张图。
Bitmap curBitmap = bitmapList.get(curIndex);
Bitmap nextBitmap = bitmapList.get(nextIndex);
canvas.save();
camera.save();
camera.rotateX(rotatedDegree);
camera.getMatrix(matrix);
camera.restore();
matrix.preTranslate(-viewWidth / 2, -viewHeight);
matrix.postTranslate(viewWidth / 2, (1 - rate) * viewHeight);
canvas.drawBitmap(curBitmap, matrix, paint);
camera.save();
camera.rotateX(rotatedDegree - 90);
camera.getMatrix(matrix);
camera.restore();
matrix.preTranslate(-viewWidth / 2, 0);
matrix.postTranslate(viewWidth / 2, (1 - rate) * viewHeight);
canvas.drawBitmap(nextBitmap, matrix, paint);
canvas.restore();
代码到这里当然还没结束,只是分析了其中的一种情况,而其他的情况都跟这中情况是类似的,无非就是是垂直翻转或水平翻转,下一张,前一张的区别,思想都是一样的,代码我也不贴了,如果是想实现这效果的话,最好是自己写个demo,一点点去实现,慢慢琢磨研究,才会记忆深刻。如果你认真写了,你就会发现其实很有很多细节没有处理呢,例如下面:
public void toNext() {
if (isRolling) return;
animator = ValueAnimator.ofFloat(0, 90);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
rotatedDegree = (float) animation.getAnimatedValue();
setRotateDegree(rotatedDegree);
invalidate();
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
isRolling = false;
setRotateDegree(0);
invalidate();
curIndex++;
if (curIndex > bitmapList.size() - 1) {
curIndex = 0;
}
initIndex();
}
});
animator.setDuration(1000);
isNext = true;
isRolling = true;
animator.start();
}
public void setRotateDegree(float rotateDegree) {
this.rotatedDegree = rotateDegree;
rate = rotateDegree / 90;
}
private void initIndex() {
nextIndex = curIndex + 1;
preIndex = curIndex - 1;
if (nextIndex > bitmapList.size() - 1) {
nextIndex = 0;
}
if (preIndex < 0) {
preIndex = bitmapList.size() - 1;
}
}
isRolling是判断当前动画是否执行完成,对图片下标index的初始化,对初始旋转角度的初始化。实现效果后再对代码进行简化,整理,封装等等。
欢迎大家提出不同的理解和看法,共同学习,进步。
上一篇: Android自定义View-自定义组件
下一篇: 如何在Linux服务器上部署禅道