[关闭]
@myron-lee 2017-03-22T12:28:21.000000Z 字数 8798 阅读 2436

自定义ViewPager实现轮播效果

Blog


前言

这种轮播效果多应用于展示电影海报,但是效果并不好,有些没有手势动画,更加没有fling效果。我将ViewPager的源码拷贝出来,做了修改,实现了这两个效果。

效果图

效果图

原理

  1. 一屏显示三个page,让中间一个page居中突出显示
    首先我们可以设置一个page宽为ViewPager宽的一定比例,比如 0.7。然后,ViewPager在两个page之间切换是通过设置scrollX实现的(你可以简单把ViewPager想象为一个HorizontalScrollView
    )。但是ViewPager原本的逻辑是让当前的page靠ViewPager的左边缘显示的。所以我们需要把修改这部分的逻辑,把scrollX加上一个负偏移,让中间一个page居中。见第一个代码段。
  2. 手势动画
    手势动画是通过监听滑动状态,并改变相关视图的变换矩阵实现的。因为你的page不再充满ViewPager,也不再靠ViewPager左边缘显示,所以处理起来比较麻烦,要分段处理。见第二个代码段。
  3. fling效果
    ViewPager原本的逻辑是手指抬起的时候,根据移动距离和速度去判断是否翻页或退页,没有fling。我的做法在手指抬起时,判断速度,如果速度小于一定阈值按ViewPager原先的处理方式处理,否则,使用Scroller工具类进行fling,在fling结束后调整位置,选中特定的page。见第三个代码段。

Demo

Github

