美文网首页安卓知识Android自定义控件Android
30行代码,打造一个垂直的ViewPager

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

作者: hackware | 来源:发表于2016-08-11 01:05 被阅读7912次

    最近的需求中,需要用到一个横向、竖向同时可滚动的 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

    相关文章

      网友评论

      • 水蜜桃口味的你:这么做有一个BUG, 会放大y值的移动,ViewPager在事件拦截的时候会同时判断x和y值的移动,源码如下:
        if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) {
        ...
        mIsBeingDragged = true;
        ...
        } else if (yDiff > mTouchSlop) { // 在拦截之间dy大于了mTouchSlop,会设置unableToDrag标志使得系列事件无法拦截
        ...
        mIsUnableToDrag = true;
        }

        所以这么做很容易出现滑不动的BUG,可以通过修改y值修改:

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

        在给MotionEvent设置y值的时候增加一个系数。
        double_so3:很到位
      • 菜鸟程序员_:event.setLocation((event.getY() / height) * width, (event.getX() / width) * height);
        这个可真是秒不可言
      • 神经病人思路广:你这个是手势啊,我还以为是竖向的切换
      • The_Ashes:写的非常好!
        我想请教一个问题,我在一个横向的viewpager里边嵌套一个纵向的viewpager,事件冲突问题怎么解决呢?真心求教。
        The_Ashes:解决了,我把轴y轴的滑动距离弄反了,怪不得得不到我想要的效果。
      • 汪简书:这种需求让我想起直播房间的切换
      • 岁月留痕:大神 收下我的膝盖
      • 悟饭的夏天:我是做的TV应用, 焦点在右边,再按右会触发向下滚动, 期望是焦点最下,按下会触发向下滚动.
      • Z__K:VerticalViewPager的item里面是ScrollView的布局呢?
      • 69c600613d1b:原来是你!这几天邮箱收到不少你的邮件!果然是大神
        hackware: @小菜包子 你肯定是watch了
        69c600613d1b: @hackware 额,看错了,那个是下啦刷新的框架。你的只收到一封,9.13号更新的,我为什么会受到你的邮件啊,是我转发了你的框架吗,在github上。
        hackware:@小菜包子 :grin: 什么邮件,谁发的?
      • Super简单:你的双向没有分享 项目///
      • 捡淑:66666
        963274b5be88:怎么哪都有你呢
        rico1412:你这头像:flushed:
      • zorozhaochonggm:手势不是可以达到这个效果吗?
        hackware:@zorozhaochonggm 要做这种效果,方式多多,要衡量代价

      本文标题:30行代码,打造一个垂直的ViewPager

      本文链接:https://www.haomeiwen.com/subject/kibjsttx.html