Android高级图片滚动控件实现3D版图片轮播器

时间:2021-09-12 08:22:37

大家好,好久不见了,最近由于工作特别繁忙,已经有一个多月的时间没写博客了,我也是深感惭愧。那么今天的这篇既然是阔别了一个多月的文章,当然要带来更加给力点的内容了,那么话不多说,赶快进入到今天的正题吧。

说到图片轮播器,很多的Android应用中都会带有这个功能,比如说网易新闻、淘宝等。最新我们公司的一款应用也加入了这个功能,并且在图片轮播的基础上还增加了三维立体的效果,但比较遗憾的是,整体效果并不理想,用户体验性比较糟糕。因此,我就花了点时间去编写了一个效果更好的3D图片轮播器,自我感觉还是比较满意的,这里果断写一篇博客来分享给大家。

首先来介绍一下实现原理吧,传统的图片轮播器在一个界面上只会显示一张图片,要用手指进行左右滑动才能看到其它的图片。这里我们将思维发散一下,允许在一个界面上同时显示三张图片,再通过Camera的方式对左右的两张图进行3D旋转,这样就能制作出一种立体的图片轮播器了,原理示意图如下所示:

Android高级图片滚动控件实现3D版图片轮播器

对图片进行立体操作还是要使用到Camera技术,如果你对这个技术还不太熟悉,可以到网上搜一些相关资料,或者参考我前面的一篇文章:Android实现中轴旋转特效 Android制作别样的图片浏览器 。

那么我们现在就开始动手吧,首先新建一个Android项目,起名叫做ImageSwitchViewTest。

然后新建一个Image3DView继承自ImageView,它会继承ImageView的所有属性,并且加入3D旋转的功能,代码如下所示:

?
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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
public class Image3DView extends ImageView {
 /**
 * 旋转角度的基准值
 */
 private static final float BASE_DEGREE = 50f;
 /**
 * 旋转深度的基准值
 */
 private static final float BASE_DEEP = 150f;
 private Camera mCamera;
 private Matrix mMaxtrix;
 private Bitmap mBitmap;
 /**
 * 当前图片对应的下标
 */
 private int mIndex;
 /**
 * 在前图片在X轴方向滚动的距离
 */
 private int mScrollX;
 /**
 * Image3DSwitchView控件的宽度
 */
 private int mLayoutWidth;
 /**
 * 当前图片的宽度
 */
 private int mWidth;
 /**
 * 当前旋转的角度
 */
 private float mRotateDegree;
 /**
 * 旋转的中心点
 */
 private float mDx;
 /**
 * 旋转的深度
 */
 private float mDeep;
 
