今天開發一個滑動切換的3d動畫效果,就在gitHub上邊找了代碼,而後把這些代碼放到新建的項目中,實現了循環切換的效果,具體代碼以下:android
package pym.test.gallery3d.util;git
import android.content.res.Resources;canvas
import android.graphics.Bitmap;數組
import android.graphics.BitmapFactory;app
import android.view.Display;ide
/**函數
* Bitmap縮放處理工具類工具
* @author bamboopost
*動畫
*/
public class BitmapScaleDownUtil {
/* 數據段begin */
private final String TAG = "BitmapScaleDownUtil";
/* 數據段end */
/* 函數段begin */
/**
* @function 獲取屏幕大小
* @param display
* @return 屏幕寬高
*/
public static int[] getScreenDimension(Display display)
{
int[] dimension = new int[2];
dimension[0] = display.getWidth();
dimension[1] = display.getHeight();
return dimension;
}
/**
* @function 以取樣方式加載Bitmap
* @param res
* @param resId
* @param reqWidth
* @param reqHeight
* @return 取樣後的Bitmap
*/
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight)
{
// step1,將inJustDecodeBounds置爲true,以解析Bitmap真實尺寸
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// step2,計算Bitmap取樣比例
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// step3,將inJustDecodeBounds置爲false,以取樣比列解析Bitmap
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
/**
* @function 計算Bitmap取樣比例
* @param options
* @param reqWidth
* @param reqHeight
* @return 取樣比例
*/
private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight)
{
// 默認取樣比例爲1:1
int inSampleSize = 1;
// Bitmap原始尺寸
final int width = options.outWidth;
final int height = options.outHeight;
// 取最大取樣比例
if (height > reqHeight || width > reqWidth)
{
final int widthRatio = Math.round((float) width / (float) reqWidth);
final int heightRatio = Math.round((float) height / (float) reqHeight);
// 取樣比例爲X:1,其中X>=1
inSampleSize = Math.max(widthRatio, heightRatio);
}
return inSampleSize;
}
/* 函數段end */
}
package pym.test.gallery3d.widget;
import android.content.Context;
import android.graphics.Camera;
import android.graphics.Matrix;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.Transformation;
import android.widget.Gallery;
/***
* 自定義控件
* @author bamboo
*
*/
public class GalleryFlow extends Gallery {
/* 數據段begin */
private final String TAG = "GalleryFlow";
// 邊緣圖片最大旋轉角度
private final float MAX_ROTATION_ANGLE = 75;
// 中心圖片最大前置距離
private final float MAX_TRANSLATE_DISTANCE = -100;
// GalleryFlow中心X座標
private int mGalleryFlowCenterX;
// 3D變換Camera
private Camera mCamera = new Camera();
/* 數據段end */
/* 函數段begin */
public GalleryFlow(Context context, AttributeSet attrs)
{
super(context, attrs);
// 開啓,在滑動過程當中,回調getChildStaticTransformation()
this.setStaticTransformationsEnabled(true);
}
/**
* @function 獲取GalleryFlow中心X座標
* @return
*/
private int getCenterXOfCoverflow()
{
return (getWidth() - getPaddingLeft() - getPaddingRight()) / 2 + getPaddingLeft();
}
/**
* @function 獲取GalleryFlow子view的中心X座標
* @param childView
* @return
*/
private int getCenterXOfView(View childView)
{
return childView.getLeft() + childView.getWidth() / 2;
}
/**
* @note step1 系統調用measure()方法時,回調此方法;代表此時系統正在計算view的大小
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mGalleryFlowCenterX = getCenterXOfCoverflow();
Log.d(TAG, "onMeasure, mGalleryFlowCenterX = " + mGalleryFlowCenterX);
}
/**
* @note step2 系統調用layout()方法時,回調此方法;代表此時系統正在給child view分配空間
* @note 一定在onMeasure()以後回調,但與onSizeChanged()前後順序不必定
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
super.onLayout(changed, l, t, r, b);
mGalleryFlowCenterX = getCenterXOfCoverflow();
Log.d(TAG, "onLayout, mGalleryFlowCenterX = " + mGalleryFlowCenterX);
}
/**
* @note step2 系統調用measure()方法後,當須要繪製此view時,回調此方法;代表此時系統已計算完view的大小
* @note 一定在onMeasure()以後回調,但與onSizeChanged()前後順序不必定
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
super.onSizeChanged(w, h, oldw, oldh);
mGalleryFlowCenterX = getCenterXOfCoverflow();
Log.d(TAG, "onSizeChanged, mGalleryFlowCenterX = " + mGalleryFlowCenterX);
}
@Override
protected boolean getChildStaticTransformation(View childView, Transformation t)
{
// 計算旋轉角度
float rotationAngle = calculateRotationAngle(childView);
// 計算前置距離
float translateDistance = calculateTranslateDistance(childView);
// 開始3D變換
transformChildView(childView, t, rotationAngle, translateDistance);
return true;
}
/**
* @function 計算GalleryFlow子view的旋轉角度
* @note1 位於Gallery中心的圖片不旋轉
* @note2 位於Gallery中心兩側的圖片按照離中心點的距離旋轉
* @param childView
* @return
*/
private float calculateRotationAngle(View childView)
{
final int childCenterX = getCenterXOfView(childView);
float rotationAngle = 0;
rotationAngle = (mGalleryFlowCenterX - childCenterX) / (float) mGalleryFlowCenterX * MAX_ROTATION_ANGLE;
if (rotationAngle > MAX_ROTATION_ANGLE)
{
rotationAngle = MAX_ROTATION_ANGLE;
}
else if (rotationAngle < -MAX_ROTATION_ANGLE)
{
rotationAngle = -MAX_ROTATION_ANGLE;
}
return rotationAngle;
}
/**
* @function 計算GalleryFlow子view的前置距離
* @note1 位於Gallery中心的圖片前置
* @note2 位於Gallery中心兩側的圖片不前置
* @param childView
* @return
*/
private float calculateTranslateDistance(View childView)
{
final int childCenterX = getCenterXOfView(childView);
float translateDistance = 0;
if (mGalleryFlowCenterX == childCenterX)
{
translateDistance = MAX_TRANSLATE_DISTANCE;
}
return translateDistance;
}
/**
* @function 開始變換GalleryFlow子view
* @param childView
* @param t
* @param rotationAngle
* @param translateDistance
*/
private void transformChildView(View childView, Transformation t, float rotationAngle, float translateDistance)
{
t.clear();
t.setTransformationType(Transformation.TYPE_MATRIX);
final Matrix imageMatrix = t.getMatrix();
final int imageWidth = childView.getWidth();
final int imageHeight = childView.getHeight();
mCamera.save();
/* rotateY */
// 在Y軸上旋轉,位於中心的圖片不旋轉,中心兩側的圖片豎向向裏或向外翻轉。
mCamera.rotateY(rotationAngle);
/* rotateY */
/* translateZ */
// 在Z軸上前置,位於中心的圖片會有放大的效果
mCamera.translate(0, 0, translateDistance);
/* translateZ */
// 開始變換(個人理解是:移動Camera,在2D視圖上產生3D效果)
mCamera.getMatrix(imageMatrix);
imageMatrix.preTranslate(-imageWidth / 2, -imageHeight / 2);
imageMatrix.postTranslate(imageWidth / 2, imageHeight / 2);
mCamera.restore();
}
/* 函數段end */
}
package pym.test.gallery3d.widget;
import pym.test.gallery3d.util.BitmapScaleDownUtil;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Shader.TileMode;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Gallery;
import android.widget.ImageView;
public class ImageAdapter extends BaseAdapter {
/* 數據段begin */
private final String TAG = "ImageAdapter";
private Context mContext;
// 圖片數組
private int[] mImageIds;
// 圖片控件數組
private ImageView[] mImages;
// 圖片控件LayoutParams
private GalleryFlow.LayoutParams mImagesLayoutParams;
/* 數據段end */
/* 函數段begin */
public ImageAdapter(Context context, int[] imageIds) {
mContext = context;
mImageIds = imageIds;
mImages = new ImageView[mImageIds.length];
mImagesLayoutParams = new GalleryFlow.LayoutParams(
Gallery.LayoutParams.WRAP_CONTENT,
Gallery.LayoutParams.WRAP_CONTENT);
}
/**
* @function 根據指定寬高建立待繪製的Bitmap,並繪製到ImageView控件上
* @param imageWidth
* @param imageHeight
* @return void
*/
@SuppressWarnings("deprecation")
public void createImages(int imageWidth, int imageHeight) {
// 原圖與倒影的間距5px
final int gapHeight = 5;
int index = 0;
for (int imageId : mImageIds) {
/* step1 採樣方式解析原圖並生成倒影 */
// 解析原圖,生成原圖Bitmap對象
// Bitmap originalImage =
// BitmapFactory.decodeResource(mContext.getResources(), imageId);
Bitmap originalImage = BitmapScaleDownUtil
.decodeSampledBitmapFromResource(mContext.getResources(),
imageId, imageWidth, imageHeight);
int width = originalImage.getWidth();
int height = originalImage.getHeight();
// Y軸方向反向,實質就是X軸翻轉
Matrix matrix = new Matrix();
matrix.setScale(1, -1);
// 且僅取原圖下半部分建立倒影Bitmap對象
Bitmap reflectionImage = Bitmap.createBitmap(originalImage, 0,
height / 2, width, height / 2, matrix, false);
/* step2 繪製 */
// 建立一個可包含原圖+間距+倒影的新圖Bitmap對象
Bitmap bitmapWithReflection = Bitmap.createBitmap(width, (height
+ gapHeight + height / 2), Config.ARGB_8888);
// 在新圖Bitmap對象之上建立畫布
Canvas canvas = new Canvas(bitmapWithReflection);
// 抗鋸齒效果
canvas.setDrawFilter(new PaintFlagsDrawFilter(0,
Paint.ANTI_ALIAS_FLAG));
// 繪製原圖
canvas.drawBitmap(originalImage, 0, 0, null);
// 繪製間距
Paint gapPaint = new Paint();
gapPaint.setColor(0xFFCCCCCC);
canvas.drawRect(0, height, width, height + gapHeight, gapPaint);
// 繪製倒影
canvas.drawBitmap(reflectionImage, 0, height + gapHeight, null);
/* step3 渲染 */
// 建立一個線性漸變的渲染器用於渲染倒影
Paint paint = new Paint();
LinearGradient shader = new LinearGradient(0, height, 0, (height
+ gapHeight + height / 2), 0x70ffffff, 0x00ffffff,
TileMode.CLAMP);
// 設置畫筆渲染器
paint.setShader(shader);
// 設置圖片混合模式
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
// 渲染倒影+間距
canvas.drawRect(0, height, width,
(height + gapHeight + height / 2), paint);
/* step4 在ImageView控件上繪製 */
ImageView imageView = new ImageView(mContext);
imageView.setImageBitmap(bitmapWithReflection);
imageView.setLayoutParams(mImagesLayoutParams);
// 打log
imageView.setTag(index);
/* step5 釋放heap */
originalImage.recycle();
reflectionImage.recycle();
// bitmapWithReflection.recycle();
mImages[index++] = imageView;
}
}
@Override
public int getCount() {
return Integer.MAX_VALUE;
}
@Override
public Object getItem(int position) {
return mImages[position];
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return mImages[position % mImages.length];
}
/* 函數段end */
}
package pym.test.gallery3d.widget;
import pym.test.gallery3d.util.BitmapScaleDownUtil;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
public class MainActivity extends Activity {/* 數據段begin */
private final String TAG = "Gallery3DActivity";
private Context mContext;
// 圖片縮放倍率(相對屏幕尺寸的縮小倍率)
public static final int SCALE_FACTOR = 8;
// 圖片間距(控制各圖片之間的距離)
private final int GALLERY_SPACING = -10;
// 控件
private GalleryFlow mGalleryFlow;
/* 數據段end */
/* 函數段begin */
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = getApplicationContext();
setContentView(R.layout.gallery_3d_activity_layout);
initGallery();
}
private void initGallery() {
// 圖片ID
int[] images = { R.drawable.picture_1, R.drawable.picture_2,
R.drawable.picture_3, R.drawable.picture_4,
R.drawable.picture_5, R.drawable.picture_6,
R.drawable.picture_7 };
ImageAdapter adapter = new ImageAdapter(mContext, images);
// 計算圖片的寬高
int[] dimension = BitmapScaleDownUtil
.getScreenDimension(getWindowManager().getDefaultDisplay());
int imageWidth = dimension[0] / SCALE_FACTOR;
int imageHeight = dimension[1] / SCALE_FACTOR;
// 初始化圖片
adapter.createImages(imageWidth, imageHeight);
// 設置Adapter,顯示位置位於控件中間,這樣使得左右都可"無限"滑動
mGalleryFlow = (GalleryFlow) findViewById(R.id.gallery_flow);
mGalleryFlow.setSpacing(GALLERY_SPACING);
mGalleryFlow.setAdapter(adapter);
mGalleryFlow.setSelection(Integer.MAX_VALUE / 2);
}
/* 函數段end */
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<pym.test.gallery3d.widget.GalleryFlow
android:id="@+id/gallery_flow"
android:layout_width="match_parent"
android:layout_marginTop="20dp"
android:layout_height="150dp" />
</LinearLayout>