Android自定義view;實現掌閱打開書籍動畫效果

鏈接:https://www.jianshu.com/p/8950d620b1d2

打開書籍效果

最近看到了掌閱app的打開動畫,感覺不錯,模仿了一下。首先看一下效果。

Android自定義view;實現掌閱打開書籍動畫效果

這個效果通過打開書籍時的效果Camera來實現二維來模仿三維動畫(View沒有厚度是偽3D),並且放大畫布來實現的。

什麼是Camera

我們知道canvas用的是View的座標系一個二維的座標系。但是camera用的是三維的座標系,x軸與二維座標系相同,y軸與二維座標系相反,z軸是朝著view裡邊的。

在z軸會存在一個相機,通過相機來產生投影,就是看到的效果。相機的默認位置是-8(英寸),可以通過setLocation調整相機的距離來實現最終結果的大小。下圖為旋轉後的投影出的圖形

Android自定義view;實現掌閱打開書籍動畫效果

這裡Camera的軸心是canvas的座標原點,我們可以利用canvas.translate或者Matrix 平移到中心,然後把投影之後的圖形挪回來。

Camera常用方法

Camera一般是配合Matrix以及canvas使用。以下就是Camera最常見的方法

計算當前狀態下單矩陣對應的狀態,並將計算後的矩陣應用到指定的canvas上。

<code>void applyToCanvas (Canvas canvas)

/<code>

計算當前狀態下矩陣對應的狀態,並將計算後的矩陣賦值給參數matrix。

<code>void getMatrix (Matrix matrix)

/<code>

Camera旋轉和平移

Camera可以利用自己的三維座標系進行旋轉。

Android自定義view;實現掌閱打開書籍動畫效果

<code>void rotate (float x, float y, float z);

// 控制View繞單個座標軸旋轉
void rotateX (float deg);
void rotateY (float deg);
void rotateZ (float deg);

/<code>

當然Camera也有自己的平移效果不過很少用到,前邊說過x軸平移與二維座標系相同,y軸相反,移動z軸座標,當View和相機在同一條直線上時相當於縮放的效果,z軸平移的遠,看到的效果越小。如果不在一條直線上,在縮小的同時也在不斷接近攝像機在屏幕投影位置(通常情況下為Z軸,在平面上表現為接近座標原點)。相反,當View接近攝像機的時候,View在放大的同時會遠離攝像機在屏幕投影位置。

在z軸會存在一個相機,通過相機來產生投影,就是看到的效果。相機的默認位置是-8(英寸),可以通過setLocation調整相機的距離來實現最終結果的大小。下圖為旋轉後的投影出的圖形

當然Camera也有自己的平移效果不過很少用到,前邊說過x軸平移與二維座標系相同,y軸相反,移動z軸座標,當View和相機在同一條直線上時相當於縮放的效果,z軸平移的遠,看到的效果越小。如果不在一條直線上,在縮小的同時也在不斷接近攝像機在屏幕投影位置(通常情況下為Z軸,在平面上表現為接近座標原點)。相反,當View接近攝像機的時候,View在放大的同時會遠離攝像機在屏幕投影位置。

假設大矩形是手機屏幕,白色小矩形是View,攝像機位於屏幕左上角,請注意上面View與攝像機的距離以及下方View的大小以及距離左上角(攝像機在屏幕投影位置)的距離。

Android自定義view;實現掌閱打開書籍動畫效果

實現打開書籍動畫

這裡利用自定義view的方式來處理,初始化數據,camera通過setLocation調整相機的位置,但是Camera 的位置單位是英寸,英寸和像素的換算單位在 Skia 中被寫成了72 像素,8 x 72 = 576,所以它的默認位置是 (0, 0, -576)。所以這裡需要做一個位置的適配。

<code> 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();
}

/<code>

接下來看打開書籍的動畫,很簡單設置一個動畫係數。

<code>        openBookview.setVisibility(View.VISIBLE);
Bitmap bitmap = ((BitmapDrawable) bookshlefBook.getBackground()).getBitmap();
openBookview.openAnimation(bitmap, bookshlefBook.getLeft(), bookshlefBook.getTop(), bookshlefBook.getWidth(), bookshlefBook.getHeight(), new AnimatorListenerAdapter() {
@Override
public 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();
}

@Keep
public void setScaleX(float scale) {
if (isOpen) {
this.scale = scale;
} else {
this.scale = 1f - scale;
}
postInvalidate();
}

/<code>

自定義view核心部分

最後核心繪製代碼,本質是將畫布擴大並且使用camera旋轉y軸進行投影操作。首先通過動畫的scale平移當前的畫布,然後根據bitmap的像素值和總大小判斷擴大的比例,通過canvas.drawRect(bookRect, pageBackgroundPaint)繪製對應的黃色畫紙,通過canvas.translate(0, -coverHeight / 2);調整camera軸心。從而實現效果。

<code>        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();

/<code>

設置比例

最後說一下,bitmap顯示的比例,因為從原始書籍比例與達到最終效果的比例,在這裡viewScaleWidth和viewScaleHeight bitmap和外部控件的比例,maxScaleWidth和maxScaleHeight是最終達到效果的比例,也就是說畫布在擴大的時候按照原始書籍通過動畫係數一直到最終比例。

<code>    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();
}

/<code>
<code>    private void resetWidthHeight() {
if (coverBitmap != null) {
maxScaleWidth = (float) width / coverBitmap.getWidth();
maxScaleHeight = (float) height / coverBitmap.getHeight();
}
}

/<code>
<code>      float scaleX = viewScaleWidth + (maxScaleWidth - viewScaleWidth) * scale;
float scaleY = viewScaleHeight + (maxScaleHeight - viewScaleHeight) * scale;

canvas.scale(scaleX, scaleY);

/<code>

鏈接地址github;https://github.com/xiaoxiangyeyuHeaven/OpenBookView


分享到:


相關文章: