Android改進版CoverFlow效果控件


  最近研究了一下如何在Android上實現CoverFlow效果的控件,其實早在2010年,就有Neil Davies開發並開源出了這個控件,Neil大神的這篇博客地址http://www.inter-fuser.com/2010/02/android-coverflow-widget-v2.html。首先是閱讀源碼,弄明白核心思路后,自己重新寫了一遍這個控件,並加入了詳盡的注釋以便日后查閱;而后在使用過程中,發現了有兩點可以改進:(1)初始圖片位於中間,左邊空了一半空間,比較難看,可以改為重復滾動地展示、(2)由於圖片一開始就需要加載出來,所以對內存開銷較大,很容易OOM,需要對圖片的內存空間進行壓縮。

  這個自定義控件包括4個部分,用於創建及提供圖片對象的ImageAdapter,計算圖片旋轉角度等的自定義控件GalleryFlow,壓縮采樣率解析Bitmap的工具類BitmapScaleDownUtil,以及承載自定義控件的Gallery3DActivity。

  首先是ImageAdapter,代碼如下:

  1 package pym.test.gallery3d.widget;
  2 
  3 import pym.test.gallery3d.util.BitmapScaleDownUtil;
  4 import android.content.Context;
  5 import android.graphics.Bitmap;
  6 import android.graphics.Bitmap.Config;
  7 import android.graphics.Canvas;
  8 import android.graphics.LinearGradient;
  9 import android.graphics.Matrix;
 10 import android.graphics.Paint;
 11 import android.graphics.PaintFlagsDrawFilter;
 12 import android.graphics.PorterDuff.Mode;
 13 import android.graphics.PorterDuffXfermode;
 14 import android.graphics.Shader.TileMode;
 15 import android.view.View;
 16 import android.view.ViewGroup;
 17 import android.widget.BaseAdapter;
 18 import android.widget.Gallery;
 19 import android.widget.ImageView;
 20 
 21 /**
 22  * @author pengyiming
 23  * @date 2013-9-30
 24  * @function GalleryFlow適配器
 25  */
 26 public class ImageAdapter extends BaseAdapter
 27 {
 28     /* 數據段begin */
 29     private final String TAG = "ImageAdapter";
 30     private Context mContext;
 31     
 32     //圖片數組
 33     private int[] mImageIds ;
 34     //圖片控件數組
 35     private ImageView[] mImages;
 36     //圖片控件LayoutParams
 37     private GalleryFlow.LayoutParams mImagesLayoutParams;
 38     /* 數據段end */
 39 
 40     /* 函數段begin */
 41     public ImageAdapter(Context context, int[] imageIds)
 42     {
 43         mContext = context;
 44         mImageIds = imageIds;
 45         mImages = new ImageView[mImageIds.length];
 46         mImagesLayoutParams = new GalleryFlow.LayoutParams(Gallery.LayoutParams.WRAP_CONTENT, Gallery.LayoutParams.WRAP_CONTENT);
 47     }
 48     
 49     /**
 50      * @function 根據指定寬高創建待繪制的Bitmap,並繪制到ImageView控件上
 51      * @param imageWidth
 52      * @param imageHeight
 53      * @return void
 54      */
 55     public void createImages(int imageWidth, int imageHeight)
 56     {
 57         // 原圖與倒影的間距5px
 58         final int gapHeight = 5;
 59         
 60         int index = 0;
 61         for (int imageId : mImageIds)
 62         {
 63             /* step1 采樣方式解析原圖並生成倒影 */
 64             // 解析原圖,生成原圖Bitmap對象
 65 //            Bitmap originalImage = BitmapFactory.decodeResource(mContext.getResources(), imageId);
 66             Bitmap originalImage = BitmapScaleDownUtil.decodeSampledBitmapFromResource(mContext.getResources(), imageId, imageWidth, imageHeight);
 67             int width = originalImage.getWidth();
 68             int height = originalImage.getHeight();
 69             
 70             // Y軸方向反向,實質就是X軸翻轉
 71             Matrix matrix = new Matrix();
 72             matrix.setScale(1, -1);
 73             // 且僅取原圖下半部分創建倒影Bitmap對象
 74             Bitmap reflectionImage = Bitmap.createBitmap(originalImage, 0, height / 2, width, height / 2, matrix, false);
 75             
 76             /* step2 繪制 */
 77             // 創建一個可包含原圖+間距+倒影的新圖Bitmap對象
 78             Bitmap bitmapWithReflection = Bitmap.createBitmap(width, (height + gapHeight + height / 2), Config.ARGB_8888);
 79             // 在新圖Bitmap對象之上創建畫布
 80             Canvas canvas = new Canvas(bitmapWithReflection);
 81             // 抗鋸齒效果
 82             canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG));
 83             // 繪制原圖
 84             canvas.drawBitmap(originalImage, 0, 0, null);
 85             // 繪制間距
 86             Paint gapPaint = new Paint();
 87             gapPaint.setColor(0xFFCCCCCC);
 88             canvas.drawRect(0, height, width, height + gapHeight, gapPaint);
 89             // 繪制倒影
 90             canvas.drawBitmap(reflectionImage, 0, height + gapHeight, null);
 91             
 92             /* step3 渲染 */
 93             // 創建一個線性漸變的渲染器用於渲染倒影
 94             Paint paint = new Paint();
 95             LinearGradient shader = new LinearGradient(0, height, 0, (height + gapHeight + height / 2), 0x70ffffff, 0x00ffffff, TileMode.CLAMP);
 96             // 設置畫筆渲染器
 97             paint.setShader(shader);
 98             // 設置圖片混合模式
 99             paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
100             // 渲染倒影+間距
101             canvas.drawRect(0, height, width, (height + gapHeight + height / 2), paint);
102             
103             /* step4 在ImageView控件上繪制 */
104             ImageView imageView = new ImageView(mContext);
105             imageView.setImageBitmap(bitmapWithReflection);
106             imageView.setLayoutParams(mImagesLayoutParams);
107             // 打log
108             imageView.setTag(index);
109             
110             /* step5 釋放heap */
111             originalImage.recycle();
112             reflectionImage.recycle();
113 //          bitmapWithReflection.recycle();
114             
115             mImages[index++] = imageView;
116         }
117     }
118 
119     @Override
120     public int getCount()
121     {
122         return Integer.MAX_VALUE;
123     }
124     
125     @Override
126     public Object getItem(int position)
127     {
128         return mImages[position];
129     }
130     
131     @Override
132     public long getItemId(int position)
133     {
134         return position;
135     }
136     
137     @Override
138     public View getView(int position, View convertView, ViewGroup parent)
139     {
140         return mImages[position % mImages.length];
141     }
142     /* 函數段end */
143 }

  其次是GalleryFlow,代碼如下:

  1 package pym.test.gallery3d.widget;
  2 
  3 import android.content.Context;
  4 import android.graphics.Camera;
  5 import android.graphics.Matrix;
  6 import android.util.AttributeSet;
  7 import android.util.Log;
  8 import android.view.View;
  9 import android.view.animation.Transformation;
 10 import android.widget.Gallery;
 11 
 12 /**
 13  * @author pengyiming
 14  * @date 2013-9-30
 15  * @function 自定義控件
 16  */
 17 public class GalleryFlow extends Gallery
 18 {
 19     /* 數據段begin */
 20     private final String TAG = "GalleryFlow";
 21     
 22     // 邊緣圖片最大旋轉角度
 23     private final float MAX_ROTATION_ANGLE = 75;
 24     // 中心圖片最大前置距離
 25     private final float MAX_TRANSLATE_DISTANCE = -100;
 26     // GalleryFlow中心X坐標
 27     private int mGalleryFlowCenterX;
 28     // 3D變換Camera
 29     private Camera mCamera = new Camera();
 30     /* 數據段end */
 31 
 32     /* 函數段begin */
 33     public GalleryFlow(Context context, AttributeSet attrs)
 34     {
 35         super(context, attrs);
 36         
 37         // 開啟,在滑動過程中,回調getChildStaticTransformation()
 38         this.setStaticTransformationsEnabled(true);
 39     }
 40     
 41     /**
 42      * @function 獲取GalleryFlow中心X坐標
 43      * @return
 44      */
 45     private int getCenterXOfCoverflow()
 46     {
 47         return (getWidth() - getPaddingLeft() - getPaddingRight()) / 2 + getPaddingLeft();
 48     }
 49     
 50     /**
 51      * @function 獲取GalleryFlow子view的中心X坐標
 52      * @param childView
 53      * @return
 54      */
 55     private int getCenterXOfView(View childView)
 56     {
 57         return childView.getLeft() + childView.getWidth() / 2;
 58     }
 59     
 60     /**
 61      * @note step1 系統調用measure()方法時,回調此方法;表明此時系統正在計算view的大小
 62      */
 63     @Override
 64     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
 65     {
 66         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 67         
 68         mGalleryFlowCenterX = getCenterXOfCoverflow();
 69         Log.d(TAG, "onMeasure, mGalleryFlowCenterX = " + mGalleryFlowCenterX);
 70     }
 71     
 72     /**
 73      * @note step2 系統調用layout()方法時,回調此方法;表明此時系統正在給child view分配空間
 74      * @note 必定在onMeasure()之后回調,但與onSizeChanged()先后順序不一定
 75      */
 76     @Override
 77     protected void onLayout(boolean changed, int l, int t, int r, int b)
 78     {
 79         super.onLayout(changed, l, t, r, b);
 80         
 81         mGalleryFlowCenterX = getCenterXOfCoverflow();
 82         Log.d(TAG, "onLayout, mGalleryFlowCenterX = " + mGalleryFlowCenterX);
 83     }
 84     
 85     /**
 86      * @note step2 系統調用measure()方法后,當需要繪制此view時,回調此方法;表明此時系統已計算完view的大小
 87      * @note 必定在onMeasure()之后回調,但與onSizeChanged()先后順序不一定
 88      */
 89     @Override
 90     protected void onSizeChanged(int w, int h, int oldw, int oldh)
 91     {
 92         super.onSizeChanged(w, h, oldw, oldh);
 93         
 94         mGalleryFlowCenterX = getCenterXOfCoverflow();
 95         Log.d(TAG, "onSizeChanged, mGalleryFlowCenterX = " + mGalleryFlowCenterX);
 96     }
 97     
 98     @Override
 99     protected boolean getChildStaticTransformation(View childView, Transformation t)
100     {
101         // 計算旋轉角度
102         float rotationAngle = calculateRotationAngle(childView);
103         
104         // 計算前置距離
105         float translateDistance = calculateTranslateDistance(childView);
106         
107         // 開始3D變換
108         transformChildView(childView, t, rotationAngle, translateDistance);
109         
110         return true;
111     }
112     
113     /**
114      * @function 計算GalleryFlow子view的旋轉角度
115      * @note1 位於Gallery中心的圖片不旋轉
116      * @note2 位於Gallery中心兩側的圖片按照離中心點的距離旋轉
117      * @param childView
118      * @return
119      */
120     private float calculateRotationAngle(View childView)
121     {
122         final int childCenterX = getCenterXOfView(childView);
123         float rotationAngle = 0;
124         
125         rotationAngle = (mGalleryFlowCenterX - childCenterX) / (float) mGalleryFlowCenterX * MAX_ROTATION_ANGLE;
126         
127         if (rotationAngle > MAX_ROTATION_ANGLE)
128         {
129             rotationAngle = MAX_ROTATION_ANGLE;
130         }
131         else if (rotationAngle < -MAX_ROTATION_ANGLE)
132         {
133             rotationAngle = -MAX_ROTATION_ANGLE;
134         }
135         
136         return rotationAngle;
137     }
138     
139     /**
140      * @function 計算GalleryFlow子view的前置距離
141      * @note1 位於Gallery中心的圖片前置
142      * @note2 位於Gallery中心兩側的圖片不前置
143      * @param childView
144      * @return
145      */
146     private float calculateTranslateDistance(View childView)
147     {
148         final int childCenterX = getCenterXOfView(childView);
149         float translateDistance = 0;
150         
151         if (mGalleryFlowCenterX == childCenterX)
152         {
153             translateDistance = MAX_TRANSLATE_DISTANCE;
154         }
155         
156         return translateDistance;
157     }
158     
159     /**
160      * @function 開始變換GalleryFlow子view
161      * @param childView
162      * @param t
163      * @param rotationAngle
164      * @param translateDistance
165      */
166     private void transformChildView(View childView, Transformation t, float rotationAngle, float translateDistance)
167     {
168         t.clear();
169         t.setTransformationType(Transformation.TYPE_MATRIX);
170         
171         final Matrix imageMatrix = t.getMatrix();
172         final int imageWidth = childView.getWidth();
173         final int imageHeight = childView.getHeight();
174         
175         mCamera.save();
176         
177         /* rotateY */
178         // 在Y軸上旋轉,位於中心的圖片不旋轉,中心兩側的圖片豎向向里或向外翻轉。
179         mCamera.rotateY(rotationAngle);
180         /* rotateY */
181         
182         /* translateZ */
183         // 在Z軸上前置,位於中心的圖片會有放大的效果
184         mCamera.translate(0, 0, translateDistance);
185         /* translateZ */
186         
187         // 開始變換(我的理解是:移動Camera,在2D視圖上產生3D效果)
188         mCamera.getMatrix(imageMatrix);
189         imageMatrix.preTranslate(-imageWidth / 2, -imageHeight / 2);
190         imageMatrix.postTranslate(imageWidth / 2, imageHeight / 2);
191         
192         mCamera.restore();
193     }
194     /* 函數段end */
195 }

  Bitmap解析用具BitmapScaleDownUtil,代碼如下:

 1 package pym.test.gallery3d.util;
 2 
 3 import android.content.res.Resources;
 4 import android.graphics.Bitmap;
 5 import android.graphics.BitmapFactory;
 6 import android.view.Display;
 7 
 8 /**
 9  * @author pengyiming
10  * @date 2013-9-30
11  * @function Bitmap縮放處理工具類
12  */
13 public class BitmapScaleDownUtil
14 {
15     /* 數據段begin */
16     private final String TAG = "BitmapScaleDownUtil";
17     /* 數據段end */
18 
19     /* 函數段begin */
20     /**
21      * @function 獲取屏幕大小
22      * @param display
23      * @return 屏幕寬高
24      */
25     public static int[] getScreenDimension(Display display)
26     {
27         int[] dimension = new int[2];
28         dimension[0] = display.getWidth();
29         dimension[1] = display.getHeight();
30         
31         return dimension;
32     }
33     
34     /**
35      * @function 以取樣方式加載Bitmap 
36      * @param res
37      * @param resId
38      * @param reqWidth
39      * @param reqHeight
40      * @return 取樣后的Bitmap
41      */
42     public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight)
43     {
44         // step1,將inJustDecodeBounds置為true,以解析Bitmap真實尺寸
45         final BitmapFactory.Options options = new BitmapFactory.Options();
46         options.inJustDecodeBounds = true;
47         BitmapFactory.decodeResource(res, resId, options);
48 
49         // step2,計算Bitmap取樣比例
50         options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
51 
52         // step3,將inJustDecodeBounds置為false,以取樣比列解析Bitmap
53         options.inJustDecodeBounds = false;
54         return BitmapFactory.decodeResource(res, resId, options);
55     }
56 
57     /**
58      * @function 計算Bitmap取樣比例
59      * @param options
60      * @param reqWidth
61      * @param reqHeight
62      * @return 取樣比例
63      */
64     private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight)
65     {
66         // 默認取樣比例為1:1
67         int inSampleSize = 1;
68 
69         // Bitmap原始尺寸
70         final int width = options.outWidth;
71         final int height = options.outHeight;
72 
73         // 取最大取樣比例
74         if (height > reqHeight || width > reqWidth)
75         {
76             final int widthRatio = Math.round((float) width / (float) reqWidth);
77             final int heightRatio = Math.round((float) height / (float) reqHeight);
78 
79             // 取樣比例為X:1,其中X>=1
80             inSampleSize = Math.max(widthRatio, heightRatio);
81         }
82 
83         return inSampleSize;
84     }
85     /* 函數段end */
86 }

  測試控件的Gallery3DActivity,代碼如下:

 1 package pym.test.gallery3d.main;
 2 
 3 import pym.test.gallery3d.R;
 4 import pym.test.gallery3d.util.BitmapScaleDownUtil;
 5 import pym.test.gallery3d.widget.GalleryFlow;
 6 import pym.test.gallery3d.widget.ImageAdapter;
 7 import android.app.Activity;
 8 import android.content.Context;
 9 import android.os.Bundle;
