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

}


 

 

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