 public Image3DView(Context context, AttributeSet attrs) {
 super(context, attrs);
 mCamera = new Camera();
 mMaxtrix = new Matrix();
 }
 /**
 * 初始化Image3DView所需要的信息,包括图片宽度,截取背景图等。
 */
 public void initImageViewBitmap() {
 if (mBitmap == null) {
 setDrawingCacheEnabled(true);
 buildDrawingCache();
 mBitmap = getDrawingCache();
 }
 mLayoutWidth = Image3DSwitchView.mWidth;
 mWidth = getWidth() + Image3DSwitchView.IMAGE_PADDING * 2;
 }
 /**
 * 设置旋转角度。
 *
 * @param index
 * 当前图片的下标
 * @param scrollX
 * 当前图片在X轴方向滚动的距离
 */
 public void setRotateData(int index, int scrollX) {
 mIndex = index;
 mScrollX = scrollX;
 }
 /**
 * 回收当前的Bitmap对象,以释放内存。
 */
 public void recycleBitmap() {
 if (mBitmap != null && !mBitmap.isRecycled()) {
 mBitmap.recycle();
 }
 }
 @Override
 public void setImageResource(int resId) {
 super.setImageResource(resId);
 mBitmap = null;
 initImageViewBitmap();
 }
 @Override
 public void setImageBitmap(Bitmap bm) {
 super.setImageBitmap(bm);
 mBitmap = null;
 initImageViewBitmap();
 }
 @Override
 public void setImageDrawable(Drawable drawable) {
 super.setImageDrawable(drawable);
 mBitmap = null;
 initImageViewBitmap();
 }
 @Override
 public void setImageURI(Uri uri) {
 super.setImageURI(uri);
 mBitmap = null;
 initImageViewBitmap();
 }
 @Override
 protected void onDraw(Canvas canvas) {
 if (mBitmap == null) {
 // 如果Bitmap对象还不存在,先使用父类的onDraw方法进行绘制
 super.onDraw(canvas);
 } else {
 if (isImageVisible()) {
 // 绘图时需要注意,只有当图片可见的时候才进行绘制,这样可以节省运算效率
 computeRotateData();
 mCamera.save();
 mCamera.translate(0.0f, 0.0f, mDeep);
 mCamera.rotateY(mRotateDegree);
 mCamera.getMatrix(mMaxtrix);
 mCamera.restore();
 mMaxtrix.preTranslate(-mDx, -getHeight() / 2);
 mMaxtrix.postTranslate(mDx, getHeight() / 2);
 canvas.drawBitmap(mBitmap, mMaxtrix, null);
 }
 }
 }
 /**
 * 在这里计算所有旋转所需要的数据。
 */
 private void computeRotateData() {
 float degreePerPix = BASE_DEGREE / mWidth;
 float deepPerPix = BASE_DEEP / ((mLayoutWidth - mWidth) / 2);
 switch (mIndex) {
 case 0:
 mDx = mWidth;
 mRotateDegree = 360f - (2 * mWidth + mScrollX) * degreePerPix;
 if (mScrollX < -mWidth) {
 mDeep = 0;
 } else {
 mDeep = (mWidth + mScrollX) * deepPerPix;
 }
 break;
 case 1:
 if (mScrollX > 0) {
 mDx = mWidth;
 mRotateDegree = (360f - BASE_DEGREE) - mScrollX * degreePerPix;
 mDeep = mScrollX * deepPerPix;
 } else {
 if (mScrollX < -mWidth) {
 mDx = -Image3DSwitchView.IMAGE_PADDING * 2;
 mRotateDegree = (-mScrollX - mWidth) * degreePerPix;
 } else {
 mDx = mWidth;
 mRotateDegree = 360f - (mWidth + mScrollX) * degreePerPix;
 }
 mDeep = 0;
 }
 break;
 case 2:
 if (mScrollX > 0) {
 mDx = mWidth;
 mRotateDegree = 360f - mScrollX * degreePerPix;
 mDeep = 0;
 if (mScrollX > mWidth) {
 mDeep = (mScrollX - mWidth) * deepPerPix;
 }
 } else {
 mDx = -Image3DSwitchView.IMAGE_PADDING * 2;
 mRotateDegree = -mScrollX * degreePerPix;
 mDeep = 0;
 if (mScrollX < -mWidth) {
 mDeep = -(mWidth + mScrollX) * deepPerPix;
 }
 }
 break;
 case 3:
 if (mScrollX < 0) {
 mDx = -Image3DSwitchView.IMAGE_PADDING * 2;
 mRotateDegree = BASE_DEGREE - mScrollX * degreePerPix;
 mDeep = -mScrollX * deepPerPix;
 } else {
 if (mScrollX > mWidth) {
 mDx = mWidth;
 mRotateDegree = 360f - (mScrollX - mWidth) * degreePerPix;
 } else {
 mDx = -Image3DSwitchView.IMAGE_PADDING * 2;
 mRotateDegree = BASE_DEGREE - mScrollX * degreePerPix;
 }
 mDeep = 0;
 }
 break;
 case 4:
 mDx = -Image3DSwitchView.IMAGE_PADDING * 2;
 mRotateDegree = (2 * mWidth - mScrollX) * degreePerPix;
 if (mScrollX > mWidth) {
 mDeep = 0;
 } else {
 mDeep = (mWidth - mScrollX) * deepPerPix;
 }
 break;
 }
 }
 /**
 * 判断当前图片是否可见。
 *
 * @return 当前图片可见返回true,不可见返回false。
 */
 private boolean isImageVisible() {
 boolean isVisible = false;
 switch (mIndex) {
 case 0:
 if (mScrollX < (mLayoutWidth - mWidth) / 2 - mWidth) {
 isVisible = true;
 } else {
 isVisible = false;
 }
 break;
 case 1:
 if (mScrollX > (mLayoutWidth - mWidth) / 2) {
 isVisible = false;
 } else {
 isVisible = true;
 }
 break;
 case 2:
 if (mScrollX > mLayoutWidth / 2 + mWidth / 2
 || mScrollX < -mLayoutWidth / 2 - mWidth / 2) {
 isVisible = false;
 } else {
 isVisible = true;
 }
 break;
 case 3:
 if (mScrollX < -(mLayoutWidth - mWidth) / 2) {
 isVisible = false;
 } else {
 isVisible = true;
 }
 break;
 case 4:
 if (mScrollX > mWidth - (mLayoutWidth - mWidth) / 2) {
 isVisible = true;
 } else {
 isVisible = false;
 }
 break;
 }
 return isVisible;
 }
}

