您也可以点此浏览,阅读体验更佳
前言
这种轮播效果多应用于展示电影海报,但是效果并不好,有些没有手势动画,更加没有fling效果。我将ViewPager的源码拷贝出来,做了修改,实现了这两个效果。
效果图
原理
- 一屏显示三个page,让中间一个page居中突出显示
首先我们可以设置一个page宽为ViewPager宽的一定比例,比如 0.7。然后,ViewPager在两个page之间切换是通过设置scrollX实现的(你可以简单把ViewPager想象为一个HorizontalScrollView
)。但是ViewPager原本的逻辑是让当前的page靠ViewPager的左边缘显示的。所以我们需要把修改这部分的逻辑,把scrollX加上一个负偏移,让中间一个page居中。见第一个代码段。 - 手势动画
手势动画是通过监听滑动状态,并改变相关视图的变换矩阵实现的。因为你的page不再充满ViewPager,也不再靠ViewPager左边缘显示,所以处理起来比较麻烦,要分段处理。见第二个代码段。 - fling效果
ViewPager原本的逻辑是手指抬起的时候,根据移动距离和速度去判断是否翻页或退页,没有fling。我的做法在手指抬起时,判断速度,如果速度小于一定阈值按ViewPager原先的处理方式处理,否则,使用Scroller工具类进行fling,在fling结束后调整位置,选中特定的page。见第三个代码段。
Demo
源码(我修改的ViewPager部分)
private void scrollToItem(int item, boolean smoothScroll, int velocity,
boolean dispatchSelected) {
final ItemInfo curInfo = infoForPosition(item);
int destX = 0;
if (curInfo != null) {
final int width = getClientWidth();
destX = (int) (width * Math.max(mFirstOffset,
Math.min(curInfo.offset, mLastOffset)));
}
//我的修改
if (item != 0 && item != mAdapter.getCount() - 2 && item != mAdapter.getCount() - 1) {
destX = destX - itemScrollOffset;
}
/*
if (item == mAdapter.getCount() - 1) {
destX = destX + itemScrollOffset;
} else {
destX = destX - itemScrollOffset;
}
*/
if (smoothScroll) {
smoothScrollTo(destX, 0, velocity);
if (dispatchSelected) {
dispatchOnPageSelected(item);
}
} else {
if (dispatchSelected) {
dispatchOnPageSelected(item);
}
completeScroll(false);
scrollTo(destX, 0);
pageScrolled(destX);
}
}
//这个逻辑比较复杂,总的来说是通过第一个page的相对窗口的偏移去计算当前可见的三个page的缩放比例,这个计算过程比较复杂
//这跟你的page占屏幕宽度的比例,后台page缩放多少有关。
viewPager.addOnPageChangeListener(new MultiCardViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// Log.e("", position + " " + positionOffset);
if (position == 0 && positionOffset == 0) {
return;
}
View leftPage = viewPager.findViewWithTag(position);
View middlePage = viewPager.findViewWithTag(position + 1);
View rightPage = viewPager.findViewWithTag(position + 2);
if (position == 0) {
if (middlePage != null) {
float scale = 1 - positionOffset * frontPageLeftOffset * frontBackScaleDelta;
setScale(middlePage, scale);
setDim(middlePage, scale);
}
if (rightPage != null) {
float scale = backgroundPageScale + positionOffset * frontPageLeftOffset * frontBackScaleDelta;
setScale(rightPage, scale);
setDim(rightPage, scale);
}
return;
}
if (leftPage != null) {
if (positionOffset < pageGoToBackgroundOffsetThreshold) {
//become smaller
float scale = 1 - (positionOffset + frontPageLeftOffset) * frontBackScaleDelta;
setScale(leftPage, scale);
setDim(leftPage, scale);
} else {
//stay still
setScale(leftPage, backgroundPageScale);
setDim(leftPage, backgroundPageScale);
}
}
if (middlePage != null) {
if (positionOffset < pageGoToBackgroundOffsetThreshold) {
//become bigger
float scale = backgroundPageScale + (positionOffset + frontPageLeftOffset) * frontBackScaleDelta;
setScale(middlePage, scale);
setDim(middlePage, scale);
} else {
//become smaller
float scale = 1 - (positionOffset - pageGoToBackgroundOffsetThreshold) * frontBackScaleDelta;
setScale(middlePage, scale);
setDim(middlePage, scale);
}
}
if (rightPage != null) {
if (positionOffset < pageGoToBackgroundOffsetThreshold) {
//stay still
setScale(rightPage, backgroundPageScale);
setDim(rightPage, backgroundPageScale);
} else {
//become bigger
float scale = backgroundPageScale + (positionOffset - pageGoToBackgroundOffsetThreshold) * frontBackScaleDelta;
setScale(rightPage, scale);
setDim(rightPage, scale);
}
}
}
@Override
public void onPageSelected(int position) {
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (mFakeDragging) {
// A fake drag is in progress already, ignore this real one
// but still eat the touch events.
// (It is likely that the user is multi-touching the screen.)
return true;
}
if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
// Don't handle edge touches immediately -- they may actually belong to one of our
// descendants.
return false;
}
if (mAdapter == null || mAdapter.getCount() == 0) {
// Nothing to present or scroll; nothing to touch.
return false;
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
final int action = ev.getAction();
boolean needsInvalidate = false;
switch (action & MotionEventCompat.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
mScroller.abortAnimation();
mPopulatePending = false;
populate();
// Remember where the motion event started
mLastMotionX = mInitialMotionX = ev.getX();
mLastMotionY = mInitialMotionY = ev.getY();
mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
break;
}
case MotionEvent.ACTION_MOVE:
if (!mIsBeingDragged) {
final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
final float x = MotionEventCompat.getX(ev, pointerIndex);
final float xDiff = Math.abs(x - mLastMotionX);
final float y = MotionEventCompat.getY(ev, pointerIndex);
final float yDiff = Math.abs(y - mLastMotionY);
if (DEBUG)
Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
if (xDiff > mTouchSlop && xDiff > yDiff) {
if (DEBUG) Log.v(TAG, "Starting drag!");
mIsBeingDragged = true;
requestParentDisallowInterceptTouchEvent(true);
mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop :
mInitialMotionX - mTouchSlop;
mLastMotionY = y;
setScrollState(SCROLL_STATE_DRAGGING);
setScrollingCacheEnabled(true);
// Disallow Parent Intercept, just in case
ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
}
// Not else! Note that mIsBeingDragged can be set above.
if (mIsBeingDragged) {
// Scroll to follow the motion event
final int activePointerIndex = MotionEventCompat.findPointerIndex(
ev, mActivePointerId);
final float x = MotionEventCompat.getX(ev, activePointerIndex);
needsInvalidate |= performDrag(x);
}
break;
case MotionEvent.ACTION_UP:
if (mIsBeingDragged) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
velocityTracker, mActivePointerId);
mPopulatePending = true;
final int width = getClientWidth();
final int scrollX = getScrollX();
final ItemInfo ii = infoForCurrentScrollPosition();
final int currentPage = ii.position;
final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;
final int activePointerIndex =
MotionEventCompat.findPointerIndex(ev, mActivePointerId);
final float x = MotionEventCompat.getX(ev, activePointerIndex);
final int totalDelta = (int) (x - mInitialMotionX);
//我的修改
if (Math.abs(initialVelocity) < 4000) {
int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
totalDelta);
setCurrentItemInternal(nextPage, true, true, initialVelocity);
} else {
fling = true;
// Log.e("Velocity", initialVelocity + "");
mScroller.fling(getScrollX(), getScrollY(), -initialVelocity, 0, minScrollX, maxScrollX, getScrollY(), getScrollY());
}
mActivePointerId = INVALID_POINTER;
endDrag();
needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
}
break;
case MotionEvent.ACTION_CANCEL:
if (mIsBeingDragged) {
scrollToItem(mCurItem, true, 0, false);
mActivePointerId = INVALID_POINTER;
endDrag();
needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
}
break;
case MotionEventCompat.ACTION_POINTER_DOWN: {
final int index = MotionEventCompat.getActionIndex(ev);
final float x = MotionEventCompat.getX(ev, index);
mLastMotionX = x;
mActivePointerId = MotionEventCompat.getPointerId(ev, index);
break;
}
case MotionEventCompat.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
mLastMotionX = MotionEventCompat.getX(ev,
MotionEventCompat.findPointerIndex(ev, mActivePointerId));
break;
}
if (needsInvalidate) {
ViewCompat.postInvalidateOnAnimation(this);
}
return true;
}
@Override
public void computeScroll() {
if (!myScroller.isFinished() && myScroller.computeScrollOffset()) {
int oldX = getScrollX();
int oldY = getScrollY();
int x = myScroller.getCurrX();
int y = myScroller.getCurrY();
if (oldX != x || oldY != y) {
scrollTo(x, y);
if (!pageScrolled(x)) {
myScroller.abortAnimation();
scrollTo(0, y);
}
}
// Keep on drawing until the animation has finished.
ViewCompat.postInvalidateOnAnimation(this);
return;
}
if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
int oldX = getScrollX();
int oldY = getScrollY();
int x = mScroller.getCurrX();
int y = mScroller.getCurrY();
if (oldX != x || oldY != y) {
scrollTo(x, y);
if (!pageScrolled(x)) {
mScroller.abortAnimation();
scrollTo(0, y);
}
}
// Keep on drawing until the animation has finished.
ViewCompat.postInvalidateOnAnimation(this);
return;
}
//我的修改
if (fling) {
ItemInfo curInfo = infoForCurrentScrollPosition();
if (getScrollX() > (curInfo.offset + 0.225) * getWidth()) {
if (curInfo.position + 1 < mAdapter.getCount()) {
curInfo = infoForPosition(curInfo.position + 1);
}
}
// final ItemInfo curInfo = infoForPosition(item);
int destX = 0;
if (curInfo != null) {
final int width = getClientWidth();
destX = (int) (width * Math.max(mFirstOffset,
Math.min(curInfo.offset, mLastOffset)));
}
if (curInfo.position != 0 && curInfo.position != mAdapter.getCount() - 2 && curInfo.position != mAdapter.getCount() - 1) {
destX = destX - itemScrollOffset;
}
// smoothScrollTo(destX, 0, 100);
mScroller.startScroll(getScrollX(), getScrollY(), destX - getScrollX(), 0, 600);
ViewCompat.postInvalidateOnAnimation(this);
dispatchOnPageSelected(curInfo.position);
fling = false;
return;
}
// scrollToItem(itemInfo.position, true, 100, true);
// setCurrentItemInternal(itemInfo.position, false, true, 100);
// setCurrentItem(itemInfo.position, true);
// fling = false;
// Done with scroll, clean up state.
completeScroll(true);
}