最近研究了一下如何在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,代码如下:

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

Android改进版CoverFlow效果控件的更多相关文章

  1. Android Material适配 为控件设置指定背景色和点击波纹效果

    Android Material适配 为控件设置指定背景色和点击波纹效果,有需要的朋友可以参考下. 大部分时候,我们都需要为控件设置指定背景色和点击效果 4.x以下可以使用selector,5.0以上 ...

  2. Android开发:文本控件详解——TextView(二)文字跑马灯效果实现

    一.需要使用的属性: 1.android:ellipsize 作用:若文字过长,控制该控件如何显示. 对于同样的文字“Android开发:文本控件详解——TextView(二)文字跑马灯效果实现”,不 ...

  3. android中的EditView控件

    android中的EditView控件 EditText继承关系:View-->TextView-->EditText ,EditText是可编辑文本框 1.EditText默认情况下,光 ...

  4. Android中通过WebView控件实现与JavaScript方法相互调用的地图应用

    在Android中通过WebView控件,可以实现要加载的页面与Android方法相互调用,我们要实现WebView中的addJavascriptInterface方法,这样html才能调用andro ...

  5. Android 性能优化——之控件的优化

    Android 性能优化——之控件的优化 前面讲了图像的优化,接下来分享一下控件的性能优化,这里主要是面向自定义View的优化. 1.首先先说一下我们在自定义View中可能会犯的3个错误: 1)Use ...

  6. Android 5.0新控件——FloatingActionButton(悬浮按钮)

    Android 5.0新控件--FloatingActionButton(悬浮按钮) FloatingActionButton是5.0以后的新控件,一个悬浮按钮,之所以叫做悬浮按钮,主要是因为自带阴影 ...

  7. Android开发:文本控件详解——TextView(一)基本属性

    一.简单实例: 新建的Android项目初始自带的Hello World!其实就是一个TextView. 在activity_main.xml中可以新建TextView,从左侧组件里拖拽到右侧预览界面 ...

  8. Android高级_视频播放控件

    一.Android系统自带VideoView控件 1. 创建步骤: (1)自带视频文件放入res/raw文件夹下: (2)声明初始化VideoView控件: (3)创建视频文件Uri路径,Uri调用p ...

  9. Android仿iPhone 滚轮控件 实现

    Android_开发 实用滚轮效果选择数字http://blog.csdn.net/zhangtengyuan23/article/details/8653771 Android仿iPhone滚轮控件 ...

随机推荐

  1. python自定义mininet拓扑

    python自定义mininet拓扑 前言 闲来无聊,想到很早之前都是用GUI来自定义拓扑,这次用python来自定义一下(以前留下的苦果) 转自Mininet 自定义网络拓扑 过程相对简单 实现过程 ...

  2. 团队作业8——测试与发布(Beta阶段)目录

    团队作业8——测试与发布(Beta阶段) http://www.cnblogs.com/zy-96/p/8053097.html 团队作业8——测试与发布(Beta阶段)之展示博客 http://ww ...

  3. C#ToString() 格式化数值

    格式字符串采用以下形式:Axx,其中 A 为格式说明符,指定格式化类型,xx 为精度说明符,控制格式化输出的有效位数或小数位数. 格式说明符 说明 示例 输出 C 货币 2.5.ToString(&q ...

  4. 2D开机动画

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name ...

  5. Alpha冲刺——day4

    Alpha冲刺--day4 作业链接 Alpha冲刺随笔集 github地址 团队成员 031602636 许舒玲(队长) 031602237 吴杰婷 031602220 雷博浩 031602634 ...

  6. Alpha 冲刺九

    团队成员 051601135 岳冠宇 051604103 陈思孝 031602629 刘意晗 031602248 郑智文 031602234 王淇 会议照片 项目燃尽图 项目进展 完善各自部分 项目描 ...

  7. PAT 甲级 1117 Eddington Number

    https://pintia.cn/problem-sets/994805342720868352/problems/994805354762715136 British astronomer Edd ...

  8. MySQL中EXPLAIN解释命令 查看索引是否生效

    explain显示了mysql如何使用索引来处理select语句以及连接表.可以帮助选择更好的索引和写出更优化的查询语句. 使用方法,在select语句前加上explain就可以了: 如: expla ...

  9. ORM的详解

    有很多小伙伴都不太理解ORM是什么,其实不用想象的那么复杂.我们先根据3W1H去理解. who:首先ORM可以立即为(Object/Relation Mapping): 对象/关系映射 what:其次 ...

  10. 一条sql语句搞定基于mysql的sql执行顺序的基本理解

    对数据库基本操作是每个程序员基本功,如何理解并快速记住sql执行的顺序呢,其实一条复杂的sql就能搞定: SELECT DISTINCT <select_list> FROM <le ...