1. 程式人生 > >(二十五)3D 翻轉效果

(二十五)3D 翻轉效果

一、ViewPager 的 3D 效果

1.ViewPager

先直接上個簡單的 ViewPager 的 demo。
activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
> <android.support.v4.view.ViewPager android:id="@+id/vp" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout>

TranslateFragment

public class TranslateFragment extends Fragment {

    @Nullable
    @Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { Bundle bundle = getArguments(); int layoutId = bundle.getInt("layoutId"); View view = inflater.inflate(layoutId, null); return view; } }

MainActivity

public class MainActivity extends AppCompatActivity {

    private ViewPager vp;
    //這三個佈局介面就不貼出來,隨便一個介面都可以
    private int[] layouts = {
            R.layout.welcome1,
            R.layout.welcome2,
            R.layout.welcome3
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        vp = (ViewPager) findViewById(R.id.vp);
        MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager());
        vp.setAdapter(adapter);
    }

    class MyPagerAdapter extends FragmentPagerAdapter {

        public MyPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            Fragment f = new TranslateFragment();
            Bundle bundle = new Bundle();
            bundle.putInt("layoutId", layouts[position]);
            f.setArguments(bundle );
            return f;
        }

        @Override
        public int getCount() {
            return 3;
        }
    }
}

使用 v4 支援包下的 ViewPager,可以很方便的做出分介面滑動效果。執行效果:
這裡寫圖片描述

2.TransFormer

ViewPager 在切換介面的時候想要使用 3D 效果,需要用到一個介面轉換器 PageTransformer,也是在 v4 支援包裡面(v4 包會不斷更新,較早的 v4 包沒有 PageTransformer),PageTransformer 是一個介面,需要實現。

實現 PageTransformer 介面,需要實現 transformPage(View page, float position) 方法,在滑動過程中,每一個頁面 View 都會不停呼叫這個方法

transformPage(View page, float position)
page:當前頁
position:當前頁的位置,在螢幕正中間為 0,在左邊為負,右邊為正。

來個簡單的 PageTransformer,滑動的時候,當前介面慢慢縮小滑出去,下一個介面慢慢放大進入螢幕。
WelcomeTransFormer

public class WelcomeTransFormer implements ViewPager.PageTransformer {

    @Override
    public void transformPage(View page, float position) {
        page.setScaleX(1 - Math.abs(position));
        page.setScaleY(1 - Math.abs(position));
    }
}

記得要呼叫 ViewPager 的 setPageTransformer。
MainActivity

public class MainActivity extends AppCompatActivity {

    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        vp = (ViewPager) findViewById(R.id.vp);
        MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager());
        vp.setPageTransformer(true, new WelcomeTransFormer());
        vp.setAdapter(adapter);
    }
    ...
}

效果:
這裡寫圖片描述

3.3D 翻轉

在切換過程中,讓當前介面與下一個介面繞公共邊進行旋轉即可。
WelcomeTransFormer

public class WelcomeTransFormer implements ViewPager.PageTransformer {

    @Override
    public void transformPage(View page, float position) {
        //0~45度
        if (position > -1 && position < 1){
            page.setPivotX(position < 0f?page.getWidth() : 0f);//左邊頁面:0~-1;右邊的頁面:1~0
            //不進行設定旋轉點的 Y 也可以,預設中點。
            page.setPivotY(page.getHeight()*0.5f);
            //0~45度
            page.setRotationY(position*45f);
        }
    }

效果:
這裡寫圖片描述

注:個人測試時候在華為安卓7.0的手機,旋轉效果直接白屏,原因未知。有人說是華為手機在 3D 這塊效果處理一直有問題,但是使用三星的模擬器,在一些 3D 效果偶爾也碰到問題。

二、Camera

android.graphics.Camera.java,系統還有個是硬體的 Camera,別導錯了。

原始碼裡面的 Camera 備註:用來計算 3D 變換,以及生成一個用於畫布繪製的 matrix 矩陣。

/**
* A camera instance can be used to compute 3D transformations and
* generate a matrix that can be applied, for instance, on a
* {@link Canvas}.
*/

Camera 內部實際也是用上 OpenGL。很多時候我們用 OpenGL 做 3D 特效,其實 Camera 可以滿足絕大部分的需要。 Camera 內部很多方法是 native 的,由底層 C 去實現呼叫。

1.Camera 的方向

這裡寫圖片描述

Camera X 的正方向是水平向右,Y 的正方向是向上的,這點與螢幕的方向不一樣,Z 的正方向是遠離眼睛的方向(效果就是 Z 為正,縮小; Z 為負,放大)。

2.Camera 簡單 Demo

自定義 Camera 類,直接畫一個圓。
CameraView

public class CameraView extends View {

