最近的需求中,需要用到一个横向、竖向同时可滚动的 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。
网友评论
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值的时候增加一个系数。
这个可真是秒不可言
我想请教一个问题,我在一个横向的viewpager里边嵌套一个纵向的viewpager,事件冲突问题怎么解决呢?真心求教。