• 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效果图~~~

     

  • 相关阅读:
    面试90%都会翻车的高并发分布式事务,我劝你好好啃透!
    JVM最多支持多少个线程?你知道吗?
    利用注解 + 反射消除重复代码(Java项目)
    ASP.NET HTTP模拟提交通用类 GET POST
    UPW学习资料整理 .NET C# 转
    前端引擎初步设计稿 -通过配置生成动态页面 ,LandaSugar平台 .NET-C#-MVC
    分享一个ASP.NET 文件压缩解压类 C#
    验证码的种类与实现 C#封装类
    ASP.NET MVC 使用 IOC框架 AutoFac 自动释放数据库资源
    ASP.NET MVC权限验证 封装类
  • 原文地址:https://www.cnblogs.com/zealotrouge/p/3380682.html
Copyright © 2020-2023  润新知