    private Paint paint;

    public CameraView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.GREEN);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.drawCircle(400, 400, 300, paint);
    }
}

在佈局檔案引用改控制元件。

<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.xiaoyue.camera.MainActivity">

    <com.xiaoyue.camera.CameraView
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</android.support.constraint.ConstraintLayout>

效果:
這裡寫圖片描述

3.使用 Camera 進行變化

CameraView

public class CameraView extends View {

    private Camera camera;
    private Matrix matrix;
    private Paint paint;

    public CameraView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.GREEN);

        camera = new Camera();
        matrix = new Matrix();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        matrix.reset();
        camera.save();

        camera.translate(100, 100, 0);
        camera.getMatrix(matrix);

        camera.restore();

        canvas.concat(matrix);
        canvas.drawCircle(400, 400, 300, paint);
    }
}

效果:
這裡寫圖片描述

這邊在 Y 方向上是正的,但是往上平移,再次說明一下:Camera 裡面的座標系跟 Android 的螢幕座標系不一樣。

4.Camera 的 Z 方向

CameraView

public class CameraView extends View {
    ...
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        matrix.reset();
        camera.save();

        camera.translate(100, 100, 400);
        ...
    }
}

效果:
這裡寫圖片描述

Z 方向為正,即遠離眼睛方向,所以對比上一個沒有進行 Z 方向變化的圓,這個圓縮小了。

三、Camera 的旋轉

新建一個 CameraView2 的自定義控制元件,對圖片進行旋轉。

CameraView2 :

public class CameraView2 extends View {
    private final Camera camera;
    private final Matrix matrix;
    private Paint paint;
    Bitmap bitmap;


    public CameraView2(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.GREEN);

        camera = new Camera();
        matrix = new Matrix();

        bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.zuobiao);

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        matrix.reset();
        camera.save();
        camera.rotateX(45);
        camera.getMatrix(matrix);
        camera.restore();

        canvas.drawBitmap(bitmap,matrix,paint);
    }
}

效果:
這裡寫圖片描述

繞 X 軸旋轉,則圖片的下方離眼睛較近,所以放大。有時候我們需要對圖片進行中心旋轉,我們沒辦法把圖片的中點設為旋轉點,所以需要在旋轉前把圖片的中點移到左上角,旋轉後在移動回來。

Camera 的旋轉最終是通過矩陣來實現的,可以直接對矩陣進行前乘和後乘進行設定。

CameraView2 :

public class CameraView2 extends View {
    ...
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        matrix.reset();
        camera.save();
        camera.rotateX(45);//繞著X軸旋轉

        camera.getMatrix(matrix);
        camera.restore();
        //前乘矩陣---先平移矩陣,再進行變換
        matrix.preTranslate(-bitmap.getWidth()/2,-bitmap.getHeight()/2);
        //變換玩之後,再恢復到原來的位置
        matrix.postTranslate(bitmap.getWidth()/2,bitmap.getHeight()/2);

        canvas.drawBitmap(bitmap,matrix,paint);
    }
}

效果:
這裡寫圖片描述

四、Camera 的翻轉動畫

先來看一下效果:

2D 滾動:
這裡寫圖片描述

3D 整體滾動:
這裡寫圖片描述

尾部逐漸分離再合併:
這裡寫圖片描述

百葉窗:
這裡寫圖片描述

各模組依次滾動:
這裡寫圖片描述

注:這些效果我在華為 7.0 系統執行還是有一些異常,各型號手機不確定是否有效果,應該跟手機的底層對 Carema 處理有關。

這邊採用一個自定義控制元件實現,翻轉模式在 xml 中根據自定義屬性進行設定。這邊直接上程式碼了,具體內容都解除安裝註釋裡面。

Camera3DView

public class Camera3DView extends View {

    private Context context;
    //畫筆
    private Paint paint;
    //畫布
    private Camera camera;
    //矩陣
    private Matrix matrix;

    //圖片集合
    private List<Bitmap> bitmapList;
    //每個圖片要切割成小塊的個數
    private int partNumber = 1;
    //切割後圖片的儲存(有多張圖片)
    private List<Bitmap[]> bitmapsList;

