Android高级图片滚动控件,编写3D版的图片轮播器

时间:2022-05-28 03:47:41

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/17482089

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

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

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

Android高级图片滚动控件,编写3D版的图片轮播器

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

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

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

  1. public class Image3DView extends ImageView {
  2. /**
  3. * 旋转角度的基准值
  4. */
  5. private static final float BASE_DEGREE = 50f;
  6. /**
  7. * 旋转深度的基准值
  8. */
  9. private static final float BASE_DEEP = 150f;
  10. private Camera mCamera;
  11. private Matrix mMaxtrix;
  12. private Bitmap mBitmap;
  13. /**
  14. * 当前图片对应的下标
  15. */
  16. private int mIndex;
  17. /**
  18. * 在前图片在X轴方向滚动的距离
  19. */
  20. private int mScrollX;
  21. /**
  22. * Image3DSwitchView控件的宽度
  23. */
  24. private int mLayoutWidth;
  25. /**
  26. * 当前图片的宽度
  27. */
  28. private int mWidth;
  29. /**
  30. * 当前旋转的角度
  31. */
  32. private float mRotateDegree;
  33. /**
  34. * 旋转的中心点
  35. */
  36. private float mDx;
  37. /**
  38. * 旋转的深度
  39. */
  40. private float mDeep;
  41. public Image3DView(Context context, AttributeSet attrs) {
  42. super(context, attrs);
  43. mCamera = new Camera();
  44. mMaxtrix = new Matrix();
  45. }
  46. /**
  47. * 初始化Image3DView所需要的信息,包括图片宽度,截取背景图等。
  48. */
  49. public void initImageViewBitmap() {
  50. if (mBitmap == null) {
  51. setDrawingCacheEnabled(true);
  52. buildDrawingCache();
  53. mBitmap = getDrawingCache();
  54. }
  55. mLayoutWidth = Image3DSwitchView.mWidth;
  56. mWidth = getWidth() + Image3DSwitchView.IMAGE_PADDING * 2;
  57. }
  58. /**
  59. * 设置旋转角度。
  60. *
  61. * @param index
  62. *            当前图片的下标
  63. * @param scrollX
  64. *            当前图片在X轴方向滚动的距离
  65. */
  66. public void setRotateData(int index, int scrollX) {
  67. mIndex = index;
  68. mScrollX = scrollX;
  69. }
  70. /**
  71. * 回收当前的Bitmap对象,以释放内存。
  72. */
  73. public void recycleBitmap() {
  74. if (mBitmap != null && !mBitmap.isRecycled()) {
  75. mBitmap.recycle();
  76. }
  77. }
  78. @Override
  79. public void setImageResource(int resId) {
  80. super.setImageResource(resId);
  81. mBitmap = null;
  82. initImageViewBitmap();
  83. }
  84. @Override
  85. public void setImageBitmap(Bitmap bm) {
  86. super.setImageBitmap(bm);
  87. mBitmap = null;
  88. initImageViewBitmap();
  89. }
  90. @Override
  91. public void setImageDrawable(Drawable drawable) {
  92. super.setImageDrawable(drawable);
  93. mBitmap = null;
  94. initImageViewBitmap();
  95. }
  96. @Override
  97. public void setImageURI(Uri uri) {
  98. super.setImageURI(uri);
  99. mBitmap = null;
  100. initImageViewBitmap();
  101. }
  102. @Override
  103. protected void onDraw(Canvas canvas) {
  104. if (mBitmap == null) {
  105. // 如果Bitmap对象还不存在,先使用父类的onDraw方法进行绘制
  106. super.onDraw(canvas);
  107. } else {
  108. if (isImageVisible()) {
  109. // 绘图时需要注意,只有当图片可见的时候才进行绘制,这样可以节省运算效率
  110. computeRotateData();
  111. mCamera.save();
  112. mCamera.translate(0.0f, 0.0f, mDeep);
  113. mCamera.rotateY(mRotateDegree);
  114. mCamera.getMatrix(mMaxtrix);
  115. mCamera.restore();
  116. mMaxtrix.preTranslate(-mDx, -getHeight() / 2);
  117. mMaxtrix.postTranslate(mDx, getHeight() / 2);
  118. canvas.drawBitmap(mBitmap, mMaxtrix, null);
  119. }
  120. }
  121. }
  122. /**
  123. * 在这里计算所有旋转所需要的数据。
  124. */
  125. private void computeRotateData() {
  126. float degreePerPix = BASE_DEGREE / mWidth;
  127. float deepPerPix = BASE_DEEP / ((mLayoutWidth - mWidth) / 2);
  128. switch (mIndex) {
  129. case 0:
  130. mDx = mWidth;
  131. mRotateDegree = 360f - (2 * mWidth + mScrollX) * degreePerPix;
  132. if (mScrollX < -mWidth) {
  133. mDeep = 0;
  134. } else {
  135. mDeep = (mWidth + mScrollX) * deepPerPix;
  136. }
  137. break;
  138. case 1:
  139. if (mScrollX > 0) {
  140. mDx = mWidth;
  141. mRotateDegree = (360f - BASE_DEGREE) - mScrollX * degreePerPix;
  142. mDeep = mScrollX * deepPerPix;
  143. } else {
  144. if (mScrollX < -mWidth) {
  145. mDx = -Image3DSwitchView.IMAGE_PADDING * 2;
  146. mRotateDegree = (-mScrollX - mWidth) * degreePerPix;
  147. } else {
  148. mDx = mWidth;
  149. mRotateDegree = 360f - (mWidth + mScrollX) * degreePerPix;
  150. }
  151. mDeep = 0;
  152. }
  153. break;
  154. case 2:
  155. if (mScrollX > 0) {
  156. mDx = mWidth;
  157. mRotateDegree = 360f - mScrollX * degreePerPix;
  158. mDeep = 0;
  159. if (mScrollX > mWidth) {
  160. mDeep = (mScrollX - mWidth) * deepPerPix;
  161. }
  162. } else {
  163. mDx = -Image3DSwitchView.IMAGE_PADDING * 2;
  164. mRotateDegree = -mScrollX * degreePerPix;
  165. mDeep = 0;
  166. if (mScrollX < -mWidth) {
  167. mDeep = -(mWidth + mScrollX) * deepPerPix;
  168. }
  169. }
  170. break;
  171. case 3:
  172. if (mScrollX < 0) {
  173. mDx = -Image3DSwitchView.IMAGE_PADDING * 2;
  174. mRotateDegree = BASE_DEGREE - mScrollX * degreePerPix;
  175. mDeep = -mScrollX * deepPerPix;
  176. } else {
  177. if (mScrollX > mWidth) {
  178. mDx = mWidth;
  179. mRotateDegree = 360f - (mScrollX - mWidth) * degreePerPix;
  180. } else {
  181. mDx = -Image3DSwitchView.IMAGE_PADDING * 2;
  182. mRotateDegree = BASE_DEGREE - mScrollX * degreePerPix;
  183. }
  184. mDeep = 0;
  185. }
  186. break;
  187. case 4:
  188. mDx = -Image3DSwitchView.IMAGE_PADDING * 2;
  189. mRotateDegree = (2 * mWidth - mScrollX) * degreePerPix;
  190. if (mScrollX > mWidth) {
  191. mDeep = 0;
  192. } else {
  193. mDeep = (mWidth - mScrollX) * deepPerPix;
  194. }
  195. break;
  196. }
  197. }
  198. /**
  199. * 判断当前图片是否可见。
  200. *
  201. * @return 当前图片可见返回true,不可见返回false。
  202. */
  203. private boolean isImageVisible() {
  204. boolean isVisible = false;
  205. switch (mIndex) {
  206. case 0:
  207. if (mScrollX < (mLayoutWidth - mWidth) / 2 - mWidth) {
  208. isVisible = true;
  209. } else {
  210. isVisible = false;
  211. }
  212. break;
  213. case 1:
  214. if (mScrollX > (mLayoutWidth - mWidth) / 2) {
  215. isVisible = false;
  216. } else {
  217. isVisible = true;
  218. }
  219. break;
  220. case 2:
  221. if (mScrollX > mLayoutWidth / 2 + mWidth / 2
  222. || mScrollX < -mLayoutWidth / 2 - mWidth / 2) {
  223. isVisible = false;
  224. } else {
  225. isVisible = true;
  226. }
  227. break;
  228. case 3:
  229. if (mScrollX < -(mLayoutWidth - mWidth) / 2) {
  230. isVisible = false;
  231. } else {
  232. isVisible = true;
  233. }
  234. break;
  235. case 4:
  236. if (mScrollX > mWidth - (mLayoutWidth - mWidth) / 2) {
  237. isVisible = true;
  238. } else {
  239. isVisible = false;
  240. }
  241. break;
  242. }
  243. return isVisible;
  244. }
  245. }

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

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

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

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

  1. public class Image3DSwitchView extends ViewGroup {
  2. /**
  3. * 图片左右两边的空白间距
  4. */
  5. public static final int IMAGE_PADDING = 10;
  6. private static final int TOUCH_STATE_REST = 0;
  7. private static final int TOUCH_STATE_SCROLLING = 1;
  8. /**
  9. * 滚动到下一张图片的速度
  10. */
  11. private static final int SNAP_VELOCITY = 600;
  12. /**
  13. * 表示滚动到下一张图片这个动作
  14. */
  15. private static final int SCROLL_NEXT = 0;
  16. /**
  17. * 表示滚动到上一张图片这个动作
  18. */
  19. private static final int SCROLL_PREVIOUS = 1;
  20. /**
  21. * 表示滚动回原图片这个动作
  22. */
  23. private static final int SCROLL_BACK = 2;
  24. private static Handler handler = new Handler();
  25. /**
  26. * 控件宽度
  27. */
  28. public static int mWidth;
  29. private VelocityTracker mVelocityTracker;
  30. private Scroller mScroller;
  31. /**
  32. * 图片滚动监听器,当图片发生滚动时回调这个接口
  33. */
  34. private OnImageSwitchListener mListener;
  35. /**
  36. * 记录当前的触摸状态
  37. */
  38. private int mTouchState = TOUCH_STATE_REST;
  39. /**
  40. * 记录被判定为滚动运动的最小滚动值
  41. */
  42. private int mTouchSlop;
  43. /**
  44. * 记录控件高度
  45. */
  46. private int mHeight;
  47. /**
  48. * 记录每张图片的宽度
  49. */
  50. private int mImageWidth;
  51. /**
  52. * 记录图片的总数量
  53. */
  54. private int mCount;
  55. /**
  56. * 记录当前显示图片的坐标
  57. */
  58. private int mCurrentImage;
  59. /**
  60. * 记录上次触摸的横坐标值
  61. */
  62. private float mLastMotionX;
  63. /**
  64. * 是否强制重新布局
  65. */
  66. private boolean forceToRelayout;
  67. private int[] mItems;
  68. public Image3DSwitchView(Context context, AttributeSet attrs) {
  69. super(context, attrs);
  70. mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
  71. mScroller = new Scroller(context);
  72. }
  73. @Override
  74. protected void onLayout(boolean changed, int l, int t, int r, int b) {
  75. if (changed || forceToRelayout) {
  76. mCount = getChildCount();
  77. // 图片数量必须大于5,不然无法正常显示
  78. if (mCount < 5) {
  79. return;
  80. }
  81. mWidth = getMeasuredWidth();
  82. mHeight = getMeasuredHeight();
  83. // 每张图片的宽度设定为控件宽度的百分之六十
  84. mImageWidth = (int) (mWidth * 0.6);
  85. if (mCurrentImage >= 0 && mCurrentImage < mCount) {
  86. mScroller.abortAnimation();
  87. setScrollX(0);
  88. int left = -mImageWidth * 2 + (mWidth - mImageWidth) / 2;
  89. // 分别获取每个位置上应该显示的图片下标
  90. int[] items = { getIndexForItem(1), getIndexForItem(2),
  91. getIndexForItem(3), getIndexForItem(4),
  92. getIndexForItem(5) };
  93. mItems = items;
  94. // 通过循环为每张图片设定位置
  95. for (int i = 0; i < items.length; i++) {
  96. Image3DView childView = (Image3DView) getChildAt(items[i]);
  97. childView.layout(left + IMAGE_PADDING, 0, left
  98. + mImageWidth - IMAGE_PADDING, mHeight);
  99. childView.initImageViewBitmap();
  100. left = left + mImageWidth;
  101. }
  102. refreshImageShowing();
  103. }
  104. forceToRelayout = false;
  105. }
  106. }
  107. @Override
  108. public boolean onTouchEvent(MotionEvent event) {
  109. if (mScroller.isFinished()) {
  110. if (mVelocityTracker == null) {
  111. mVelocityTracker = VelocityTracker.obtain();
  112. }
  113. mVelocityTracker.addMovement(event);
  114. int action = event.getAction();
  115. float x = event.getX();
  116. switch (action) {
  117. case MotionEvent.ACTION_DOWN:
  118. // 记录按下时的横坐标
  119. mLastMotionX = x;
  120. break;
  121. case MotionEvent.ACTION_MOVE:
  122. int disX = (int) (mLastMotionX - x);
  123. mLastMotionX = x;
  124. scrollBy(disX, 0);
  125. // 当发生移动时刷新图片的显示状态
  126. refreshImageShowing();
  127. break;
  128. case MotionEvent.ACTION_UP:
  129. mVelocityTracker.computeCurrentVelocity(1000);
  130. int velocityX = (int) mVelocityTracker.getXVelocity();
  131. if (shouldScrollToNext(velocityX)) {
  132. // 滚动到下一张图
  133. scrollToNext();
  134. } else if (shouldScrollToPrevious(velocityX)) {
  135. // 滚动到上一张图
  136. scrollToPrevious();
  137. } else {
  138. // 滚动回当前图片
  139. scrollBack();
  140. }
  141. if (mVelocityTracker != null) {
  142. mVelocityTracker.recycle();
  143. mVelocityTracker = null;
  144. }
  145. break;
  146. }
  147. }
  148. return true;
  149. }
  150. /**
  151. * 根据当前的触摸状态来决定是否屏蔽子控件的交互能力。
  152. */
  153. @Override
  154. public boolean onInterceptTouchEvent(MotionEvent ev) {
  155. int action = ev.getAction();
  156. if ((action == MotionEvent.ACTION_MOVE)
  157. && (mTouchState != TOUCH_STATE_REST)) {
  158. return true;
  159. }
  160. float x = ev.getX();
  161. switch (action) {
  162. case MotionEvent.ACTION_DOWN:
  163. mLastMotionX = x;
  164. mTouchState = TOUCH_STATE_REST;
  165. break;
  166. case MotionEvent.ACTION_MOVE:
  167. int xDiff = (int) Math.abs(mLastMotionX - x);
  168. if (xDiff > mTouchSlop) {
  169. mTouchState = TOUCH_STATE_SCROLLING;
  170. }
  171. break;
  172. case MotionEvent.ACTION_UP:
  173. default:
  174. mTouchState = TOUCH_STATE_REST;
  175. break;
  176. }
  177. return mTouchState != TOUCH_STATE_REST;
  178. }
  179. @Override
  180. public void computeScroll() {
  181. if (mScroller.computeScrollOffset()) {
  182. scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
  183. refreshImageShowing();
  184. postInvalidate();
  185. }
  186. }
  187. /**
  188. * 设置图片滚动的监听器,每当有图片滚动时会回调此接口。
  189. *
  190. * @param listener
  191. *            图片滚动监听器
  192. */
  193. public void setOnImageSwitchListener(OnImageSwitchListener listener) {
  194. mListener = listener;
  195. }
  196. /**
  197. * 设置当前显示图片的下标,注意如果该值小于零或大于等于图片的总数量,图片则无法正常显示。
  198. *
  199. * @param currentImage
  200. *            图片的下标
  201. */
  202. public void setCurrentImage(int currentImage) {
  203. mCurrentImage = currentImage;
  204. requestLayout();
  205. }
  206. /**
  207. * 滚动到下一张图片。
  208. */
  209. public void scrollToNext() {
  210. if (mScroller.isFinished()) {
  211. int disX = mImageWidth - getScrollX();
  212. checkImageSwitchBorder(SCROLL_NEXT);
  213. if (mListener != null) {
  214. mListener.onImageSwitch(mCurrentImage);
  215. }
  216. beginScroll(getScrollX(), 0, disX, 0, SCROLL_NEXT);
  217. }
  218. }
  219. /**
  220. * 滚动到上一张图片。
  221. */
  222. public void scrollToPrevious() {
  223. if (mScroller.isFinished()) {
  224. int disX = -mImageWidth - getScrollX();
  225. checkImageSwitchBorder(SCROLL_PREVIOUS);
  226. if (mListener != null) {
  227. mListener.onImageSwitch(mCurrentImage);
  228. }
  229. beginScroll(getScrollX(), 0, disX, 0, SCROLL_PREVIOUS);
  230. }
  231. }
  232. /**
  233. * 滚动回原图片。
  234. */
  235. public void scrollBack() {
  236. if (mScroller.isFinished()) {
  237. beginScroll(getScrollX(), 0, -getScrollX(), 0, SCROLL_BACK);
  238. }
  239. }
  240. /**
  241. * 回收所有图片对象,释放内存。
  242. */
  243. public void clear() {
  244. for (int i = 0; i < mCount; i++) {
  245. Image3DView childView = (Image3DView) getChildAt(i);
  246. childView.recycleBitmap();
  247. }
  248. }
  249. /**
  250. * 让控件中的所有图片开始滚动。
  251. */
  252. private void beginScroll(int startX, int startY, int dx, int dy,
  253. final int action) {
  254. int duration = (int) (700f / mImageWidth * Math.abs(dx));
  255. mScroller.startScroll(startX, startY, dx, dy, duration);
  256. invalidate();
  257. handler.postDelayed(new Runnable() {
  258. @Override
  259. public void run() {
  260. if (action == SCROLL_NEXT || action == SCROLL_PREVIOUS) {
  261. forceToRelayout = true;
  262. requestLayout();
  263. }
  264. }
  265. }, duration);
  266. }
  267. /**
  268. * 根据当前图片的下标和传入的item参数,来判断item位置上应该显示哪张图片。
  269. *
  270. * @param item
  271. *            取值范围是1-5
  272. * @return 对应item位置上应该显示哪张图片。
  273. */
  274. private int getIndexForItem(int item) {
  275. int index = -1;
  276. index = mCurrentImage + item - 3;
  277. while (index < 0) {
  278. index = index + mCount;
  279. }
  280. while (index > mCount - 1) {
  281. index = index - mCount;
  282. }
  283. return index;
  284. }
  285. /**
  286. * 刷新所有图片的显示状态,包括当前的旋转角度。
  287. */
  288. private void refreshImageShowing() {
  289. for (int i = 0; i < mItems.length; i++) {
  290. Image3DView childView = (Image3DView) getChildAt(mItems[i]);
  291. childView.setRotateData(i, getScrollX());
  292. childView.invalidate();
  293. }
  294. }
  295. /**
  296. * 检查图片的边界,防止图片的下标超出规定范围。
  297. */
  298. private void checkImageSwitchBorder(int action) {
  299. if (action == SCROLL_NEXT && ++mCurrentImage >= mCount) {
  300. mCurrentImage = 0;
  301. } else if (action == SCROLL_PREVIOUS && --mCurrentImage < 0) {
  302. mCurrentImage = mCount - 1;
  303. }
  304. }
  305. /**
  306. * 判断是否应该滚动到下一张图片。
  307. */
  308. private boolean shouldScrollToNext(int velocityX) {
  309. return velocityX < -SNAP_VELOCITY || getScrollX() > mImageWidth / 2;
  310. }
  311. /**
  312. * 判断是否应该滚动到上一张图片。
  313. */
  314. private boolean shouldScrollToPrevious(int velocityX) {
  315. return velocityX > SNAP_VELOCITY || getScrollX() < -mImageWidth / 2;
  316. }
  317. /**
  318. * 图片滚动的监听器
  319. */
  320. public interface OnImageSwitchListener {
  321. /**
  322. * 当图片滚动时会回调此方法
  323. *
  324. * @param currentImage
  325. *            当前图片的坐标
  326. */
  327. void onImageSwitch(int currentImage);
  328. }
  329. }