这段代码比较长,也比较复杂的,我们慢慢来分析。在Image3DView的构造函数中初始化了一个Camera和Matrix对象,用于在后面对图片进行3D操作。然后在initImageViewBitmap()方法中初始化了一些必要的信息,比如对当前图片进行截图,以用于后续的立体操作,得到当前图片的宽度等。

然后还提供了一个setRotateData()方法,用于设置当前图片的下标和滚动距离,有了这两样数据就可以通过computeRotateData()方法来计算旋转角度的一些数据,以及通过isImageVisible()方法来判断出当前图片是否可见了,具体详细的算法逻辑你可以阅读代码来慢慢分析。

接下来当图片需要绘制到屏幕上的时候就会调用onDraw()方法,在onDraw()方法中会进行判断,如果当前图片可见就调用computeRotateData()方法来计算旋转时所需要的各种数据,之后再通过Camera和Matrix来执行旋转操作就可以了。

接着新建一个Image3DSwitchView继承自ViewGroup,代码如下所示:

 

?
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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
public class Image3DSwitchView extends ViewGroup {
 /**
 * 图片左右两边的空白间距
 */
 public static final int IMAGE_PADDING = 10;
 private static final int TOUCH_STATE_REST = 0;
 private static final int TOUCH_STATE_SCROLLING = 1;
 /**
 * 滚动到下一张图片的速度
 */
 private static final int SNAP_VELOCITY = 600;
 /**
 * 表示滚动到下一张图片这个动作
 */
 private static final int SCROLL_NEXT = 0;
 /**
 * 表示滚动到上一张图片这个动作
 */
 private static final int SCROLL_PREVIOUS = 1;
 /**
 * 表示滚动回原图片这个动作
 */
 private static final int SCROLL_BACK = 2;
 private static Handler handler = new Handler();
 /**
 * 控件宽度
 */
 public static int mWidth;
 private VelocityTracker mVelocityTracker;
 private Scroller mScroller;
 /**
 * 图片滚动监听器,当图片发生滚动时回调这个接口
 */
 private OnImageSwitchListener mListener;
 /**
 * 记录当前的触摸状态
 */
 private int mTouchState = TOUCH_STATE_REST;
 /**
 * 记录被判定为滚动运动的最小滚动值
 */
 private int mTouchSlop;
 /**
 * 记录控件高度
 */
 private int mHeight;
 /**
 * 记录每张图片的宽度
 */
 private int mImageWidth;
 /**
 * 记录图片的总数量
 */
 private int mCount;
 /**
 * 记录当前显示图片的坐标
 */
 private int mCurrentImage;
 /**
 * 记录上次触摸的横坐标值
 */
 private float mLastMotionX;
 /**
 * 是否强制重新布局
 */
 private boolean forceToRelayout;
 private int[] mItems;
 public Image3DSwitchView(Context context, AttributeSet attrs) {
 super(context, attrs);
 mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
 mScroller = new Scroller(context);
 }
 @Override
 protected void onLayout(boolean changed, int l, int t, int r, int b) {
 if (changed || forceToRelayout) {
 mCount = getChildCount();
 // 图片数量必须大于5,不然无法正常显示
 if (mCount < 5) {
 return;
 }
 mWidth = getMeasuredWidth();
 mHeight = getMeasuredHeight();
 // 每张图片的宽度设定为控件宽度的百分之六十
 mImageWidth = (int) (mWidth * 0.6);
 if (mCurrentImage >= 0 && mCurrentImage < mCount) {
 mScroller.abortAnimation();
 setScrollX(0);
 int left = -mImageWidth * 2 + (mWidth - mImageWidth) / 2;
 // 分别获取每个位置上应该显示的图片下标
 int[] items = { getIndexForItem(1), getIndexForItem(2),
 getIndexForItem(3), getIndexForItem(4),
 getIndexForItem(5) };
 mItems = items;
 // 通过循环为每张图片设定位置
 for (int i = 0; i < items.length; i++) {
 Image3DView childView = (Image3DView) getChildAt(items[i]);
 childView.layout(left + IMAGE_PADDING, 0, left
 + mImageWidth - IMAGE_PADDING, mHeight);
 childView.initImageViewBitmap();
 left = left + mImageWidth;
 }
 refreshImageShowing();
 }
 forceToRelayout = false;
 }
 }
 @Override
 public boolean onTouchEvent(MotionEvent event) {
 if (mScroller.isFinished()) {
 if (mVelocityTracker == null) {
 mVelocityTracker = VelocityTracker.obtain();
 }
 mVelocityTracker.addMovement(event);
 int action = event.getAction();
 float x = event.getX();
 switch (action) {
 case MotionEvent.ACTION_DOWN:
 // 记录按下时的横坐标
 mLastMotionX = x;
 break;
 case MotionEvent.ACTION_MOVE:
 int disX = (int) (mLastMotionX - x);
 mLastMotionX = x;
 scrollBy(disX, 0);
 // 当发生移动时刷新图片的显示状态
 refreshImageShowing();
 break;
 case MotionEvent.ACTION_UP:
 mVelocityTracker.computeCurrentVelocity(1000);
 int velocityX = (int) mVelocityTracker.getXVelocity();
 if (shouldScrollToNext(velocityX)) {
 // 滚动到下一张图
 scrollToNext();
 } else if (shouldScrollToPrevious(velocityX)) {
 // 滚动到上一张图
 scrollToPrevious();
 } else {
 // 滚动回当前图片
 scrollBack();
 }
 if (mVelocityTracker != null) {
 mVelocityTracker.recycle();
 mVelocityTracker = null;
 }
 break;
 }
 }
 return true;
 }
 /**
 * 根据当前的触摸状态来决定是否屏蔽子控件的交互能力。
 */
 @Override
 public boolean onInterceptTouchEvent(MotionEvent ev) {
 int action = ev.getAction();
 if ((action == MotionEvent.ACTION_MOVE)
 && (mTouchState != TOUCH_STATE_REST)) {
 return true;
 }
 float x = ev.getX();
 switch (action) {
 case MotionEvent.ACTION_DOWN:
 mLastMotionX = x;
 mTouchState = TOUCH_STATE_REST;
 break;
 case MotionEvent.ACTION_MOVE:
 int xDiff = (int) Math.abs(mLastMotionX - x);
 if (xDiff > mTouchSlop) {
 mTouchState = TOUCH_STATE_SCROLLING;
 }
 break;
 case MotionEvent.ACTION_UP:
 default:
 mTouchState = TOUCH_STATE_REST;
 break;
 }
 return mTouchState != TOUCH_STATE_REST;
 }
 @Override
 public void computeScroll() {
 if (mScroller.computeScrollOffset()) {
 scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
 refreshImageShowing();
 postInvalidate();
 }
 }
 /**
 * 设置图片滚动的监听器,每当有图片滚动时会回调此接口。
 *
 * @param listener
 * 图片滚动监听器
 */
 public void setOnImageSwitchListener(OnImageSwitchListener listener) {
 mListener = listener;
 }
 /**
 * 设置当前显示图片的下标,注意如果该值小于零或大于等于图片的总数量,图片则无法正常显示。
 *
 * @param currentImage
 * 图片的下标
 */
 public void setCurrentImage(int currentImage) {
 mCurrentImage = currentImage;
 requestLayout();
 }
 /**
 * 滚动到下一张图片。
 */
 public void scrollToNext() {
 if (mScroller.isFinished()) {
 int disX = mImageWidth - getScrollX();
 checkImageSwitchBorder(SCROLL_NEXT);
 if (mListener != null) {
 mListener.onImageSwitch(mCurrentImage);
 }
 beginScroll(getScrollX(), 0, disX, 0, SCROLL_NEXT);
 }
 }
 /**
 * 滚动到上一张图片。
 */
 public void scrollToPrevious() {
 if (mScroller.isFinished()) {
 int disX = -mImageWidth - getScrollX();
 checkImageSwitchBorder(SCROLL_PREVIOUS);
 if (mListener != null) {
 mListener.onImageSwitch(mCurrentImage);
 }
 beginScroll(getScrollX(), 0, disX, 0, SCROLL_PREVIOUS);
 }
 }
 /**
 * 滚动回原图片。
 */
 public void scrollBack() {
 if (mScroller.isFinished()) {
 beginScroll(getScrollX(), 0, -getScrollX(), 0, SCROLL_BACK);
 }
 }
 /**
 * 回收所有图片对象,释放内存。
 */
 public void clear() {
 for (int i = 0; i < mCount; i++) {
 Image3DView childView = (Image3DView) getChildAt(i);
 childView.recycleBitmap();
 }
 }
 /**
 * 让控件中的所有图片开始滚动。
 */
 private void beginScroll(int startX, int startY, int dx, int dy,
 final int action) {
 int duration = (int) (700f / mImageWidth * Math.abs(dx));
 mScroller.startScroll(startX, startY, dx, dy, duration);
 invalidate();
 handler.postDelayed(new Runnable() {
 @Override
 public void run() {
 if (action == SCROLL_NEXT || action == SCROLL_PREVIOUS) {
 forceToRelayout = true;
 requestLayout();
 }
 }
 }, duration);
 }
 /**
 * 根据当前图片的下标和传入的item参数,来判断item位置上应该显示哪张图片。
 *
 * @param item
 * 取值范围是1-5
 * @return 对应item位置上应该显示哪张图片。
 */
 private int getIndexForItem(int item) {
 int index = -1;
 index = mCurrentImage + item - 3;
 while (index < 0) {
 index = index + mCount;
 }
 while (index > mCount - 1) {
 index = index - mCount;
 }
 return index;
 }
 /**
 * 刷新所有图片的显示状态,包括当前的旋转角度。
 */
 private void refreshImageShowing() {
 for (int i = 0; i < mItems.length; i++) {
 Image3DView childView = (Image3DView) getChildAt(mItems[i]);
 childView.setRotateData(i, getScrollX());
 childView.invalidate();
 }
 }
 /**
 * 检查图片的边界,防止图片的下标超出规定范围。
 */
 private void checkImageSwitchBorder(int action) {
 if (action == SCROLL_NEXT && ++mCurrentImage >= mCount) {
 mCurrentImage = 0;
 } else if (action == SCROLL_PREVIOUS && --mCurrentImage < 0) {
 mCurrentImage = mCount - 1;
 }
 }
 /**
 * 判断是否应该滚动到下一张图片。
 */
 private boolean shouldScrollToNext(int velocityX) {
 return velocityX < -SNAP_VELOCITY || getScrollX() > mImageWidth / 2;
 }
 /**
 * 判断是否应该滚动到上一张图片。
 */
 private boolean shouldScrollToPrevious(int velocityX) {
 return velocityX > SNAP_VELOCITY || getScrollX() < -mImageWidth / 2;
 }
 /**
 * 图片滚动的监听器
 */
 public interface OnImageSwitchListener {
 /**
 * 当图片滚动时会回调此方法
 *
 * @param currentImage
 * 当前图片的坐标
 */
 void onImageSwitch(int currentImage);
 }
}

这段代码也比较长,我们来一点点进行分析。在onLayout()方法首先要判断子视图个数是不是大于等于5,如果不足5个则图片轮播器无法正常显示,直接return掉。如果大于等于5个,就会通过一个for循环来为每个子视图分配显示的位置,而每个子视图都是一个Image3DView,在for循环中又会调用Image3DView的initImageViewBitmap()方法来为每个控件执行初始化操作,之后会调用refreshImageShowing()方法来刷新图片的显示状态。

接着当手指在Image3DSwitchView控件上滑动的时候就会进入到onTouchEvent()方法中,当手指按下时会记录按下时的横坐标,然后当手指滑动时会计算出滑动的距离,并调用scrollBy()方法来进行滚动,当手指离开屏幕时会距离当前滑动的距离和速度来决定,是滚动到下一张图片,还是滚动到上一张图片,还是滚动回原图片。分别调用的方法是scrollToNext()、scrollToPrevious()和scrollBack()。

在scrollToNext()方法中会先计算一下还需滚动的距离,然后进行一下边界检查,防止当前图片的下标超出合理范围,接着会调用beginScroll()方法来进行滚动。在beginScroll()方法中其实就是调用了Scroller的startScroll()方法来执行滚动操作的,当滚动结束后还会调用requestLayout()方法来要求重新布局,之后onLayout()方法就会重新执行,每个图片的位置也就会跟着改变了。至于scrollToPrevious()和scrollBack()方法的原理也是一样的,这里就不再重复分析了。