    //滾動方向:1豎直方向 其他為水平方向
    private int direction = 1;
    //滾動模式
    private RollMode rollMode = RollMode.SepartConbine;
    //圖片下標,前一個,當前個、下一個
    private int preIndex = 0, currIndex = 0, nextIndex = 0;

    //控制元件的寬高
    private int viewWidth, viewHeight;
    //切割小塊的寬高
    int averageWidth = 0, averageHeight = 0;

    //動畫執行的進度
    private float progress = 0;

    //各模組依次滾動 RollInTurn 下情況下,各小模組的動畫執行時間佔總動畫時間比
    private float partAnimatorPercent = 0;
    //小模組開始執行動畫時間差
    private float partSpaceTime = 0;


    public Camera3DView(Context context) {
        this(context, null);
    }

    public Camera3DView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public Camera3DView(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }
    public Camera3DView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

        TypedArray a = getContext().obtainStyledAttributes(attrs,R.styleable.Camera3DView_Param);
        int caremaRollMode = a.getInt(R.styleable.Camera3DView_Param_camera_roll_mode, 0);
        partAnimatorPercent = a.getFloat(R.styleable.Camera3DView_Param_part_animator_percent, 1);
        partNumber = a.getInt(R.styleable.Camera3DView_Param_partNumber, 3);
        direction = a.getInt(R.styleable.Camera3DView_Param_camera_roll_direction, 1);

        if (caremaRollMode == 0) {
            rollMode = RollMode.Roll2D;
        } else if (caremaRollMode == 1) {
            rollMode = RollMode.Whole3D;
        } else if (caremaRollMode == 2) {
            rollMode = RollMode.SepartConbine;
        } else if (caremaRollMode == 3) {
            rollMode = RollMode.Jalousie;
        } else {
            if (partAnimatorPercent <= 0 || partAnimatorPercent >= 1) {
                rollMode = RollMode.SepartConbine;
            }else {
                rollMode = RollMode.RollInTurn;
                partSpaceTime = (1 - partAnimatorPercent) / partNumber;
            }
        }