这 段代码也比较长,我们来一点点进行分析。在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. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:layout_width="match_parent"
  3. android:layout_height="match_parent"
  4. android:background="#fff" >
  5. <com.example.imageswitchviewtest.Image3DSwitchView
  6. android:id="@+id/image_switch_view"
  7. android:layout_width="match_parent"
  8. android:layout_height="150dp" >
  9. <com.example.imageswitchviewtest.Image3DView
  10. android:id="@+id/image1"
  11. android:layout_width="match_parent"
  12. android:layout_height="match_parent"
  13. android:scaleType="fitXY"
  14. android:src="@drawable/image1" />
  15. <com.example.imageswitchviewtest.Image3DView
  16. android:id="@+id/image2"
  17. android:layout_width="match_parent"
  18. android:layout_height="match_parent"
  19. android:scaleType="fitXY"
  20. android:src="@drawable/image2" />
  21. <com.example.imageswitchviewtest.Image3DView
  22. android:id="@+id/image3"
  23. android:layout_width="match_parent"
  24. android:layout_height="match_parent"
  25. android:scaleType="fitXY"
  26. android:src="@drawable/image3" />
  27. <com.example.imageswitchviewtest.Image3DView
  28. android:id="@+id/image4"
  29. android:layout_width="match_parent"
  30. android:layout_height="match_parent"
  31. android:scaleType="fitXY"
  32. android:src="@drawable/image4" />
  33. <com.example.imageswitchviewtest.Image3DView
  34. android:id="@+id/image5"
  35. android:layout_width="match_parent"
  36. android:layout_height="match_parent"
  37. android:scaleType="fitXY"
  38. android:src="@drawable/image5" />
  39. <com.example.imageswitchviewtest.Image3DView
  40. android:id="@+id/image6"
  41. android:layout_width="match_parent"
  42. android:layout_height="match_parent"
  43. android:scaleType="fitXY"
  44. android:src="@drawable/image6" />
  45. <com.example.imageswitchviewtest.Image3DView
  46. android:id="@+id/image7"
  47. android:layout_width="match_parent"
  48. android:layout_height="match_parent"
  49. android:scaleType="fitXY"
  50. android:src="@drawable/image7" />
  51. </com.example.imageswitchviewtest.Image3DSwitchView>
  52. </RelativeLayout>

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

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

Android高级图片滚动控件,编写3D版的图片轮播器

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

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

源码下载,请点击这里