CoverFlow效果

来源:互联网 发布:微信跳转淘宝app 编辑:程序博客网 时间:2024/04/30 09:49

源码介绍皆转载:

 

  最近研究了一下如何在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             // 打log108             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     @Override120     public int getCount()121     {122         return Integer.MAX_VALUE;123     }124     125     @Override126     public Object getItem(int position)127     {128         return mImages[position];129     }130     131     @Override132     public long getItemId(int position)133     {134         return position;135     }136     137     @Override138     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 childView118      * @return119      */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 childView144      * @return145      */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子view161      * @param childView162      * @param t163      * @param rotationAngle164      * @param translateDistance165      */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 pengyiming10  * @date 2013-9-3011  * @function Bitmap缩放处理工具类12  */13 public class BitmapScaleDownUtil14 {15     /* 数据段begin */16     private final String TAG = "BitmapScaleDownUtil";17     /* 数据段end */18 19     /* 函数段begin */20     /**21      * @function 获取屏幕大小22      * @param display23      * @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 res37      * @param resId38      * @param reqWidth39      * @param reqHeight40      * @return 取样后的Bitmap41      */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,以取样比列解析Bitmap53         options.inJustDecodeBounds = false;54         return BitmapFactory.decodeResource(res, resId, options);55     }56 57     /**58      * @function 计算Bitmap取样比例59      * @param options60      * @param reqWidth61      * @param reqHeight62      * @return 取样比例63      */64     private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight)65     {66         // 默认取样比例为1:167         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>=180             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 pengyiming13  * @date 2013-9-3014  */15 public class Gallery3DActivity extends Activity16 {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     @Override33     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         // 图片ID45         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效果图~~~

 
0 0