10 
11 /**
12  * @author pengyiming
13  * @date 2013-9-30
14  */
15 public class Gallery3DActivity extends Activity
16 {
17     /* 數據段begin */
18     private final String TAG = "Gallery3DActivity";
19     private Context mContext;
20     
21     // 圖片縮放倍率(相對屏幕尺寸的縮小倍率)
22     public static final int SCALE_FACTOR = 8;
23     
24     // 圖片間距(控制各圖片之間的距離)
25     private final int GALLERY_SPACING = -10;
26     
27     // 控件
28     private GalleryFlow mGalleryFlow;
29     /* 數據段end */
30 
31     /* 函數段begin */
32     @Override
33     protected void onCreate(Bundle savedInstanceState)
34     {
35         super.onCreate(savedInstanceState);
36         mContext = getApplicationContext();
37         
38         setContentView(R.layout.gallery_3d_activity_layout);
39         initGallery();
40     }
41     
42     private void initGallery()
43     {
44         // 圖片ID
45         int[] images = {
46                 R.drawable.picture_1,
47                 R.drawable.picture_2,
48                 R.drawable.picture_3,
49                 R.drawable.picture_4,
50                 R.drawable.picture_5,
51                 R.drawable.picture_6,
52                 R.drawable.picture_7 };
53 
54         ImageAdapter adapter = new ImageAdapter(mContext, images);
55         // 計算圖片的寬高
56         int[] dimension = BitmapScaleDownUtil.getScreenDimension(getWindowManager().getDefaultDisplay());
57         int imageWidth = dimension[0] / SCALE_FACTOR;
58         int imageHeight = dimension[1] / SCALE_FACTOR;
59         // 初始化圖片
60         adapter.createImages(imageWidth, imageHeight);
61 
62         // 設置Adapter,顯示位置位於控件中間,這樣使得左右均可"無限"滑動
63         mGalleryFlow = (GalleryFlow) findViewById(R.id.gallery_flow);
64         mGalleryFlow.setSpacing(GALLERY_SPACING);
65         mGalleryFlow.setAdapter(adapter);
66         mGalleryFlow.setSelection(Integer.MAX_VALUE / 2);
67     }
68     /* 函數段end */
69 }

  see效果圖~~~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM