您好,登錄后才能下訂單哦!
最近研究了一下如何在Android上實現CoverFlow效果的控件,其實早在2010年,就有Neil Davies開發并開源出了這個控件,Neil大神的這篇博客地址。首先是閱讀源碼,弄明白核心思路后,自己重新寫了一遍這個控件,并加入了詳盡的注釋以便日后查閱;而后在使用過程中,發現了有兩點可以改進:
(1)初始圖片位于中間,左邊空了一半空間,比較難看,可以改為重復滾動地展示;
(2)由于圖片一開始就需要加載出來,所以對內存開銷較大,很容易OOM,需要對圖片的內存空間進行壓縮。
這個自定義控件包括4個部分,用于創建及提供圖片對象的ImageAdapter,計算圖片旋轉角度等的自定義控件GalleryFlow,壓縮采樣率解析Bitmap的工具類BitmapScaleDownUtil,以及承載自定義控件的Gallery3DActivity。
首先是ImageAdapter,代碼如下:
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.Mode; 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; /** * @author pengyiming * @date 2013-9-30 * @function GalleryFlow適配器 */ 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 */ 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(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 */ }
其次是GalleryFlow,代碼如下:
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 pengyiming * @date 2013-9-30 * @function 自定義控件 */ 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 */ }
Bitmap解析用具BitmapScaleDownUtil,代碼如下:
package pym.test.gallery3d.util; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.view.Display; /** * @author pengyiming * @date 2013-9-30 * @function Bitmap縮放處理工具類 */ 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 */ }
測試控件的Gallery3DActivity,代碼如下:
package pym.test.gallery3d.main; import pym.test.gallery3d.R; import pym.test.gallery3d.util.BitmapScaleDownUtil; import pym.test.gallery3d.widget.GalleryFlow; import pym.test.gallery3d.widget.ImageAdapter; import android.app.Activity; import android.content.Context; import android.os.Bundle; /** * @author pengyiming * @date 2013-9-30 */ public class Gallery3DActivity 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 */ }
see效果圖~~~
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。