Camera實現3D效果

android.graphics.Camera:3D開發
官方介紹

A camera instance can be used to compute 3D transformations and generate a matrix that can be applied, for instance, on aCanvas。

一個照相機實例可以被用於計算3D變換,生成一個可以被使用的Matrix矩陣,一個實例,用在畫布上。

Camera內部機制實際上還是opengl,不過大大簡化了使用。


縱向 3D 整體滾動效果

核心代碼

private void drawWhole3D(Canvas canvas) {
        canvas.save();
        Bitmap currWholeBitmap = bitmapList.get(currIndex);
        Bitmap nextWholeBitmap = bitmapList.get(nextIndex);
        if(direction == 1){//縱向
            camera.save();
            camera.rotateX(-rotateDegree);
            camera.getMatrix(matrix);
            camera.restore();
            matrix.preTranslate(-viewHeight/2,0);
            //下面的view繞着自己的top旋轉,轉完之後顯示要往下平移(rotateDegree/90)*viewWidth;
            matrix.postTranslate(viewWidth/2,axisY);
            canvas.drawBitmap(currWholeBitmap,matrix,paint);

            camera.save();
            //第二張圖片旋轉
            camera.rotateX(90-rotateDegree);
            camera.getMatrix(matrix);
            camera.restore();
            matrix.preTranslate(-viewWidth/2,-viewHeight);
            //下面的view繞着自己的Bottom旋轉,轉完之後顯示要往上平移(rotateDegree/90)*viewWidth;
            matrix.postTranslate(viewWidth/2,axisY);
            canvas.drawBitmap(nextWholeBitmap,matrix,paint);

        }else {//橫向

        }
        canvas.restore();
    }

分析動畫

橫向3D整體翻滾

核心代碼

private void drawWhole3D(Canvas canvas) {
        canvas.save();
        Bitmap currWholeBitmap = bitmapList.get(currIndex);
        Bitmap nextWholeBitmap = bitmapList.get(nextIndex);
        if(direction == 1){//縱向
            camera.save();
            camera.rotateX(-rotateDegree);
            camera.getMatrix(matrix);
            camera.restore();
            matrix.preTranslate(-viewWidth/2,0);
            //下面的view繞着自己的top旋轉,轉完之後顯示要往下平移(rotateDegree/90)*viewWidth;
            matrix.postTranslate(viewWidth/2,axisY);
            canvas.drawBitmap(currWholeBitmap,matrix,paint);

            camera.save();
            //第二張圖片旋轉
            camera.rotateX(90-rotateDegree);
            camera.getMatrix(matrix);
            camera.restore();
            matrix.preTranslate(-viewWidth/2,-viewHeight);
            //下面的view繞着自己的Bottom旋轉,轉完之後顯示要往上平移(rotateDegree/90)*viewWidth;
            matrix.postTranslate(viewWidth/2,axisY);
            canvas.drawBitmap(nextWholeBitmap,matrix,paint);

        }else {//橫向
            camera.save();
            camera.rotateY(rotateDegree);
            camera.getMatrix(matrix);
            camera.restore();
            matrix.preTranslate(0,-viewHeight/2);
            matrix.postTranslate(axisX,viewHeight/2);
            canvas.drawBitmap(currWholeBitmap,matrix,paint);

            camera.save();
            //第二張圖片旋轉
            camera.rotateY(-(90-rotateDegree));
            camera.getMatrix(matrix);
            camera.restore();
            matrix.preTranslate(-viewWidth,-viewHeight/2);
            matrix.postTranslate(axisX,viewHeight/2);
            canvas.drawBitmap(nextWholeBitmap,matrix,paint);
        }
        canvas.restore();
    }

動畫分析 和縱向一樣 只不過橫向 是繞Y軸旋轉

各模塊依次滾動

核心原理和上面一樣 這裏對圖片進行了分割處理 後面附上完整的代碼

百葉窗效果

尾部逐漸分離再合併效果

整體翻滾2D效果

下面附上源碼

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffffff"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <FrameLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/shap_bg"
        android:layout_marginRight="10dp"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="10dp">
        <com.twy.sqlitehelper.view.Camera3DView
            android:id="@+id/cv_1"
            android:layout_width="200dp"
            android:layout_height="200dp"/>
    </FrameLayout>

    <SeekBar
        android:id="@+id/atdv_seek_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp" />

</LinearLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private Bitmap bitmap1;
    private Bitmap bitmap2;
    private Camera3DView camera3DView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        camera3DView = findViewById(R.id.cv_1);
        SeekBar seekBar = findViewById(R.id.atdv_seek_bar);
        seekBar.setMax(100);//0 - 100
        seekBar.setOnSeekBarChangeListener(onSeekBarChangeListener);
        bitmap1 = BitmapFactory.decodeResource(getResources(), R.mipmap.aa);
        bitmap2 = BitmapFactory.decodeResource(getResources(), R.mipmap.bb);
        List<Bitmap> list = new ArrayList<>();
        list.add(bitmap1);
        list.add(bitmap2);

        //整體翻滾2D
        /*camera3DView.setRollDirection(Camera3DView.Orientation.Horizontal);
        camera3DView.setRollMode(Camera3DView.RollMode.Roll2D);*/
        //整體翻滾3D
        /*camera3DView.setRollDirection(2);
        camera3DView.setRollMode(Camera3DView.RollMode.Whole3D);*/


        //各模塊依次滾動
        /*camera3DView.setRollDirection(Camera3DView.Orientation.Horizontal);
        camera3DView.setRollMode(Camera3DView.RollMode.RollInTurn);
        camera3DView.setPartNumber(10);//設置分割數量*/

        //百葉窗
        /*camera3DView.setRollDirection(Camera3DView.Orientation.Horizontal);
        camera3DView.setRollMode(Camera3DView.RollMode.Jalousie);
        camera3DView.setPartNumber(10);*/

        //尾部逐漸分離再合併效果
        camera3DView.setRollDirection(Camera3DView.Orientation.Horizontal);
        camera3DView.setRollMode(Camera3DView.RollMode.SepartConbine);
        camera3DView.setPartNumber(10);


        camera3DView.addAllBitmap(list);
    }
    SeekBar.OnSeekBarChangeListener onSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() {
        @Override
        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
            camera3DView.setRotateDegree(progress);
        }

        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
        }

        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
        }
    };
}

Camera3DView.java

package com.twy.sqlitehelper.view;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Camera;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.view.View;

import java.util.ArrayList;
import java.util.List;

/**
 * Author by twy, Email [email protected], Date on 2019/5/23.
 * PS: Not easy to write code, please indicate.
 */
public class Camera3DView extends View {
    private Context context;
    private Camera camera;
    private Matrix matrix;
    private List<Bitmap> bitmapList;
    private Bitmap[][] bitmaps;
    private int partNumber = 5;
    private Orientation direction = Orientation.Horizontal;

    private float averageWidth,averageHeight;
    private float viewWidth,viewHeight;
    private int preIndex = 0, currIndex = 0, nextIndex = 0;
    private float rotateDegree = 0;

    //X方向旋轉軸   Y方向旋轉軸
    private float axisX = 0, axisY = 0;

    //滾動模式
    private RollMode rollMode = RollMode.Whole3D;
    private Paint paint;


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

    public enum Orientation{
        //水平方向  垂直方向
        Horizontal,Vertical
    }

    public Camera3DView(Context context) {
        super(context);
        init(context);
    }

