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