效果图

方案一 修改ViewPager
实践中发现许多较为关键的类、变量、方法都是私有的,无法进行操作,只好定义SuperViewPager继承自ViewGroup,复制ViewPager里面所有内容并进行修改
1.定义两个自定义属性,pagerSpace代表两个卡片之间的距离,pageOerlayWidth代表卡片越界布局的宽度
12345678910
public (Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SuperPager); pagerSpace = a.getDimensionPixelSize(R.styleable.SuperPager_sp_pagerSpace, (int) dp2px(20)); pageOerlayWidth = a.getDimensionPixelSize(R.styleable.SuperPager_sp_pageOerlayWidth, (int) dp2px(12)); a.recycle(); initViewPager(); }
2.修改onLayout()方法
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
protected void onLayout(boolean changed, int l, int t, int r, int b) { final int count = getChildCount(); int width = r - l; int height = b - t; int paddingLeft = getPaddingLeft(); int paddingTop = getPaddingTop(); int paddingRight = getPaddingRight(); int paddingBottom = getPaddingBottom(); final int scrollX = getScrollX(); int decorCount = 0; // we have the proper offsets for non-decor views later. for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); int childLeft = 0; int childTop = 0; if (lp.isDecor) { final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK; switch (hgrav) { default: childLeft = paddingLeft; break; case Gravity.LEFT: childLeft = paddingLeft; paddingLeft += child.getMeasuredWidth(); break; case Gravity.CENTER_HORIZONTAL: childLeft = Math.max((width - child.getMeasuredWidth()) / 2, paddingLeft); break; case Gravity.RIGHT: childLeft = width - paddingRight - child.getMeasuredWidth(); paddingRight += child.getMeasuredWidth(); break; } switch (vgrav) { default: childTop = paddingTop; break; case Gravity.TOP: childTop = paddingTop; paddingTop += child.getMeasuredHeight(); break; case Gravity.CENTER_VERTICAL: childTop = Math.max((height - child.getMeasuredHeight()) / 2, paddingTop); break; case Gravity.BOTTOM: childTop = height - paddingBottom - child.getMeasuredHeight(); paddingBottom += child.getMeasuredHeight(); break; } childLeft += scrollX; child.layout(childLeft, childTop, childLeft + child.getMeasuredWidth(), childTop + child.getMeasuredHeight()); decorCount++; } } } //以下为关键代码 int pageWidth = width - paddingLeft - paddingRight - 2 * pagerSpace - 2 * pageOerlayWidth; // Page views. Do this once we have the right padding offsets from above. for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); ItemInfo ii; if (!lp.isDecor && (ii = infoForChild(child)) != null) { int loff = (int) ((pageWidth+pagerSpace) * ii.offset); int childLeft = paddingLeft + pageOerlayWidth + pagerSpace + loff; int childTop = paddingTop; child.layout(childLeft, childTop, childLeft + pageWidth, childTop + child.getMeasuredHeight()); } } } mTopPageBounds = paddingTop; mBottomPageBounds = height - paddingBottom; mDecorChildCount = decorCount; if (mFirstLayout) { scrollToItem(mCurItem, false, 0, false);大专栏 自定义不占满全屏可左右滑动的卡片v> } mFirstLayout = false; }
先计算卡片宽度为:viewpager宽度-padding值-两边的pagerSpace-两边的pageOverlayWidth
然后根据child计算左边坐标的偏移量
3.修改scrollToItem(int item, boolean smoothScroll, int velocity,
boolean dispatchSelected)方法
123456789101112131415161718192021222324252627282930313233
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 - 2 * pageOerlayWidth - pagerSpace) * Math.max(mFirstOffset, Math.min(curInfo.offset, mLastOffset))); } if (smoothScroll) { smoothScrollTo(destX, 0, velocity); if (dispatchSelected) { dispatchOnPageSelected(item); } } else { if (dispatchSelected) { dispatchOnPageSelected(item); } completeScroll(false); scrollTo(destX, 0); pageScrolled(destX); } } ``` 修正滚动x坐标的计算,确保滚动后卡片能位于中间- 4.修改滚动动效为回弹效果 在initViewPager()方法中修改 ```java mScroller = new Scroller(context, new OvershootInterpolator(1.5F));
注,ViewPager外部可通过映射的方法访问私有成员mScroller
123456789101112
private void setViewPagerScrollSpeed(SuperViewPager viewPager) { try { Field field = ViewPager.class.getDeclaredField("mScroller"); field.setAccessible(true); Scroller viewPagerScroller = new Scroller(viewPager.getContext(), new OvershootInterpolator(1.5F)); field.set(viewPager, viewPagerScroller); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } }
5.现存bug
页面滚动较困难,需修改touch事件
方案二 自定义ViewPager
上一个方案主要修改onLayout()方法,该方法主要修改onMeasure()方法,为了及时测量Page宽度,要求必须设置matchChildWidth属性(传入需测量的布局id)

- 使用方法
1234567891011121314 | viewpager布局<com.lcodecore.openlib.MultiViewPager android:id="@+id/card_pager" android:layout_width="match_parent" android:layout_height="match_parent" app:matchChildWidth="@+id/vg_cover"/>item布局<RelativeLayout android:id="@+id/vg_cover" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginLeft="40dp" android:layout_marginRight="40dp"> |
1234 | java代码需要调用setOffscreenPageLimit()方法CardPagerAdapter pagerAdapter = new CardPagerAdapter(getFragmentManager(),cards);card_pager.setAdapter(pagerAdapter);card_pager.setOffscreenPageLimit(8); |
- 代码实现
自定义MultiViewPager继承自ViewPager,重写onMeasure()方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //记录测量的宽高 size.set(MeasureSpec.getSize(widthMeasureSpec),MeasureSpec.getSize(heightMeasureSpec)); if (mMaxWidth >= 0 || mMaxHeight >= 0) { //自定义属性最大宽高 maxSize.set(mMaxWidth, mMaxHeight); //比较 constrainTo(size, maxSize); widthMeasureSpec = MeasureSpec.makeMeasureSpec( size.x, MeasureSpec.EXACTLY); heightMeasureSpec = MeasureSpec.makeMeasureSpec( size.y, MeasureSpec.EXACTLY); } super.onMeasure(widthMeasureSpec, heightMeasureSpec); onMeasurePage(widthMeasureSpec, heightMeasureSpec); } protected void onMeasurePage(int widthMeasureSpec, int heightMeasureSpec) { // Only measure if a measurement pass was scheduled if (!mNeedsMeasurePage) { return; } if (mMatchWidthChildResId == 0) { mNeedsMeasurePage = false; } else if (getChildCount() > 0) { View child = getChildAt(0); child.measure(widthMeasureSpec, heightMeasureSpec); int pageWidth = child.getMeasuredWidth(); View match = child.findViewById(mMatchWidthChildResId); if (match == null) { throw new NullPointerException( "MatchWithChildResId did not find that ID in the first fragment of the ViewPager; " + "is that view defined in the child view's layout? Note that MultiViewPager " + "only measures the child for index 0."); } int childWidth = match.getMeasuredWidth(); // Check that the measurement was successful if (childWidth > 0) { mNeedsMeasurePage = false; int difference = pageWidth - childWidth; setPageMargin(-difference); int offscreen = (int) Math.ceil((float) pageWidth / (float) childWidth) + 1; setOffscreenPageLimit(offscreen); requestLayout(); } } }
方案三 修改RecyclerView
TODO