        //初始化
        init(context);
    }

    /**
     * 初始化一些引數
     * @param context
     */
    private void init(Context context) {
        bitmapList = new ArrayList<>();
        bitmapsList = new ArrayList<>();
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        camera = new Camera();
        matrix = new Matrix();
        this.context = context;
    }

    /**
     * 新增圖片
     * @param bitmap 要新增的圖片
     */
    public void addBitmap(Bitmap bitmap){

        bitmapList.add(bitmap);

        //這是為了處理佈局完之後新增的圖片
        if (viewWidth != 0 && viewHeight != 0) {
            //縮放處理bitmap
            bitmap = scaleBitmap(bitmap);
            //切割操作
            initBitmap(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;
    }
    private void initBitmap(Bitmap bitmap) {
        //初始化
        initIndex();
        Bitmap partBitmap;
        Bitmap[] partBitmaps = new Bitmap[partNumber];

        for (int i = 0; i < partNumber; i ++){
            Rect rect;
            if(rollMode != RollMode.Jalousie){
                if(direction == 1){
                    //縱向切割
                    rect = new Rect(i * averageWidth, 0, (i + 1) * averageWidth, viewHeight);
                    //按照矩形區域切割圖片
                    partBitmap = getPartBitmap(bitmap, i * averageWidth, 0, rect);
                }else{
                    //橫向切割
                    rect = new Rect(0, i * averageHeight, viewWidth, (i + 1) * averageHeight);
                    partBitmap = getPartBitmap(bitmap, 0, i * averageHeight, rect);
                }
            }else{
                if (direction == 1) {
                    //橫向切割
                    rect = new Rect(0, i * averageHeight, viewWidth, (i + 1) * averageHeight);
                    partBitmap = getPartBitmap(bitmap, 0, i * averageHeight, rect);
                } else {
                    //縱向切割
                    rect = new Rect(i * averageWidth, 0, (i + 1) * averageWidth, viewHeight);
                    partBitmap = getPartBitmap(bitmap, i * averageWidth, 0, rect);
                }
            }
            partBitmaps[i] = partBitmap;
        }
        bitmapsList.add(partBitmaps);
    }

    /**
     * 切割圖片
     * @param bitmap
     * @param left
     * @param top
     * @param rect
     * @return
     */
    private Bitmap getPartBitmap(Bitmap bitmap, int left, int top, Rect rect) {
        return Bitmap.createBitmap(bitmap,left,top,rect.width(),rect.height());
    }

    /**
     * 根據當前圖片的下標,計算前一個與後一個的下標
     */
    private void initIndex() {
        int listSize = bitmapList.size();
        nextIndex = currIndex +1;
        preIndex = currIndex -1;
        if(nextIndex > listSize -1){
            nextIndex = 0;//當到了邊界,再迴圈變換
        }
        if(preIndex < 0){
            preIndex = listSize -1;
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //獲取控制元件寬高
        viewWidth = getMeasuredWidth();
        viewHeight = getMeasuredHeight();
        //獲取切割後小塊寬高
        averageWidth = (int)(viewWidth/partNumber);
        averageHeight = (int)(viewHeight/partNumber);

        //處理在佈局前新增的圖片
        if (viewWidth != 0 && viewHeight != 0) {
            for (int i = 0; i < bitmapList.size(); i ++) {
                //縮放處理bitmap
                bitmapList.set(i, scaleBitmap(bitmapList.get(i)));
                //切割操作
                initBitmap(bitmapList.get(i));
            }
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (bitmapList.size() == 0) {
            return;
        }

        switch (rollMode){
            case Roll2D:
                drawRoll2D(canvas);
                break;
            case Whole3D:
                drawWhole3D(canvas);
                break;

            case SepartConbine:
                drawSepartConbine(canvas);
                break;
            case Jalousie:
                drawJalousie(canvas);
                break;
            case RollInTurn:
                drawRollInTurn(canvas);
                break;
        }

    }

    /**
     * 2D 滾動繪製
     * @param canvas
     */
    private void drawRoll2D(Canvas canvas) {
        canvas.save();
        Bitmap currWholeBitmap = bitmapList.get(currIndex);
        Bitmap nextWholeBitmap = bitmapList.get(nextIndex);

        if(direction == 1){
            //豎直方向滑動

            //當前圖片滑動
            matrix.postTranslate(0, progress * viewHeight);
            canvas.drawBitmap(currWholeBitmap, matrix, paint);
            matrix.reset();

            //下一張圖片滑動
            matrix.postTranslate(0, -(1 - progress) * viewHeight);
            canvas.drawBitmap(nextWholeBitmap, matrix, paint);
            matrix.reset();
        } else {
            //水平方向滑動

            //當前圖片滑動
            matrix.postTranslate(-progress * viewHeight, 0);
            canvas.drawBitmap(currWholeBitmap, matrix, paint);
            matrix.reset();

            //下一張圖片滑動
            matrix.postTranslate((1 - progress) * viewHeight, 0);
            canvas.drawBitmap(nextWholeBitmap, matrix, paint);
            matrix.reset();
        }
        canvas.restore();
    }

    /**
     * 3D 整體滾動
     * @param canvas
     */
    private void drawWhole3D(Canvas canvas) {
        canvas.save();
        Bitmap currWholeBitmap = bitmapList.get(currIndex);
        Bitmap nextWholeBitmap = bitmapList.get(nextIndex);

        if(direction == 1){
            //豎直方向滑動

            //兩張分別進行旋轉,繞著同一個軸旋轉
            float axisY = progress * viewHeight;

            //第一張圖片旋轉
            camera.save();
            camera.rotateX(-progress * 90);
            camera.getMatrix(matrix);
            camera.restore();
            //下面的view繞著自己的top旋轉,Y 方向不需要進行平移
            matrix.preTranslate(-viewWidth/2, 0);
            //轉完之後顯示要往下平移 axisY
            matrix.postTranslate(viewWidth/2, axisY);
            canvas.drawBitmap(currWholeBitmap, matrix, paint);

            //第二張圖片旋轉
            camera.save();
            camera.rotateX((1 - progress) * 90);
            camera.getMatrix(matrix);
            camera.restore();
            //下面的view繞著自己的buttom旋轉,Y 向上平移 viewHeight
            matrix.preTranslate(-viewWidth/2, -viewHeight);
            //轉完之後顯示要往下平移 axisY
            matrix.postTranslate(viewWidth/2, axisY);
            canvas.drawBitmap(nextWholeBitmap, matrix, paint);
        }else{
            //水平方向滑動
            float axisX = (1 - progress) * viewWidth;

            camera.save();
            camera.rotateY(-progress * 90);
            camera.getMatrix(matrix);
            camera.restore();

            matrix.preTranslate(-viewWidth, -viewHeight / 2);
            matrix.postTranslate(axisX, viewHeight / 2);
            canvas.drawBitmap(currWholeBitmap, matrix, paint);

            camera.save();
            camera.rotateY((1 - progress) * 90);
            camera.getMatrix(matrix);
            camera.restore();

            matrix.preTranslate(0, -viewHeight / 2);
            matrix.postTranslate(axisX, viewHeight / 2);
            canvas.drawBitmap(nextWholeBitmap, matrix, paint);
        }
        canvas.restore();
    }

    /**
     * 尾部逐漸分離再合併
     * @param canvas
     */
    private void drawSepartConbine(Canvas canvas) {
        //跟 3D 整體滾動對比,
        //3D 整體滾動是一整張圖片進行翻轉
        //這邊是切割後的一張張小圖片進行翻轉
        canvas.save();
        Bitmap[] currWholeBitmaps = bitmapsList.get(currIndex);
        Bitmap[] nextWholeBitmaps = bitmapsList.get(nextIndex);

        if(direction == 1){
            //豎直方向滑動

            //兩張分別進行旋轉,繞著同一個軸旋轉
            float axisY = progress * viewHeight;

            //第一張圖片旋轉
            camera.save();
            camera.rotateX(-progress * 90);
            camera.getMatrix(matrix);
            camera.restore();
            //下面的view繞著自己的top旋轉,Y 方向不需要進行平移
            matrix.preTranslate(-viewWidth/2, 0);
            //轉完之後顯示要往下平移 axisY
            matrix.postTranslate(viewWidth/2, axisY);

            for (int i = 0; i < partNumber; i ++) {
                canvas.drawBitmap(currWholeBitmaps[i], matrix, paint);
                //每繪製一張小圖片,需要往下平移,否則會覆蓋住
                matrix.postTranslate(averageWidth, 0);
            }

            //第二張圖片旋轉
            camera.save();
            camera.rotateX((1 - progress) * 90);
            camera.getMatrix(matrix);
            camera.restore();
            //下面的view繞著自己的buttom旋轉,Y 向上平移 viewHeight
            matrix.preTranslate(-viewWidth/2, -viewHeight);
            //轉完之後顯示要往下平移 axisY
            matrix.postTranslate(viewWidth/2, axisY);

            for (int i = 0; i < partNumber; i ++) {
                canvas.drawBitmap(nextWholeBitmaps[i], matrix, paint);
                matrix.postTranslate(averageWidth, 0);
            }
        }else{
            //水平方向滑動
            float axisX = (1 - progress) * viewWidth;

            camera.save();
            camera.rotateY(-progress * 90);
            camera.getMatrix(matrix);
            camera.restore();

            matrix.preTranslate(-viewWidth, -viewHeight / 2);
            matrix.postTranslate(axisX, viewHeight / 2);

            for (int i = 0; i < partNumber; i ++) {
                canvas.drawBitmap(currWholeBitmaps[i], matrix, paint);
                matrix.postTranslate(0, averageHeight);
            }

            camera.save();
            camera.rotateY((1 - progress) * 90);
            camera.getMatrix(matrix);
            camera.restore();

            matrix.preTranslate(0, -viewHeight / 2);
            matrix.postTranslate(axisX, viewHeight / 2);

            for (int i = 0; i < partNumber; i ++) {
                canvas.drawBitmap(nextWholeBitmaps[i], matrix, paint);
                matrix.postTranslate(0, averageHeight);
            }
        }
        canvas.restore();
    }

    /**
     * 百葉窗
     * @param canvas
     */
    private void drawJalousie(Canvas canvas) {
        canvas.save();
        Bitmap[] currWholeBitmaps = bitmapsList.get(currIndex);
        Bitmap[] nextWholeBitmaps = bitmapsList.get(nextIndex);

        if(direction == 1){
            //豎直方向翻轉

            if (progress < 0.5) {
                //第一張圖片旋轉
                for (int i = 0; i < partNumber; i ++) {
                    //每一小部分旋轉軸不一樣
                    float axisY = (i + 0.5f) * averageHeight;

                    camera.save();
                    camera.rotateX(-progress * 180);
                    camera.getMatrix(matrix);
                    camera.restore();
                    matrix.preTranslate(-viewWidth/2, -0.5f * averageHeight);
                    matrix.postTranslate(viewWidth/2, axisY);

                    canvas.drawBitmap(currWholeBitmaps[i], matrix, paint);
                }
            } else {
                //第二張圖片旋轉
                for (int i = 0; i < partNumber; i ++) {
                    //每一小部分旋轉軸不一樣
                    float axisY = (i + 0.5f) * averageHeight;

                    camera.save();
                    camera.rotateX((1 - progress) * 180);
                    camera.getMatrix(matrix);
                    camera.restore();
                    //下面的view繞著自己的top旋轉,Y 方向不需要進行平移
                    matrix.preTranslate(-viewWidth/2, -0.5f * averageHeight);
                    //轉完之後顯示要往下平移 axisY
                    matrix.postTranslate(viewWidth/2, axisY);

                    canvas.drawBitmap(nextWholeBitmaps[i], matrix, paint);
                }
            }

        }else{
            //水平方向滑動

            if (progress < 0.5) {
                //第一張圖片旋轉
                for (int i = 0; i < partNumber; i ++) {
                    //每一小部分旋轉軸不一樣
                    float axisY = (i + 0.5f) * averageWidth;

                    camera.save();
                    camera.rotateY(-progress * 180);
                    camera.getMatrix(matrix);
                    camera.restore();
                    matrix.preTranslate(-0.5f * averageWidth, -viewHeight/2);
                    matrix.postTranslate(axisY, viewHeight/2);

                    canvas.drawBitmap(currWholeBitmaps[i], matrix, paint);
                }
            } else {
                //第二張圖片旋轉
                for (int i = 0; i < partNumber; i ++) {
                    //每一小部分旋轉軸不一樣
                    float axisY = (i + 0.5f) * averageWidth;

                    camera.save();
                    camera.rotateY((1 - progress) * 180);
                    camera.getMatrix(matrix);
                    camera.restore();
                    matrix.preTranslate(-0.5f * averageWidth, -viewHeight/2);
                    matrix.postTranslate(axisY, viewHeight/2);

                    canvas.drawBitmap(nextWholeBitmaps[i], matrix, paint);
                }
            }
        }
        canvas.restore();
    }

    /**
     * 各模組依次滾動
     * @param canvas
     */
    private void drawRollInTurn(Canvas canvas) {
        canvas.save();
        Bitmap[] currWholeBitmaps = bitmapsList.get(currIndex);
        Bitmap[] nextWholeBitmaps = bitmapsList.get(nextIndex);

        if(direction == 1){
            //豎直方向滑動
            float partProgress;
            float axisY;
            for (int i = 0; i < partNumber; i ++) {
                //計算當前小模組執行動畫進度
                partProgress = getPartProgress(i);
                axisY = partProgress * viewHeight;

                //第一張圖片旋轉
                camera.save();
                camera.rotateX(-partProgress * 90);
                camera.getMatrix(matrix);
                camera.restore();
                matrix.preTranslate(-viewWidth/2, 0);
                matrix.postTranslate(viewWidth/2 + averageWidth * i, axisY);

                canvas.drawBitmap(currWholeBitmaps[i], matrix, paint);

                //第二張圖片旋轉
                camera.save();
                camera.rotateX((1 - partProgress) * 90);
                camera.getMatrix(matrix);
                camera.restore();
                matrix.preTranslate(-viewWidth/2, -viewHeight);
                matrix.postTranslate(viewWidth/2 + averageWidth * i, axisY);

                canvas.drawBitmap(nextWholeBitmaps[i], matrix, paint);
            }
        }else{
            float partProgress;
            float axisX;
            for (int i = 0; i < partNumber; i ++) {
                //計算當前小模組執行動畫進度
                partProgress = getPartProgress(i);
                axisX = partProgress * viewWidth;

                //第一張圖片旋轉
                camera.save();
                camera.rotateY(-partProgress * 90);
                camera.getMatrix(matrix);
                camera.restore();
                matrix.preTranslate(0, -viewHeight/2);
                matrix.postTranslate(axisX, viewHeight/2 + averageWidth * i);

                canvas.drawBitmap(currWholeBitmaps[i], matrix, paint);

                //第二張圖片旋轉
                camera.save();
                camera.rotateY((1 - partProgress) * 90);
                camera.getMatrix(matrix);
                camera.restore();
                matrix.preTranslate(-viewWidth, -viewHeight/2);
                matrix.postTranslate(axisX, viewHeight/2 + averageWidth * i);

                canvas.drawBitmap(nextWholeBitmaps[i], matrix, paint);
            }
        }
        canvas.restore();
    }

    /**
     * 計算當前進度下,各小模組的進度
     * @param index
     * @return
     */
    private float getPartProgress (int index) {
        //計算當前小模組在什麼進度時候啟動
        float begin = index * partSpaceTime;

        if (progress <= begin) {
            //還沒開始執行
            return 0;
        } else if (progress - begin >= partAnimatorPercent) {
            //已經執行結束
            return 1;
        } else {
            //正在執行
            return (progress - begin) / partAnimatorPercent;
        }

    }

    /**
     * 根據切換進度進行重繪
     * @param progress
     */
    public void setProgress (float progress){
        this.progress = progress;
        invalidate();
    }

    //滾動模式
    public enum RollMode {
        Roll2D,             //2D 滾動
        Whole3D,            //3D 整體滾動
        SepartConbine,      //尾部逐漸分離再合併
        Jalousie,           //百葉窗
        RollInTurn;         //各模組依次滾動
    }
}

attrs.xml

<?xml version="1.0" encoding="UTF-8"?>
<resources>
    <declare-styleable name="Camera3DView_Param">
        <!--翻轉模式-->
        <attr name="camera_roll_mode"/>
        <!--翻轉方向-->
        <attr name="camera_roll_direction"/>
        <!--分成小模組的個數-->
        <attr name="partNumber" format="integer"/>
        <!--每個小模組啟動的動畫佔總動畫比-->
        <attr name="part_animator_percent" format="float"/>
    </declare-styleable>

    <attr name="camera_roll_mode">
        <enum name="Roll2D" value="0"/>
        <enum name="Whole3D" value="1"/>
        <enum name="SepartConbine" value="2"/>
        <enum name="Jalousie" value="3"/>
        <enum name="RollInTurn" value="4"/>
    </attr>

    <attr name="camera_roll_direction">
        <enum name="vertical" value="1"/>
        <enum 
            
           

相關推薦

3D 翻轉效果

一、ViewPager 的 3D 效果 1.ViewPager 先直接上個簡單的 ViewPager 的 demo。 activity_main.xml <RelativeLayout xmlns:android="http://s

SCPPO:從導資料看如何將一天過出多天的效果

【前言】     最近在微信朋友圈中看到一篇不錯的文章,無巧不成書,剛剛又在專案中做了一次導資料的任務,在做完後感覺收穫許多,於是總結一下,發現得出的結論結論可以用這篇文章竟標題來代替! 【有趣探索旅

【轉】JMeter學習HTTP屬性管理器HTTP Cookie Manager、HTTP Request Defaults

agen 讀取 expired fault 範圍 運行時 ear 定制 只有一個 Test Plan的配置元件中有一些和HTTP屬性相關的元件:HTTP Cache Manager、HTTP Authorization Manager、HTTP Cookie Manager

2016集訓測試賽小結

時間 第一題 是我 很多 分析 題目 ... 人的 簡單   這場測試賽有必要小結一下.   昨晚 1 點才睡, 今天狀態很差, 先睡了 1 個小時, 然後開始打比賽. 第一題不大會做, 寫了一個代碼後發現是錯的, 第二題看不懂題, 第三題簡單地分析了一下, 發現是一個樹形

C#編程----------接口

目的 count() 聲明變量 form [] 類的繼承 計算機 構造 執行 接口 如果一個類派生自一個接口,聲明這個類就會實現某些函數.並不是所有的面向對象的語言都支持接口. 例如,有一個接口:IDispoable,包含一個方法Dispose(),該方法又類實現,用於清理

企業分布式微服務雲SpringCloud SpringBoot mybatis 集成swagger2構建Restful API

sel ima eth syn conf ring 但是 batis 關註 一、引入依賴 <dependency> <groupId>io.springfox</groupId> <

Linux學習筆記shell特殊符號、 sort_wc_uniq、tee_tr_split

tee_tr_split命令一、特殊字符 1.‘* ‘任意個任意字符‘? ‘任意一個字符‘# ‘註釋字符‘ \ ‘脫義字符‘ | ‘管道符其中大部分之前都用過,對於脫義符‘\‘脫義符用法舉例如圖 2.幾個與管道有關的命令cut 分割,-d 分隔符 -f 指定段號 -c 指定第幾個字符 sort 排序,

Linux 學習總結 系統管理4

iptables filter nat netfilter iptables 應用實例 一 filter表案例 1 需求:只針對filter表,預設INPUT 鏈DROP ,其他兩個鏈ACCEPT,然後針對192.169.188.0/24開通22端口,對所有網段開放80端口,21端口。我們編寫sh

大數據筆記——Scala函數式編程

=== 情況 不能 nbsp 結構 map som class 編程 ===================== Scala函數式編程 ======================== 一、Scala中的函數 (*) 函數是Scala中的頭等公民,就和數字一樣,可以在變量中

C之數組

C語言 數組 在 C 語言中,我們不可避免的要接觸到數組。我們就來看看數組是什麽玩意,其實數組就是相同類型的變量的有序集合。下面這張圖更加形象的表示出數組的含義 數組在一片連續的內存空間中存儲元素,數組元素的個數是可以顯示或隱式指定的。比如: int a[5] = {1,

python2.7練習小例子

點贊 實例 net mil 感覺 format 經典題目 tty 遞推 25):題目:有5個人坐在一起,問第五個人多少歲?他說比第4個人大2歲。問第4個人歲數,他說比第3個人大2歲。問第三個人,又說比第2人大兩歲。問第2個人,說比第一個人大兩歲。最後問第一個人,他說

完善的復數類

C++ 復數類 賦值操作符 操作符重載 我們在之前已經是實現了復數類的相加操作,那麽我們今天就來完善下復數類。一個完整的復數類應該具備的操作有:運算(+, -, *, /);比較(==, !=);賦值(=);求模(modulus);利用的就是操作符重載來統一實現復數與實數的運算

類的加載

main函數 img emp ima info 技術 admin file bubuko jvm先找main函數,加載Demo34類 加載Car類,實例化對象進堆,成員變量都在堆中執行。類中的函數依舊在占中運行,遵循先入後出的原則,執行結束立即撤

Kafka:ZK+Kafka+Spark Streaming集群環境搭建Structured Streaming:同一個topic中包含一組數據的多個部分,按照key它們拼接為一條記錄以及遇到的問題

eas array 記錄 splay span ack timestamp b- each 需求: 目前kafka的topic上有一批數據,這些數據被分配到9個不同的partition中(就是發布時key:{m1,m2,m3,m4...m9},value:{records

MySQL數據庫8變量作用域

圖片 沒有 http global 而且 局部作用域 數據庫 過程 會話 變量作用域 變量作用域:變量能夠使用的區域範圍 局部作用域 使用declare關鍵字聲明(在結構體內:函數/存儲過程/觸發器),而且只能在結構體內部使用。 declare關鍵字聲明的變量沒有任何符號修

【HHHOJ】NOIP2018 模擬賽 解題報告

點此進入比賽 得分: \(100+100+20=220\)(\(T1\)打了兩個小時,以至於\(T3\)沒時間打了,無奈交暴力) 排名: \(Rank\ 8\) \(Rating\):\(+19\) \(T1\):【HHHOJ126】求和(點此看題面) 看到這道題,我不由得想到這道題目:【BZO

通證經濟大局觀:文藝復興與人文主義

十字軍東征 十字軍東征是教會主導的,但得到了上至國王,下至底層貴族的積極響應。這場戰爭的表面目的是為了宗教,為了奪回聖地。但根本的原因是整個西方的擴張衝動。 教會已經征服了西方,自然要聽從上帝的旨意,為更多的人帶去上帝的榮光,向東方傳教是理所當然之事。 大凡有野心的國王,都有擴張的慾望,就這一點來說

java基礎學習總結:logback詳解

為什麼使用logback logback大約有以下的一些優點: 核心重寫、測試充分、初始化記憶體載入更小,這一切讓logback效能和log4j相比有諸多倍的提升 logback非常自然地直接實現了slf4j,這個嚴格來說算不上優點,只是這樣,再理解slf4j的前提下會很容易理解

ElasticSearch最佳入門實踐mget批量查詢api

1、批量查詢的好處 就是一條一條的查詢,比如說要查詢100條資料,那麼就要傳送100次網路請求,這個開銷還是很大的 如果進行批量查詢的話,查詢100條資料,就只要傳送1次網路請求,網路請求的效能開銷縮減100倍 2、mget的語法 可以說mget是很重要

大資料:Sqoop的介紹和安裝

一、Sqoop簡介        Sqoop是一種旨在有效的Hadoop和關係型資料庫等結構化資料儲存之間傳輸大量資料的工具。Sqoop的原理就是將匯入或者匯出命令翻譯成mapreduce程式來實現。在翻譯出的mapreduce中主要就是對inputfor