30行代碼,打造一個(gè)垂直的ViewPager

最近的需求中,需要用到一個(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é)可以試一下。

最后,附上效果圖:

verticalviewpager.gif

好了,就分享這些。

推廣:一鍵直達(dá)我的最新開(kāi)源項(xiàng)目 MagicIndicator。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,922評(píng)論 25 709
  • 寫(xiě)作提綱: 1、表達(dá)酒店人脈的現(xiàn)狀。 2、表達(dá)本文核心觀點(diǎn)。 3、解釋核心觀點(diǎn)。 4、總結(jié) 酒店人的人脈現(xiàn)狀: X...
    陳熙慧閱讀 1,233評(píng)論 0 0
  • 臨春節(jié)近,倚著學(xué)生身份訂得火車(chē)票,旁觀眾人一票難求的境地。 在學(xué)校,生存成本相對(duì)低廉,不比在外上班奔忙的同學(xué)朋友。...
    woods閱讀 417評(píng)論 0 4

友情鏈接更多精彩內(nèi)容