美文网首页安卓知识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:
  • 1a92fc325558:手势不是可以达到这个效果吗?
    hackware:@zorozhaochonggm 要做这种效果,方式多多,要衡量代价

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

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