    public Camera3DView(Context context,AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public Camera3DView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public Camera3DView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context);
    }

    private void init(Context context) {
        this.context = context;
        camera = new Camera();
        matrix = new Matrix();
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        bitmapList = new ArrayList<>();
    }

    public void addAllBitmap(List<Bitmap> bitmaps){
        bitmapList.addAll(bitmaps);
        invalidate();
    }

    public void setPartNumber(int partNumber){
        this.partNumber = partNumber;
    }

    /**
     *
     * @param rotateDegree 0至100
     */
    public void setRotateDegree(float rotateDegree){
        switch (rollMode){
            case Whole3D:
            case Jalousie:
            case SepartConbine:
            case Roll2D:
                this.rotateDegree = rotateDegree/100*(rollMode==RollMode.Jalousie?180:90);
                if(Orientation.Vertical==direction){
                    //Y方向旋轉軸
                    //百葉窗旋轉180度,其他旋轉90度
                    axisY = this.rotateDegree/(float)(rollMode==RollMode.Jalousie?180:90)*viewHeight;

                }else {
                    //X方向旋轉軸
                    axisX = this.rotateDegree/(float)(rollMode==RollMode.Jalousie?180:90)*viewWidth;
                }
                break;
            case RollInTurn:
                this.rotateDegree = rotateDegree/100*(30*(partNumber-1)+(rollMode==RollMode.Jalousie?180:90));
                break;
        }

        invalidate();
    }

    public void setRollDirection(Orientation direction) {
        this.direction = direction;
    }
    public void setRollMode(RollMode rollMode){
        this.rollMode = rollMode;
    }

    //對圖片進行切割
    private void initBitmap() {
        initIndex();
        switch (rollMode){
            case RollInTurn:
            case Jalousie:
            case SepartConbine:
                bitmaps = new Bitmap[bitmapList.size()][partNumber];
                averageWidth = viewWidth/partNumber;
                averageHeight = viewHeight/partNumber;
                Rect rect;
                Bitmap partBitmap;
                for(int i = 0;i<bitmapList.size();i++){
                    for(int j = 0;j<partNumber;j++){
                        if(Orientation.Vertical==direction){//縱向切割
                            rect = new Rect((int)(j*averageWidth),0,(int)((j+1)*averageWidth),(int) viewHeight);
                            partBitmap = getPartBitmap(bitmapList.get(i),rect.left,0,rect.width(),rect.height());
                        }else {//橫向切割
                            rect = new Rect(0,(int)(j*averageHeight),(int) viewWidth,(int)((j+1)*averageHeight));
                            partBitmap = getPartBitmap(bitmapList.get(i),0,rect.top,rect.width(),rect.height());
                        }
                        bitmaps[i][j] = partBitmap;
                    }
                }
                break;
        }
    }

    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;
        }
    }

    private Bitmap getPartBitmap(Bitmap bitmap,int left,int top,int width,int height){
        return Bitmap.createBitmap(bitmap,left,top,width,height);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        viewWidth = getMeasuredWidth();
        viewHeight = getMeasuredHeight();
        if (viewWidth != 0 && viewHeight != 0) {
            //縮放處理bitmap
            for (int i = 0; i < bitmapList.size(); i++) {
                bitmapList.set(i, scaleBitmap(bitmapList.get(i)));
            }
            initBitmap();
        }
    }

    //根據給定的寬和高進行拉伸
    private Bitmap scaleBitmap(Bitmap origin) {
        if (origin == null) {
            return null;
        }
        int height = origin.getHeight();
        int width = origin.getWidth();
        float scaleWidth = viewWidth / width;
        float scaleHeight = 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);
        switch (rollMode){
            case Roll2D:
                drawWhole3D(canvas,true);
                break;
            case Whole3D:
                drawWhole3D(canvas,false);
                break;
            case RollInTurn:
                drawRollInTurn(canvas);
                break;
            case Jalousie:
                drawJalousie(canvas);
                break;
            case SepartConbine:
                drawSepartConbine(canvas);
                break;
        }
    }

    /**
     * 整體翻滾
     * @param canvas
     * @param draw2D true 2D效果 false 3D效果
     */
    private void drawWhole3D(Canvas canvas,boolean draw2D) {
        canvas.save();
        Bitmap currWholeBitmap = bitmapList.get(currIndex);
        Bitmap nextWholeBitmap = bitmapList.get(nextIndex);
        if(Orientation.Vertical==direction){//縱向
            camera.save();
            if(draw2D){
                camera.rotateX(0);
            }else {
                camera.rotateX(-rotateDegree);
            }
            camera.getMatrix(matrix);
            camera.restore();
            matrix.preTranslate(-viewWidth/2,0);
            //下面的view繞着自己的top旋轉,轉完之後顯示要往下平移(rotateDegree/90)*viewWidth;
            matrix.postTranslate(viewWidth/2,axisY);
            canvas.drawBitmap(currWholeBitmap,matrix,paint);

            camera.save();
            //第二張圖片旋轉
            if(draw2D){
                camera.rotateX(0);
            }else {
                camera.rotateX(90-rotateDegree);
            }
            camera.getMatrix(matrix);
            camera.restore();
            matrix.preTranslate(-viewWidth/2,-viewHeight);
            //下面的view繞着自己的Bottom旋轉,轉完之後顯示要往上平移(rotateDegree/90)*viewWidth;
            matrix.postTranslate(viewWidth/2,axisY);
            canvas.drawBitmap(nextWholeBitmap,matrix,paint);

        }else {//橫向
            camera.save();
            if(draw2D){
                camera.rotateX(0);
            }else {
                camera.rotateY(rotateDegree);
            }
            camera.getMatrix(matrix);
            camera.restore();
            matrix.preTranslate(0,-viewHeight/2);
            //下面的view繞着自己的top旋轉,轉完之後顯示要往下平移(rotateDegree/90)*viewWidth;
            matrix.postTranslate(axisX,viewHeight/2);
            canvas.drawBitmap(currWholeBitmap,matrix,paint);

            camera.save();
            //第二張圖片旋轉
            if(draw2D){
                camera.rotateX(0);
            }else {
                camera.rotateY(-(90-rotateDegree));
            }
            camera.getMatrix(matrix);
            camera.restore();
            matrix.preTranslate(-viewWidth,-viewHeight/2);
            //下面的view繞着自己的Bottom旋轉,轉完之後顯示要往上平移(rotateDegree/90)*viewWidth;
            matrix.postTranslate(axisX,viewHeight/2);
            canvas.drawBitmap(nextWholeBitmap,matrix,paint);
        }
        canvas.restore();
    }

    /**
     * 依次翻滾
     *
     * @param canvas
     */
    private void drawRollInTurn(Canvas canvas) {
        for (int i = 0; i < partNumber; i++) {
            Bitmap currBitmap = bitmaps[currIndex][i];
            Bitmap nextBitmap = bitmaps[nextIndex][i];

            float tDegree = rotateDegree - i * 30;
            if (tDegree < 0)
                tDegree = 0;
            if (tDegree > 90)
                tDegree = 90;


            canvas.save();
            if (Orientation.Vertical==direction) {
                float tAxisY = tDegree / 90f * viewHeight;
                if (tAxisY > viewHeight)
                    tAxisY = viewHeight;
                if (tAxisY < 0)
                    tAxisY = 0;

                camera.save();
                camera.rotateX(-tDegree);
                camera.getMatrix(matrix);
                camera.restore();

                matrix.preTranslate(-currBitmap.getWidth(), 0);
                matrix.postTranslate(currBitmap.getWidth() + i * averageWidth, tAxisY);
                canvas.drawBitmap(currBitmap, matrix, paint);

                camera.save();
                camera.rotateX((90 - tDegree));
                camera.getMatrix(matrix);
                camera.restore();

                matrix.preTranslate(-nextBitmap.getWidth(), -nextBitmap.getHeight());
                matrix.postTranslate(nextBitmap.getWidth() + i * averageWidth, tAxisY);
                canvas.drawBitmap(nextBitmap, matrix, paint);

            } else {
                float tAxisX = tDegree / 90f * viewWidth;
                if (tAxisX > viewWidth)
                    tAxisX = viewWidth;
                if (tAxisX < 0)
                    tAxisX = 0;
                camera.save();
                camera.rotateY(tDegree);
                camera.getMatrix(matrix);
                camera.restore();

                matrix.preTranslate(0, -currBitmap.getHeight() / 2);
                matrix.postTranslate(tAxisX, currBitmap.getHeight() / 2 + i * averageHeight);
                canvas.drawBitmap(currBitmap, matrix, paint);

                //
                camera.save();
                camera.rotateY(tDegree - 90);
                camera.getMatrix(matrix);
                camera.restore();

                matrix.preTranslate(-nextBitmap.getWidth(), -nextBitmap.getHeight() / 2);
                matrix.postTranslate(tAxisX, nextBitmap.getHeight() / 2 + i * averageHeight);
                canvas.drawBitmap(nextBitmap, matrix, paint);
            }
            canvas.restore();
        }
    }

    /**
     * 百葉窗翻頁
     *
     * @param canvas
     */
    private void drawJalousie(Canvas canvas) {
        for (int i = 0; i < partNumber; i++) {
            Bitmap currBitmap = bitmaps[currIndex][i];
            Bitmap nextBitmap = bitmaps[nextIndex][i];

            canvas.save();
            //注意 百葉窗的翻轉方向和其他模式是相反的  橫向的時候縱翻  縱向的時候橫翻
            if (Orientation.Vertical==direction) {

                if (rotateDegree < 90) {
                    camera.save();
                    camera.rotateY(rotateDegree);
                    camera.getMatrix(matrix);
                    camera.restore();

                    matrix.preTranslate(-currBitmap.getWidth() / 2, -currBitmap.getHeight() / 2);
                    matrix.postTranslate(currBitmap.getWidth() / 2+ i * averageWidth, currBitmap.getHeight() / 2 );
                    canvas.drawBitmap(currBitmap, matrix, paint);
                } else {
                    camera.save();
                    camera.rotateY(180 - rotateDegree);
                    camera.getMatrix(matrix);
                    camera.restore();
                    //前乘矩陣---先平移矩陣,再進行變換
                    matrix.preTranslate(-nextBitmap.getWidth() / 2, -nextBitmap.getHeight() / 2);
                    //變換完之後,再平移
                    matrix.postTranslate(nextBitmap.getWidth() / 2+ i * averageWidth, nextBitmap.getHeight() / 2);
                    canvas.drawBitmap(nextBitmap, matrix, paint);
                }


            } else {
                if (rotateDegree < 90) {
                    camera.save();
                    camera.rotateX(rotateDegree);
                    camera.getMatrix(matrix);
                    camera.restore();

                    matrix.preTranslate(-currBitmap.getWidth() / 2, -currBitmap.getHeight() / 2);
                    matrix.postTranslate(currBitmap.getWidth() / 2, currBitmap.getHeight() / 2+i * averageHeight);
                    canvas.drawBitmap(currBitmap, matrix, paint);
                } else {
                    camera.save();
                    camera.rotateX(180 - rotateDegree);
                    camera.getMatrix(matrix);
                    camera.restore();

                    matrix.preTranslate(-nextBitmap.getWidth() / 2, -nextBitmap.getHeight() / 2);
                    matrix.postTranslate(nextBitmap.getWidth() / 2 , nextBitmap.getHeight() / 2+ i * averageHeight);
                    canvas.drawBitmap(nextBitmap, matrix, paint);
                }

            }
            canvas.restore();
        }
    }

    /**
     * 縱向  頭部接合  尾部分離效果
     * degree 0->90 往下翻滾 或者 往右翻滾   90->0往上翻滾 或者往翻滾
     *
     * @param canvas
     */
    private void drawSepartConbine(Canvas canvas) {
        for (int i = 0; i < partNumber; i++) {
            Bitmap currBitmap = bitmaps[currIndex][i];
            Bitmap nextBitmap = bitmaps[nextIndex][i];

            canvas.save();
            if (Orientation.Vertical==direction) {

                camera.save();
                camera.rotateX(-rotateDegree);
                camera.getMatrix(matrix);
                camera.restore();

                matrix.preTranslate(-currBitmap.getWidth() / 2, 0);
                matrix.postTranslate(currBitmap.getWidth() / 2 + i * averageWidth, axisY);
                canvas.drawBitmap(currBitmap, matrix, paint);

                camera.save();
                camera.rotateX((90 - rotateDegree));
                camera.getMatrix(matrix);
                camera.restore();

                matrix.preTranslate(-nextBitmap.getWidth() / 2, -nextBitmap.getHeight());
                matrix.postTranslate(nextBitmap.getWidth() / 2 + i * averageWidth, axisY);
                canvas.drawBitmap(nextBitmap, matrix, paint);

            } else {
                camera.save();
                camera.rotateY(rotateDegree);
                camera.getMatrix(matrix);
                camera.restore();

                matrix.preTranslate(0, -currBitmap.getHeight() / 2);
                matrix.postTranslate(axisX, currBitmap.getHeight() / 2 + i * averageHeight);
                canvas.drawBitmap(currBitmap, matrix, paint);

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

                matrix.preTranslate(-nextBitmap.getWidth(), -nextBitmap.getHeight() / 2);
                matrix.postTranslate(axisX, nextBitmap.getHeight() / 2 + i * averageHeight);
                canvas.drawBitmap(nextBitmap, matrix, paint);
            }
            canvas.restore();
        }
    }

}


 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章