将“跳出效果”应用于ViewPager中的Overscroll(FragmentPagerAdapter)

时间:2015-06-18 01:01:58

标签: android android-layout android-fragments android-viewpager android-overscoll

在我的应用程序中,我试图实现过度滚动的反弹效果,而不是机器人无聊的正常过度滚动技术。

我正在尝试将此过卷效果应用于处理相当多片段的FragmentPagerAdapter。我该如何实现呢?

如果有帮助,这是我当前的FragmentPagerAdapter:

public class SectionsPagerAdapter extends FragmentPagerAdapter {

private MainActivity mainActivity;

public SectionsPagerAdapter(MainActivity mainActivity, FragmentManager fm) {
    super(fm);
    this.mainActivity = mainActivity;
}

@Override
public Fragment getItem(int position) {
    switch (position) {
        case 0:
            return new FragmentPages.FragmentPage1();
        case 1:
            return new FragmentPages.FragmentPage2();
        case 2:
            return new FragmentPages.FragmentPage3();
        case 3:
            return new FragmentPages.FragmentPage4();
        case 4:
            return new FragmentPages.FragmentPage5();
        case 5:
            return new FragmentPages.FragmentPage6();
        case 6:
            return new FragmentPages.FragmentPage7();
        case 7:
            return new FragmentPages.FragmentPage8();
        case 8:
            return new FragmentPages.FragmentPage9();
        case 9:
            return new FragmentPages.FragmentPage10();
        case 10:
            return new FragmentPages.FragmentPage11();
    }
    return null;
}

@Override
public int getCount() {
    return 11;
}
}

1 个答案:

答案 0 :(得分:0)

这里是我项目的样本。大部分代码来自answer

public class BounceViewPager extends ViewPager {
    private final static int LONG_ANIM_DURATION = 850;
    private final static int SHORT_ANIM_DURATION = 150;
    private final static int DEFAULT_OVERSCROLL_ANIMATION_DURATION = 200;

    private GestureDetector xScrollDetector;
    private FixedSpeedScroller scroller;

