最近的需求中,需要用到一個(gè)橫向、豎向同時(shí)可滾動(dòng)的 ViewPager,記住,是橫向、豎向同時(shí)滾動(dòng),不是橫豎切換。我想了想,難點(diǎn)在于豎向。對(duì)于豎向的 ViewPager,我似乎記得 Jake Wharton 大神寫(xiě)過(guò)一個(gè)叫 DirectionalViewPager 的框架,它基本上算是在 ViewPager 源碼上改的,但效果欠佳且早已沒(méi)人維護(hù),所以不予采用。
過(guò)了幾秒,不知怎么的,腦子里突然閃現(xiàn)了一個(gè)想法:要使得 ViewPager 豎向滑動(dòng),把 Touch 事件交換一下不就得了,也就是將 MotionEvent 的 x 坐標(biāo)轉(zhuǎn)換成 y 坐標(biāo),將 y 坐標(biāo)轉(zhuǎn)換成 x 坐標(biāo)。這樣,從下往上滑就轉(zhuǎn)換成了從右往左滑。而從右往左滑時(shí), ViewPager 會(huì)切換到下一頁(yè)。此時(shí),只要給 ViewPager 設(shè)置一個(gè)豎向切換的 PageTransfromer,就成了一個(gè)豎向的 ViewPager 了。
我激動(dòng)了一小會(huì)兒,正躍躍欲試,但懶癌又犯了,心想,“我能想到,估計(jì)別人早想到了”,于是還是打算先在 GitHub 上找找,看有沒(méi)有基于同樣思路的代碼。果不其然,有個(gè)日本伙計(jì) 10 個(gè)月前就搞出來(lái)了,代碼很短(我做了精簡(jiǎn)),大家膜拜下吧:
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)));
}
}
嗯,實(shí)在是妙啊。不過(guò)還需要在外部設(shè)置一下 overScrollMode 和 PageTransfromer,以免看出破綻:
mVerticalViewPager.setPageTransformer(true, new VerticalTransformer());
mVerticalViewPager.setOverScrollMode(OVER_SCROLL_NEVER);
GitHub 地址 -> VerticalViewPager
雙向的 ViewPager
這里的雙向不是指一個(gè) ViewPager 既可以橫向切換,也可以豎向切換(如果你想要,把上面的代碼稍作修改即可),而是一個(gè)橫向的 ViewPager 里,每一頁(yè)都是一個(gè) VerticalViewPager,你可以理解為:外面的 ViewPager 用于切換分類(lèi),里面的 ViewPager 用于切換分類(lèi)中的 Item。
如果你簡(jiǎn)單的使用 ViewPager 嵌套 VerticalViewPager,實(shí)際的效果是,里面的 ViewPager 可豎向切換,但外面的 ViewPager 不能橫向切換。原因是里面的 ViewPager 將事件消耗掉了,即便里面的 ViewPager 沒(méi)有可橫向滾動(dòng)的控件(HorizontalScrollView、橫向 RecyclerView 等)。解決辦法是重寫(xiě)外面的 ViewPager 的 onInterceptTouchEvent 方法,如果檢測(cè)到橫向滾動(dòng),則將事件攔截。代碼如下:
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;
}
}
當(dāng)然,這段代碼只適用于里面的 ViewPager 不含可橫向滾動(dòng)的控件的情況,如果有,則處理起來(lái)就相對(duì)麻煩一些,大致的思路是在 onInterceptTouchEvent 里,先將 move 事件 dispatch 給當(dāng)前頁(yè)。再根據(jù) (!disallowIntercept && mTouchSlop && dx * 0.5f > dy) 決定是否攔截事件。有興趣的同學(xué)可以試一下。
最后,附上效果圖:

好了,就分享這些。
推廣:一鍵直達(dá)我的最新開(kāi)源項(xiàng)目 MagicIndicator。