服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Android - Android实现CoverFlow效果控件的实例代码

Android实现CoverFlow效果控件的实例代码

2022-02-19 16:48热气球 Android

这篇文章主要介绍了Android实现CoverFlow效果控件的实例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

最近研究了一下如何在Android上实现CoverFlow效果的控件,其实早在2010年,就有Neil Davies开发并开源出了这个控件,Neil大神的这篇博客地址。首先是阅读源码,弄明白核心思路后,自己重新写了一遍这个控件,并加入了详尽的注释以便日后查阅;而后在使用过程中,发现了有两点可以改进:

(1)初始图片位于中间,左边空了一半空间,比较难看,可以改为重复滚动地展示;

(2)由于图片一开始就需要加载出来,所以对内存开销较大,很容易OOM,需要对图片的内存空间进行压缩。

这个自定义控件包括4个部分,用于创建及提供图片对象的ImageAdapter,计算图片旋转角度等的自定义控件GalleryFlow,压缩采样率解析Bitmap的工具类BitmapScaleDownUtil,以及承载自定义控件的Gallery3DActivity。

首先是ImageAdapter,代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
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,代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
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,代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
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,代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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效果控件的实例代码

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。

原文链接:http://www.cnblogs.com/zealotrouge/p/3380682.html

延伸 · 阅读

精彩推荐
  • AndroidAndroid程序设计之AIDL实例详解

    Android程序设计之AIDL实例详解

    这篇文章主要介绍了Android程序设计的AIDL,以一个完整实例的形式较为详细的讲述了AIDL的原理及实现方法,需要的朋友可以参考下...

    Android开发网4642021-03-09
  • AndroidAndroid实现Service获取当前位置(GPS+基站)的方法

    Android实现Service获取当前位置(GPS+基站)的方法

    这篇文章主要介绍了Android实现Service获取当前位置(GPS+基站)的方法,较为详细的分析了Service基于GPS位置的技巧,具有一定参考借鉴价值,需要的朋友可以参考下...

    Ruthless8342021-03-31
  • AndroidAndroid实现固定屏幕显示的方法

    Android实现固定屏幕显示的方法

    这篇文章主要介绍了Android实现固定屏幕显示的方法,实例分析了Android屏幕固定显示所涉及的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下...

    鉴客6192021-03-27
  • AndroidAndroid中AsyncTask详细介绍

    Android中AsyncTask详细介绍

    这篇文章主要介绍了Android中AsyncTask详细介绍,AsyncTask是一个很常用的API,尤其异步处理数据并将数据应用到视图的操作场合,需要的朋友可以参考下...

    Android开发网7452021-03-11
  • AndroidAndroid界面效果UI开发资料汇总(附资料包)

    Android界面效果UI开发资料汇总(附资料包)

    android ui界面设计,友好的界面会提高用户体验度;同时也增强了android ui界面设计的难度,本文提供了一些常用开发资料(有下载哦)感兴趣的朋友可以了解下...

    Android开发网4672021-01-03
  • AndroidAndroid CardView+ViewPager实现ViewPager翻页动画的方法

    Android CardView+ViewPager实现ViewPager翻页动画的方法

    本篇文章主要介绍了Android CardView+ViewPager实现ViewPager翻页动画的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    Abby代黎明9602022-03-02
  • Android汇总Android视频录制中常见问题

    汇总Android视频录制中常见问题

    这篇文章主要汇总了Android视频录制中常见问题,帮助大家更好地解决Android视频录制中常见的问题,需要的朋友可以参考下...

    yh_thu5192021-04-28
  • AndroidAndroid编程解析XML方法详解(SAX,DOM与PULL)

    Android编程解析XML方法详解(SAX,DOM与PULL)

    这篇文章主要介绍了Android编程解析XML方法,结合实例形式详细分析了Android解析XML文件的常用方法与相关实现技巧,需要的朋友可以参考下...

    liuhe68810052021-05-03