那么在onLayout()方法的最后调用的refreshImageShowing()方法到底执行了什么操作呢?其实就是遍历了一下每个Image3DView控件,然后调用它的setRotateData()方法,并把图片的下标和滚动距离传进去,这样每张图片就知道应该如何进行旋转了。

另外一些其它的细节就不在这里讲解了,注释写的还是比较详细的,你可以慢慢地去分析和理解。

那么下面我们来看下如何使用Image3DSwitchView这个控件吧,打开或新建activity_main.xml作为程序的主布局文件,代码如下所示:

?
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
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:background="#fff" >
 <com.example.imageswitchviewtest.Image3DSwitchView
 android:id="@+id/image_switch_view"
 android:layout_width="match_parent"
 android:layout_height="150dp" >
 <com.example.imageswitchviewtest.Image3DView
 android:id="@+id/image1"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:scaleType="fitXY"
 android:src="@drawable/image1" />
 <com.example.imageswitchviewtest.Image3DView
 android:id="@+id/image2"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:scaleType="fitXY"
 android:src="@drawable/image2" />
 <com.example.imageswitchviewtest.Image3DView
 android:id="@+id/image3"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:scaleType="fitXY"
 android:src="@drawable/image3" />
 <com.example.imageswitchviewtest.Image3DView
 android:id="@+id/image4"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:scaleType="fitXY"
 android:src="@drawable/image4" />
 <com.example.imageswitchviewtest.Image3DView
 android:id="@+id/image5"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:scaleType="fitXY"
 android:src="@drawable/image5" />
 <com.example.imageswitchviewtest.Image3DView
 android:id="@+id/image6"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:scaleType="fitXY"
 android:src="@drawable/image6" />
 <com.example.imageswitchviewtest.Image3DView
 android:id="@+id/image7"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:scaleType="fitXY"
 android:src="@drawable/image7" />
 </com.example.imageswitchviewtest.Image3DSwitchView>
</RelativeLayout>

可以看到,这里我们引入了一个Image3DSwitchView控件,然后在这个控件下面又添加了7个Image3DView控件,每个Image3DView其实就是一个ImageView,因此我们可以通过android:src属于给它指定一张图片。注意前面也说过了,Image3DSwitchView控件下的子控件必须大于等于5个,不然将无法正常显示。

代码到这里就写得差不多了,现在运行一下程序就可以看到一个3D版的图片轮播器,使用手指进行滑动可以查看更多的图片,如下图所示:

Android高级图片滚动控件实现3D版图片轮播器

怎么样?效果还是非常不错的吧!除此之外,Image3DSwitchView中还提供了setCurrentImage()方法和setOnImageSwitchListener()方法,分别可用于设置当前显示哪张图片,以及设置图片滚动的监听器,有了这些方法,你可以更加轻松地在Image3DSwitchView的基础上进行扩展,比如说加入页签显示功能等。

好了,今天的讲解就到这里,有疑问的朋友可以在下面留言(不过最近工作着实繁忙,恐怕无法一一回复大家)。

源码下载,请点击这里

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:https://blog.csdn.net/guolin_blog/article/details/17482089