    class XScrollDetector extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            return Math.abs(distanceX) > Math.abs(distanceY);
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        try {
            final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
            switch (action) {
                case MotionEvent.ACTION_DOWN: {
                    lastMotionX = ev.getX();
                    activePointerId = MotionEventCompat.getPointerId(ev, 0);
                    break;
                }
                case MotionEventCompat.ACTION_POINTER_DOWN: {
                    final int index = MotionEventCompat.getActionIndex(ev);
                    lastMotionX = MotionEventCompat.getX(ev, index);
                    activePointerId = MotionEventCompat.getPointerId(ev, index);
                    break;
                }
            }

            if (xScrollDetector.onTouchEvent(ev)) {
                super.onInterceptTouchEvent(ev);
                return true;
            }

            return super.onInterceptTouchEvent(ev);
        } catch (IllegalArgumentException | ArrayIndexOutOfBoundsException e) {
            Timber.e(e, "");
            return false;
        }
    }

    private final static int INVALID_POINTER_ID = -1;

    private class OverScrollEffect {
        private float overScroll;
        private Animator animator;

        public void setPull(final float deltaDistance) {
            overScroll = deltaDistance;
            invalidateVisibleChildren();
        }

        private void onRelease() {
            if (animator != null && animator.isRunning()) {
                animator.addListener(new Animator.AnimatorListener() {

                    @Override
                    public void onAnimationStart(Animator animation) {
                    }

                    @Override
                    public void onAnimationRepeat(Animator animation) {
                    }

                    @Override
                    public void onAnimationEnd(Animator animation) {
                        startAnimation();
                    }

                    @Override
                    public void onAnimationCancel(Animator animation) {
                    }
                });
                animator.cancel();
            } else {
                startAnimation();
            }
        }

        private void startAnimation() {
            animator = ObjectAnimator.ofFloat(this, "pull", overScroll, 0);
            animator.setInterpolator(new DecelerateInterpolator());
            final float scale = Math.abs(-overScroll);
            animator.setDuration((long) (overScrollAnimationDuration * scale));
            animator.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {
                }

                @Override
                public void onAnimationEnd(Animator animation) {
                }

                @Override
                public void onAnimationCancel(Animator animation) {
                }

                @Override
                public void onAnimationRepeat(Animator animation) {
                }
            });

            animator.start();
        }

        private boolean isOverScrolling() {
            if (scrollPosition == 0 && overScroll < 0) {
                return true;
            }
            final boolean isLast = (getAdapter().getCount() - 1) == getCurrentItem();
            return isLast && overScroll > 0;
        }

    }

    final private OverScrollEffect overscrollEffect = new OverScrollEffect();
    final private Camera camera = new Camera();

    private float lastMotionX;
    private int activePointerId;
    private int scrollPosition;
    private float scrollPositionOffset;
    final private int touchSlop;

    private float overScrollTranslation;
    private int overScrollAnimationDuration;

    public BounceViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
        xScrollDetector = new GestureDetector(getContext(), new XScrollDetector());

        setStaticTransformationsEnabled(true);
        final ViewConfiguration configuration = ViewConfiguration.get(context);
        touchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
        super.setOnPageChangeListener(new MyOnPageChangeListener());
        init();
    }

    private void init() {
        overScrollTranslation = UiUtil.getDisplayWidth((Activity) getContext()) / 3;
        overScrollAnimationDuration = DEFAULT_OVERSCROLL_ANIMATION_DURATION;

        try {
            Field mScroller;
            mScroller = ViewPager.class.getDeclaredField("mScroller");
            mScroller.setAccessible(true);
            scroller = new FixedSpeedScroller(getContext());
            setShortAnimDuration();
            mScroller.set(this, scroller);
        } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
            Timber.e(e, "");
        }
    }

    private void invalidateVisibleChildren() {
        for (int i = 0; i < getChildCount(); i++) {
            getChildAt(i).invalidate();
        }
    }

    private class MyOnPageChangeListener implements OnPageChangeListener {

        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            scrollPosition = position;
            scrollPositionOffset = positionOffset;
            invalidateVisibleChildren();
        }

        @Override
        public void onPageSelected(int position) {
        }

        @Override
        public void onPageScrollStateChanged(final int state) {
            if (state == SCROLL_STATE_IDLE) {
                scrollPositionOffset = 0;
            }
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        boolean callSuper = false;

        final int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                callSuper = true;
                lastMotionX = ev.getX();
                activePointerId = MotionEventCompat.getPointerId(ev, 0);
                break;
            }
            case MotionEventCompat.ACTION_POINTER_DOWN: {
                callSuper = true;
                final int index = MotionEventCompat.getActionIndex(ev);
                lastMotionX = MotionEventCompat.getX(ev, index);
                activePointerId = MotionEventCompat.getPointerId(ev, index);
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                if (activePointerId != INVALID_POINTER_ID) {
                    final int activePointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
                    final float x = MotionEventCompat.getX(ev, activePointerIndex);
                    final float deltaX = lastMotionX - x;
                    final float oldScrollX = getScrollX();
                    final int width = getWidth();
                    final int widthWithMargin = width + getPageMargin() - (getPaddingLeft() + getPaddingRight());
                    final int lastItemIndex = getAdapter().getCount() - 1;
                    final int currentItemIndex = getCurrentItem();
                    final float leftBound = Math.max(0, (currentItemIndex - 1) * widthWithMargin);
                    final float rightBound = (Math.min(currentItemIndex + 1, lastItemIndex)) * (widthWithMargin);
                    final float scrollX = oldScrollX + deltaX;
                    if (scrollPositionOffset == 0) {
                        if (scrollX < leftBound) {
                            if (leftBound == 0) {
                                final float over = deltaX + touchSlop;
                                overscrollEffect.setPull(over / width);
                            }
                        } else if (scrollX > rightBound) {
                            if (rightBound == lastItemIndex * widthWithMargin) {
                                final float over = scrollX - rightBound - touchSlop;
                                overscrollEffect.setPull(over / width);
                            }
                        }
                    } else {
                        lastMotionX = x;
                    }
                } else {
                    overscrollEffect.onRelease();
                }
                break;
            }
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL: {
                callSuper = true;
                activePointerId = INVALID_POINTER_ID;
                overscrollEffect.onRelease();
                break;
            }
            case MotionEvent.ACTION_POINTER_UP: {
                final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
                        >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
                final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
                if (pointerId == activePointerId) {
                    final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
                    lastMotionX = ev.getX(newPointerIndex);
                    activePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
                    callSuper = true;
                }
                break;
            }
        }

        return overscrollEffect.isOverScrolling() && !callSuper || super.onTouchEvent(ev);
    }

    @Override
    protected boolean getChildStaticTransformation(View child, Transformation t) {
        if (child.getWidth() == 0) {
            return false;
        }
        final int position = child.getLeft() / child.getWidth();
        final boolean isFirstOrLast = position == 0 || (position == getAdapter().getCount() - 1);
        if (overscrollEffect.isOverScrolling() && isFirstOrLast) {
            t.clear();
            t.setTransformationType(Transformation.TYPE_MATRIX);

            final float dx = getWidth() / 2;
            final int dy = getHeight() / 2;
            t.getMatrix().reset();
            final float translateX = overScrollTranslation * (overscrollEffect.overScroll > 0 ?
                    Math.min(overscrollEffect.overScroll, 1) : Math.max(overscrollEffect.overScroll, -1));
            camera.save();
            camera.translate(-translateX, 0, 0);
            camera.getMatrix(t.getMatrix());
            t.getMatrix().preTranslate(-dx, -dy);
            t.getMatrix().postTranslate(dx, dy);
            camera.restore();

            this.invalidate();
            return true;
        }
        return false;
    }

    private void setAnimationDuration(int duration) {
        scroller.setDuration(duration);
    }

    public void setLongAnimDuration() {
        setAnimationDuration(LONG_ANIM_DURATION);
    }

    public void setShortAnimDuration() {
        setAnimationDuration(SHORT_ANIM_DURATION);
    }
}

public class FixedSpeedScroller extends Scroller {
    private int duration = 150;

    public FixedSpeedScroller(Context context) {
        super(context, new LinearOutSlowInInterpolator());
    }

    @Override
    public void startScroll(int startX, int startY, int dx, int dy, int duration) {
        super.startScroll(startX, startY, dx, dy, this.duration);
    }

    @Override
    public void startScroll(int startX, int startY, int dx, int dy) {
        super.startScroll(startX, startY, dx, dy, duration);
    }

    public void setDuration(int duration) {
        this.duration = duration;
    }
}