源码(我修改的ViewPager部分)

  1. private void scrollToItem(int item, boolean smoothScroll, int velocity,
  2. boolean dispatchSelected) {
  3. final ItemInfo curInfo = infoForPosition(item);
  4. int destX = 0;
  5. if (curInfo != null) {
  6. final int width = getClientWidth();
  7. destX = (int) (width * Math.max(mFirstOffset,
  8. Math.min(curInfo.offset, mLastOffset)));
  9. }
  10. //我的修改
  11. if (item != 0 && item != mAdapter.getCount() - 2 && item != mAdapter.getCount() - 1) {
  12. destX = destX - itemScrollOffset;
  13. }
  14. /*
  15. if (item == mAdapter.getCount() - 1) {
  16. destX = destX + itemScrollOffset;
  17. } else {
  18. destX = destX - itemScrollOffset;
  19. }
  20. */
  21. if (smoothScroll) {
  22. smoothScrollTo(destX, 0, velocity);
  23. if (dispatchSelected) {
  24. dispatchOnPageSelected(item);
  25. }
  26. } else {
  27. if (dispatchSelected) {
  28. dispatchOnPageSelected(item);
  29. }
  30. completeScroll(false);
  31. scrollTo(destX, 0);
  32. pageScrolled(destX);
  33. }
  34. }
  1. //这个逻辑比较复杂,总的来说是通过第一个page的相对窗口的偏移去计算当前可见的三个page的缩放比例,这个计算过程比较复杂
  2. //这跟你的page占屏幕宽度的比例,后台page缩放多少有关。
  3. viewPager.addOnPageChangeListener(new MultiCardViewPager.OnPageChangeListener() {
  4. @Override
  5. public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
  6. // Log.e("", position + " " + positionOffset);
  7. if (position == 0 && positionOffset == 0) {
  8. return;
  9. }
  10. View leftPage = viewPager.findViewWithTag(position);
  11. View middlePage = viewPager.findViewWithTag(position + 1);
  12. View rightPage = viewPager.findViewWithTag(position + 2);
  13. if (position == 0) {
  14. if (middlePage != null) {
  15. float scale = 1 - positionOffset * frontPageLeftOffset * frontBackScaleDelta;
  16. setScale(middlePage, scale);
  17. setDim(middlePage, scale);
  18. }
  19. if (rightPage != null) {
  20. float scale = backgroundPageScale + positionOffset * frontPageLeftOffset * frontBackScaleDelta;
  21. setScale(rightPage, scale);
  22. setDim(rightPage, scale);
  23. }
  24. return;
  25. }
  26. if (leftPage != null) {
  27. if (positionOffset < pageGoToBackgroundOffsetThreshold) {
  28. //become smaller
  29. float scale = 1 - (positionOffset + frontPageLeftOffset) * frontBackScaleDelta;
  30. setScale(leftPage, scale);
  31. setDim(leftPage, scale);
  32. } else {
  33. //stay still
  34. setScale(leftPage, backgroundPageScale);
  35. setDim(leftPage, backgroundPageScale);
  36. }
  37. }
  38. if (middlePage != null) {
  39. if (positionOffset < pageGoToBackgroundOffsetThreshold) {
  40. //become bigger
  41. float scale = backgroundPageScale + (positionOffset + frontPageLeftOffset) * frontBackScaleDelta;
  42. setScale(middlePage, scale);
  43. setDim(middlePage, scale);
  44. } else {
  45. //become smaller
  46. float scale = 1 - (positionOffset - pageGoToBackgroundOffsetThreshold) * frontBackScaleDelta;
  47. setScale(middlePage, scale);
  48. setDim(middlePage, scale);
  49. }
  50. }
  51. if (rightPage != null) {
  52. if (positionOffset < pageGoToBackgroundOffsetThreshold) {
  53. //stay still
  54. setScale(rightPage, backgroundPageScale);
  55. setDim(rightPage, backgroundPageScale);
  56. } else {
  57. //become bigger
  58. float scale = backgroundPageScale + (positionOffset - pageGoToBackgroundOffsetThreshold) * frontBackScaleDelta;
  59. setScale(rightPage, scale);
  60. setDim(rightPage, scale);
  61. }
  62. }
  63. }
  64. @Override
  65. public void onPageSelected(int position) {
  66. }
  67. @Override
  68. public void onPageScrollStateChanged(int state) {
  69. }
  70. });
  1. @Override
  2. public boolean onTouchEvent(MotionEvent ev) {
  3. if (mFakeDragging) {
  4. // A fake drag is in progress already, ignore this real one
  5. // but still eat the touch events.
  6. // (It is likely that the user is multi-touching the screen.)
  7. return true;
  8. }
  9. if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
  10. // Don't handle edge touches immediately -- they may actually belong to one of our
  11. // descendants.
  12. return false;
  13. }
  14. if (mAdapter == null || mAdapter.getCount() == 0) {
  15. // Nothing to present or scroll; nothing to touch.
  16. return false;
  17. }
  18. if (mVelocityTracker == null) {
  19. mVelocityTracker = VelocityTracker.obtain();
  20. }
  21. mVelocityTracker.addMovement(ev);
  22. final int action = ev.getAction();
  23. boolean needsInvalidate = false;
  24. switch (action & MotionEventCompat.ACTION_MASK) {
  25. case MotionEvent.ACTION_DOWN: {
  26. mScroller.abortAnimation();
  27. mPopulatePending = false;
  28. populate();
  29. // Remember where the motion event started
  30. mLastMotionX = mInitialMotionX = ev.getX();
  31. mLastMotionY = mInitialMotionY = ev.getY();
  32. mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
  33. break;
  34. }
  35. case MotionEvent.ACTION_MOVE:
  36. if (!mIsBeingDragged) {
  37. final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
  38. final float x = MotionEventCompat.getX(ev, pointerIndex);
  39. final float xDiff = Math.abs(x - mLastMotionX);
  40. final float y = MotionEventCompat.getY(ev, pointerIndex);
  41. final float yDiff = Math.abs(y - mLastMotionY);
  42. if (DEBUG)
  43. Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
  44. if (xDiff > mTouchSlop && xDiff > yDiff) {
  45. if (DEBUG) Log.v(TAG, "Starting drag!");
  46. mIsBeingDragged = true;
  47. requestParentDisallowInterceptTouchEvent(true);
  48. mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop :
  49. mInitialMotionX - mTouchSlop;
  50. mLastMotionY = y;
  51. setScrollState(SCROLL_STATE_DRAGGING);
  52. setScrollingCacheEnabled(true);
  53. // Disallow Parent Intercept, just in case
  54. ViewParent parent = getParent();
  55. if (parent != null) {
  56. parent.requestDisallowInterceptTouchEvent(true);
  57. }
  58. }
  59. }
  60. // Not else! Note that mIsBeingDragged can be set above.
  61. if (mIsBeingDragged) {
  62. // Scroll to follow the motion event
  63. final int activePointerIndex = MotionEventCompat.findPointerIndex(
  64. ev, mActivePointerId);
  65. final float x = MotionEventCompat.getX(ev, activePointerIndex);
  66. needsInvalidate |= performDrag(x);
  67. }
  68. break;
  69. case MotionEvent.ACTION_UP:
  70. if (mIsBeingDragged) {
  71. final VelocityTracker velocityTracker = mVelocityTracker;
  72. velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
  73. int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
  74. velocityTracker, mActivePointerId);
  75. mPopulatePending = true;
  76. final int width = getClientWidth();
  77. final int scrollX = getScrollX();
  78. final ItemInfo ii = infoForCurrentScrollPosition();
  79. final int currentPage = ii.position;
  80. final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;
  81. final int activePointerIndex =
  82. MotionEventCompat.findPointerIndex(ev, mActivePointerId);
  83. final float x = MotionEventCompat.getX(ev, activePointerIndex);
  84. final int totalDelta = (int) (x - mInitialMotionX);
  85. //我的修改
  86. if (Math.abs(initialVelocity) < 4000) {
  87. int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
  88. totalDelta);
  89. setCurrentItemInternal(nextPage, true, true, initialVelocity);
  90. } else {
  91. fling = true;
  92. // Log.e("Velocity", initialVelocity + "");
  93. mScroller.fling(getScrollX(), getScrollY(), -initialVelocity, 0, minScrollX, maxScrollX, getScrollY(), getScrollY());
  94. }
  95. mActivePointerId = INVALID_POINTER;
  96. endDrag();
  97. needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
  98. }
  99. break;
  100. case MotionEvent.ACTION_CANCEL:
  101. if (mIsBeingDragged) {
  102. scrollToItem(mCurItem, true, 0, false);
  103. mActivePointerId = INVALID_POINTER;
  104. endDrag();
  105. needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
  106. }
  107. break;
  108. case MotionEventCompat.ACTION_POINTER_DOWN: {
  109. final int index = MotionEventCompat.getActionIndex(ev);
  110. final float x = MotionEventCompat.getX(ev, index);
  111. mLastMotionX = x;
  112. mActivePointerId = MotionEventCompat.getPointerId(ev, index);
  113. break;
  114. }
  115. case MotionEventCompat.ACTION_POINTER_UP:
  116. onSecondaryPointerUp(ev);
  117. mLastMotionX = MotionEventCompat.getX(ev,
  118. MotionEventCompat.findPointerIndex(ev, mActivePointerId));
  119. break;
  120. }
  121. if (needsInvalidate) {
  122. ViewCompat.postInvalidateOnAnimation(this);
  123. }
  124. return true;
  125. }
  126. @Override
  127. public void computeScroll() {
  128. if (!myScroller.isFinished() && myScroller.computeScrollOffset()) {
  129. int oldX = getScrollX();
  130. int oldY = getScrollY();
  131. int x = myScroller.getCurrX();
  132. int y = myScroller.getCurrY();
  133. if (oldX != x || oldY != y) {
  134. scrollTo(x, y);
  135. if (!pageScrolled(x)) {
  136. myScroller.abortAnimation();
  137. scrollTo(0, y);
  138. }
  139. }
  140. // Keep on drawing until the animation has finished.
  141. ViewCompat.postInvalidateOnAnimation(this);
  142. return;
  143. }
  144. if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
  145. int oldX = getScrollX();
  146. int oldY = getScrollY();
  147. int x = mScroller.getCurrX();
  148. int y = mScroller.getCurrY();
  149. if (oldX != x || oldY != y) {
  150. scrollTo(x, y);
  151. if (!pageScrolled(x)) {
  152. mScroller.abortAnimation();
  153. scrollTo(0, y);
  154. }
  155. }
  156. // Keep on drawing until the animation has finished.
  157. ViewCompat.postInvalidateOnAnimation(this);
  158. return;
  159. }
  160. //我的修改
  161. if (fling) {
  162. ItemInfo curInfo = infoForCurrentScrollPosition();
  163. if (getScrollX() > (curInfo.offset + 0.225) * getWidth()) {
  164. if (curInfo.position + 1 < mAdapter.getCount()) {
  165. curInfo = infoForPosition(curInfo.position + 1);
  166. }
  167. }
  168. // final ItemInfo curInfo = infoForPosition(item);
  169. int destX = 0;
  170. if (curInfo != null) {
  171. final int width = getClientWidth();
  172. destX = (int) (width * Math.max(mFirstOffset,
  173. Math.min(curInfo.offset, mLastOffset)));
  174. }
  175. if (curInfo.position != 0 && curInfo.position != mAdapter.getCount() - 2 && curInfo.position != mAdapter.getCount() - 1) {
  176. destX = destX - itemScrollOffset;
  177. }
  178. // smoothScrollTo(destX, 0, 100);
  179. mScroller.startScroll(getScrollX(), getScrollY(), destX - getScrollX(), 0, 600);
  180. ViewCompat.postInvalidateOnAnimation(this);
  181. dispatchOnPageSelected(curInfo.position);
  182. fling = false;
  183. return;
  184. }
  185. // scrollToItem(itemInfo.position, true, 100, true);
  186. // setCurrentItemInternal(itemInfo.position, false, true, 100);
  187. // setCurrentItem(itemInfo.position, true);
  188. // fling = false;
  189. // Done with scroll, clean up state.
  190. completeScroll(true);
  191. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注