Android 自定义 view;实现掌阅打开书籍动画效果
Camera 旋转和平移
Camera 可以利用自己的三维坐标系进行旋转。
![](//upload-images.jianshu.io/upload_images/3637091-58a89dd94edad49a.jpg?imageM
ogr2/auto-orient/strip|imageView2/2/w/1200/format/webp)
void rotate (float x, float y, float z);
// 控制 View 绕单个坐标轴旋转 void rotateX (float deg);void rotateY (float deg);void rotateZ (float deg);
当然 Camera 也有自己的平移效果不过很少用到,前边说过 x 轴平移与二维坐标系相同,y 轴相反,移动 z 轴坐标,当 View 和相机在同一条直线上时相当于缩放的效果,z 轴平移的远,看到的效果越小。如果不在一条直线上,在缩小的同时也在不断接近摄像机在屏幕投影位置(通常情况下为 Z 轴,在平面上表现为接近坐标原点)。相反,当 View 接近摄像机的时候,View 在放大的同时会远离摄像机在屏幕投影位置。
在 z 轴会存在一个相机,通过相机来产生投影,就是看到的效果。相机的默认位置是-8(英寸),可以通过 setLocation 调整相机的距离来实现最终结果的大小。下图为旋转后的投影出的图形
当然 Camera 也有自己的平移效果不过很少用到,前边说过 x 轴平移与二维坐标系相同,y 轴相反,移动 z 轴坐标,当 View 和相机在同一条直线上时相当于缩放的效果,z 轴平移的远,看到的效果越小。如果不在一条直线上,在缩小的同时也在不断接近摄像机在屏幕投影位置(通常情况下为 Z 轴,在平面上表现为接近坐标原点)。相反,当 View 接近摄像机的时候,View 在放大的同时会远离摄像机在屏幕投影位置。
假设大矩形是手机屏幕,白色小矩形是 View,摄像机位于屏幕左上角,请注意上面 View 与摄像机的距离以及下方 View 的大小以及距离左上角(摄像机在屏幕投影位置)的距离。
实现打开书籍动画
这里利用自定义 view 的方式来处理,初始化数据,camera 通过 setLocation 调整相机的位置,但是 Camera 的位置单位是英寸,英寸和像素的换算单位在 Skia 中被写成了 72 像素,8 x 72 = 576,所以它的默认位置是 (0, 0, -576)。所以这里需要做一个位置的适配。
public OpenBookView(Context context) {super(context);}
public OpenBookView(Context context, AttributeSet attrs) {super(context, attrs);init();}
private void init() {camera = new Camera();pageBackgroundPaint = new Paint();pageBackgroundPaint.setColor(0xffFFD700);DisplayMetrics displayMetrics = getResources().getDisplayMetrics();float newZ = -displayMetrics.density * 6;camera.setLocation(0, 0, newZ);refreshData();}
接下来看打开书籍的动画,很简单设置一个动画系数。
openBookview.setVisibility(View.VISIBLE);Bitmap bitmap = ((BitmapDrawable) bookshlefBook.getBackground()).getBitmap();openBookview.openAnimation(bitmap, bookshlefBook.getLeft(), bookshlefBook.getTop(), bookshlefBook.getWidth(), bookshlefBook.getHeight(), new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {Intent intent = new Intent(MainActivity.this, BrowserBookActivity.class);startActivityForResult(intent, RESULT_FIRST_USER);overridePendingTransition(0, 0);}});
public void openAnimation(Bitmap coverBitmap, float left, float top, float width, float height, AnimatorListenerAdapter adapter) {this.coverBitmap = coverBitmap;coverWidth = width;coverHeight = height;coverLeft = left;coverTop = top;isOpen = true;refreshData();startAnim(adapter);}
private void startAnim(AnimatorListenerAdapter adapter) {ObjectAnimator animator = ObjectAnimator.ofFloat(this, "scaleX", 0f, 1f);animator.setDuration(DURATION);animator.addListener(adapter);animator.start();}
@Keeppublic void setScaleX(float scale) {if (isOpen) {this.scale = scale;} else {this.scale = 1f - scale;}postInvalidate();}
自定义 view 核心部分
最后核心绘制代码,本质是将画布扩大并且使用 camera 旋转 y 轴进行投影操作。首先通过动画的 scale 平移当前的画布,然后根据 bitmap 的像素值和总大小判断扩大的比例,通过 canvas.drawRect(bookRect, pageBackgroundPaint)绘制对应的黄色画纸,通过 canvas.translate(0, -coverHeight / 2);调整 camera 轴心。从而实现效果。
canvas.save();
canvas.translate(coverLeft - coverLeft * scale, coverTop - coverTop * scale);float scaleX = viewScaleWidth + (maxScaleWidth - viewScaleWidth) * scale;float scaleY = viewScaleHeight + (maxScaleHeight - viewScaleHeight) * scale;
Log.e("scaleX", "" + scaleX);
canvas.scale(scaleX, scaleY);canvas.drawRect(bookRect, pageBackgroundPaint);camera.save();
canvas.save();canvas.translate(0, -coverHeight / 2);camera.rotateY(-180 * scale);camera.applyToCanvas(canvas);canvas.translate(0, coverHeight / 2);
canvas.drawBitmap(coverBitmap, 0, 0, pageBackgroundPaint);camera.restore();canvas.restore();canvas.restore();
设置比例
最后说一下,bitmap 显示的比例,因为从原始书籍比例与达到最终效果的比例,在这里 viewScaleWidth 和 viewScaleHeight bitmap 和外部控件的比例,maxScaleWidth 和 maxScaleHeight 是最终达到效果的比例,也就是说画布在扩大的时候按照原始书籍通过动画系数一直到最终比例。
private void refreshData() {if (coverBitmap == null) {return;}viewScaleWidth = coverWidth / coverBitmap.getWidth();viewScaleHeight = coverHeight / coverBitmap.getHeight();bookRect = new Rect(0, 0, coverBitmap.getWidth(), coverBitmap.getHeight());resetWidthHeight();}
评论