30行代码,打造一个垂直的ViewPager

96
作者 hackware
2016.08.11 01:05* 字数 956

最近的需求中,需要用到一个横向、竖向同时可滚动的 ViewPager,记住,是横向、竖向同时滚动,不是横竖切换。我想了想,难点在于竖向。对于竖向的 ViewPager,我似乎记得 Jake Wharton 大神写过一个叫 DirectionalViewPager 的框架,它基本上算是在 ViewPager 源码上改的,但效果欠佳且早已没人维护,所以不予采用。

过了几秒,不知怎么的,脑子里突然闪现了一个想法:要使得 ViewPager 竖向滑动,把 Touch 事件交换一下不就得了,也就是将 MotionEvent 的 x 坐标转换成 y 坐标,将 y 坐标转换成 x 坐标。这样,从下往上滑就转换成了从右往左滑。而从右往左滑时, ViewPager 会切换到下一页。此时,只要给 ViewPager 设置一个竖向切换的 PageTransfromer,就成了一个竖向的 ViewPager 了。

我激动了一小会儿,正跃跃欲试,但懒癌又犯了,心想,“我能想到,估计别人早想到了”,于是还是打算先在 GitHub 上找找,看有没有基于同样思路的代码。果不其然,有个日本伙计 10 个月前就搞出来了,代码很短(我做了精简),大家膜拜下吧:

public class VerticalViewPager extends ViewPager {

    public VerticalViewPager(Context context) {
        super(context);
    }

    public VerticalViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    private MotionEvent swapTouchEvent(MotionEvent event) {
        float width = getWidth();
        float height = getHeight();
        event.setLocation((event.getY() / height) * width, (event.getX() / width) * height);
        return event;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        return super.onInterceptTouchEvent(swapTouchEvent(MotionEvent.obtain(event)));
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return super.onTouchEvent(swapTouchEvent(MotionEvent.obtain(ev)));
    }
}

嗯,实在是妙啊。不过还需要在外部设置一下 overScrollMode 和 PageTransfromer,以免看出破绽:

mVerticalViewPager.setPageTransformer(true, new VerticalTransformer());
mVerticalViewPager.setOverScrollMode(OVER_SCROLL_NEVER);

GitHub 地址 -> VerticalViewPager

双向的 ViewPager


这里的双向不是指一个 ViewPager 既可以横向切换,也可以竖向切换(如果你想要,把上面的代码稍作修改即可),而是一个横向的 ViewPager 里,每一页都是一个 VerticalViewPager,你可以理解为:外面的 ViewPager 用于切换分类,里面的 ViewPager 用于切换分类中的 Item。

如果你简单的使用 ViewPager 嵌套 VerticalViewPager,实际的效果是,里面的 ViewPager 可竖向切换,但外面的 ViewPager 不能横向切换。原因是里面的 ViewPager 将事件消耗掉了,即便里面的 ViewPager 没有可横向滚动的控件(HorizontalScrollView、横向 RecyclerView 等)。解决办法是重写外面的 ViewPager 的 onInterceptTouchEvent 方法,如果检测到横向滚动,则将事件拦截。代码如下:

public class HorizontalViewPager extends ViewPager {
    private float mDownX;
    private float mDownY;
    private float mTouchSlop;

    public HorizontalViewPager(Context context) {
        super(context);
        init(context);
    }

    public HorizontalViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    private void init(Context context) {
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        boolean intercept = super.onInterceptTouchEvent(event);
        float x = event.getX();
        float y = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mDownX = x;
                mDownY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                float dx = Math.abs(x - mDownX);
                float dy = Math.abs(y - mDownY);
                if (!intercept && dx > mTouchSlop && dx * 0.5f > dy) {
                    intercept = true;
                }
                break;
            default:
                break;
        }
        return intercept;
    }
}

当然,这段代码只适用于里面的 ViewPager 不含可横向滚动的控件的情况,如果有,则处理起来就相对麻烦一些,大致的思路是在 onInterceptTouchEvent 里,先将 move 事件 dispatch 给当前页。再根据 (!disallowIntercept && mTouchSlop && dx * 0.5f > dy) 决定是否拦截事件。有兴趣的同学可以试一下。

最后,附上效果图:


verticalviewpager.gif

好了,就分享这些。

推广:一键直达我的最新开源项目 MagicIndicator

Android