效果图

最终效果

方案一 修改ViewPager

实践中发现许多较为关键的类、变量、方法都是私有的,无法进行操作,只好定义SuperViewPager继承自ViewGroup,复制ViewPager里面所有内容并进行修改

  • 1.定义两个自定义属性,pagerSpace代表两个卡片之间的距离,pageOerlayWidth代表卡片越界布局的宽度

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public SuperViewPager(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()方法

    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
    @Override
    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;
    // First pass - decor views. We need to do this in two passes so that
    // 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);
    }
    mFirstLayout = false;
    }

    先计算卡片宽度为:viewpager宽度-padding值-两边的pagerSpace-两边的pageOverlayWidth

    然后根据child计算左边坐标的偏移量

  • 3.修改scrollToItem(int item, boolean smoothScroll, int velocity,

    boolean dispatchSelected)方法
    
    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
    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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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)
multiviewpager截图

  • 使用方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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">
1
2
3
4
java代码需要调用setOffscreenPageLimit()方法
CardPagerAdapter pagerAdapter = new CardPagerAdapter(getFragmentManager(),cards);
card_pager.setAdapter(pagerAdapter);
card_pager.setOffscreenPageLimit(8);
  • 代码实现

自定义MultiViewPager继承自ViewPager,重写onMeasure()方法

@Override
    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