水平垂直都可滑動的HorizontalVerticalViewPager實現

水平垂直都可滑動的HorizontalVerticalViewPager實現

自定義一個可以在垂直方向上滑動的ViewPager,同時支持水平和垂直方向手動切換和自動切換(根據手勢判斷),垂直方向的實現通過PageTransformer來完成,自動切換功能則涉及到手勢的相關內容。以下是內容簡介:

  • 垂直滑動的ViewPager實現思路

  • PageTransformer的使用

  • 手勢事件的分發和處理

垂直滑動的ViewPager實現思路

Android的兼容包support-v4提供的ViewPager默認是水平滑動切換的,但是,想要實現垂直滑動,可行的方案還是不少的,下面舉幾個例子:

  1. 直接拷貝ViewPager源碼,把所有涉及到的水平控制的參數,調整為垂直;
  2. 旋轉ViewPager的item的角度;
  3. 設置頁面切換動畫PageTransformer;

當然,還有很多其他的方式來實現,我這里使用的就是比較簡單的PageTransformer,簡單,是因為只需要給水平的ViewPager設置一個PageTransformer就能實現,不用自定義ViewPager。

PageTransformer的使用

關于PageTransformer首先要知道的是,這是一個控制ViewPager頁面滑動動畫的一個類,要給頁面設置各種滑入和滑出的動畫,需要實現PageTransformer接口,實現里面的transformPage(View page,int position)方法,做動畫相關的操作。

先來看看官方的Demo:

DepthPageTransformer

對應的代碼如下:

public class DepthPageTransformer implements ViewPager.PageTransformer {
    private static final float MIN_SCALE = 0.75f;

    public void transformPage(View view, float position) {
        int pageWidth = view.getWidth();

        if (position < -1) { // [-Infinity,-1)
            // This page is way off-screen to the left.
            view.setAlpha(0);

        } else if (position <= 0) { // [-1,0]
            // Use the default slide transition when moving to the left page
            view.setAlpha(1);
            view.setTranslationX(0);
            view.setScaleX(1);
            view.setScaleY(1);

        } else if (position <= 1) { // (0,1]
            // Fade the page out.
            view.setAlpha(1 - position);

            // Counteract the default slide transition
            view.setTranslationX(pageWidth * -position);

            // Scale the page down (between MIN_SCALE and 1)
            float scaleFactor = MIN_SCALE
                    + (1 - MIN_SCALE) * (1 - Math.abs(position));
            view.setScaleX(scaleFactor);
            view.setScaleY(scaleFactor);

        } else { // (1,+Infinity]
            // This page is way off-screen to the right.
            view.setAlpha(0);
        }
    }
}
ZoomOutPageTransformer

對應的PageTransformer實現:

public class ZoomOutPageTransformer implements ViewPager.PageTransformer {
    private static final float MIN_SCALE = 0.85f;
    private static final float MIN_ALPHA = 0.5f;

    public void transformPage(View view, float position) {
        int pageWidth = view.getWidth();
        int pageHeight = view.getHeight();

        if (position < -1) { // [-Infinity,-1)
            // This page is way off-screen to the left.
            view.setAlpha(0);

        } else if (position <= 1) { // [-1,1]
            // Modify the default slide transition to shrink the page as well
            float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));
            float vertMargin = pageHeight * (1 - scaleFactor) / 2;
            float horzMargin = pageWidth * (1 - scaleFactor) / 2;
            if (position < 0) {
                view.setTranslationX(horzMargin - vertMargin / 2);
            } else {
                view.setTranslationX(-horzMargin + vertMargin / 2);
            }

            // Scale the page down (between MIN_SCALE and 1)
            view.setScaleX(scaleFactor);
            view.setScaleY(scaleFactor);

            // Fade the page relative to its size.
            view.setAlpha(MIN_ALPHA +
                    (scaleFactor - MIN_SCALE) /
                    (1 - MIN_SCALE) * (1 - MIN_ALPHA));

        } else { // (1,+Infinity]
            // This page is way off-screen to the right.
            view.setAlpha(0);
        }
    }
}

關于Demo的具體解讀,參見官網示例。

下面是垂直滑動效果:

手指水平滑動-頁面垂直滾動

對應的PageTransformer實現:

private class VerticalPageTransformer implements ViewPager.PageTransformer {  
  
        @Override  
        public void transformPage(View view, float position) {  
  
            if (position < -1) { // [-Infinity,-1)  
                // This page is way off-screen to the left.  
                view.setAlpha(0);  
  
            } else if (position <= 1) { // [-1,1]  
                view.setAlpha(1);  
  
                // Counteract the default slide transition  
                view.setTranslationX(view.getWidth() * -position);  
  
                //set Y position to swipe in from top  
                float yPosition = position * view.getHeight();  
                view.setTranslationY(yPosition);  
  
            } else { // (1,+Infinity]  
                // This page is way off-screen to the right.  
                view.setAlpha(0);  
            }  
        }  
    } 

到這里其實就已經可以上下滑動了,但是手勢卻還是左右滑動,還需要再做一點改動。把左右滑動的手勢變換為上下滑動。

   /**
    * Swaps the X and Y coordinates of your touch event.
    */
    private MotionEvent swapXY(MotionEvent ev) {
        float width = getWidth();
        float height = getHeight();

        float newX = (ev.getY() / height) * width;
        float newY = (ev.getX() / width) * height;

        ev.setLocation(newX, newY);

        return ev;
    }


    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (isVertical) {
            boolean intercepted = super.onInterceptTouchEvent(swapXY(ev));
            swapXY(ev); // return touch coordinates to original reference frame for any child views
            return intercepted;
        } else {
            return super.onInterceptTouchEvent(ev);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (isVertical) {
            return super.onTouchEvent(swapXY(ev));
        } else {
            return super.onTouchEvent(ev);
        }
    }

效果如下:

手指垂直滑動-頁面垂直滾動

到這里,一個垂直滾動的ViewPager已經實現了。

記住,別忘了把實現的PageTransformer設置給ViewPager:

setPageTransformer(true, new HorizontalVerticalPageTransformer());

關于PageTransformer更多的內容,還可以看看CSDN博客專家們的解讀:

巧用ViewPager 打造不一樣的廣告輪播切換效果

PageTransformer實現個性的ViewPager切換動畫

手勢事件的分發和處理

現在,我要做的是,既可以水平滑動又可以垂直滑動,完全根據手指滑動的方向自動的切換滑動方向,通過按鈕手動切換的這里就不細說了,相信看完自動切換的細節,手動的也就不是什么問題了。

這里的手勢,監聽的是ViewPager的item里面的ImageView控件的,所以,實現一個OnGestureListener,然后,在ImageView的touch事件交給GestureDetector來處理,看看OnGestureListener的代碼:

private class GestureListener implements GestureDetector.OnGestureListener {

        @Override
        public boolean onDown(MotionEvent e) {
            Log.i(getClass().getName(), "onDown-----" + getActionName(e.getAction()));
            return false;
        }

        @Override
        public void onShowPress(MotionEvent e) {
            Log.i(getClass().getName(), "onShowPress-----" + getActionName(e.getAction()));
        }

        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            Log.i(getClass().getName(), "onSingleTapUp-----" + getActionName(e.getAction()));
            return false;
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            Log.i(getClass().getName(), "onScroll-----"
                    + getActionName(e2.getAction()) + ",(" + e1.getX() + "," + e1.getY() + ") ,("
                    + e2.getX() + "," + e2.getY() + ")");
            return false;
        }

        @Override
        public void onLongPress(MotionEvent e) {
            Log.i(getClass().getName(), "onLongPress-----" + getActionName(e.getAction()));
        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            Log.i(getClass().getName(),
                    "onFling-----" + getActionName(e2.getAction()) + ",(" + e1.getX() + "," + e1.getY() + ") ,("
                            + e2.getX() + "," + e2.getY() + ")");

            float detly_x = e1.getX() - e2.getX();
            float detly_y = e1.getY() - e2.getY();

            if (Math.abs(detly_x) > Math.abs(detly_y)) {// 水平方向滑動
                int offsetPosition = 0;
                if (e1.getX() - e2.getX() > FLING_MIN_DISTANCE) {
                    offsetPosition = 1;
                    Log.i(getClass().getName(), "onFling----- 向左滑動");
                } else if (e2.getX() - e1.getX() > FLING_MIN_DISTANCE) {
                    offsetPosition = -1;
                    Log.i(getClass().getName(), "onFling----- 向右滑動");
                }
                mOnTouchListener.onHorizontalFling(offsetPosition);
            } else {// 垂直方向滑動
                int offsetPosition = 0;
                if (e1.getY() - e2.getY() > FLING_MIN_DISTANCE) {
                    offsetPosition = 1;
                    Log.i(getClass().getName(), "onFling----- 向上滑動");
                } else if (e2.getY() - e1.getY() > FLING_MIN_DISTANCE) {
                    offsetPosition = -1;
                    Log.i(getClass().getName(), "onFling----- 向下滑動");
                }
                mOnTouchListener.onVerticalFling(offsetPosition);
            }

            return false;
        }

        private String getActionName(int action) {
            String name = "";
            switch (action) {
                case MotionEvent.ACTION_DOWN: {
                    name = "ACTION_DOWN";
                    break;
                }
                case MotionEvent.ACTION_MOVE: {
                    name = "ACTION_MOVE";
                    break;
                }
                case MotionEvent.ACTION_UP: {
                    name = "ACTION_UP";
                    break;
                }
                default:
                    break;
            }
            return name;
        }
    }

設置給ImageView:

final GestureDetector detector = new GestureDetector(context, new GestureListener());
        iv.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                detector.onTouchEvent(event);
                return true;
            }
        });

當然,PageTransformer也要進行改造:

private class HorizontalVerticalPageTransformer implements PageTransformer {

        private static final float MIN_SCALE = 0.75f;

        @Override
        public void transformPage(View page, float position) {
            if (isVertical) {

                if (position < -1) {
                    page.setAlpha(0);
                } else if (position <= 1) {
                    page.setAlpha(1);

                    // Counteract the default slide transition
                    float xPosition = page.getWidth() * -position;
                    page.setTranslationX(xPosition);

                    //set Y position to swipe in from top
                    float yPosition = position * page.getHeight();
                    page.setTranslationY(yPosition);
                } else {
                    page.setAlpha(0);
                }
            } else {
                int pageWidth = page.getWidth();

                if (position < -1) { // [-Infinity,-1)
                    // This page is way off-screen to the left.
                    page.setAlpha(0);

                } else if (position <= 0) { // [-1,0]
                    // Use the default slide transition when moving to the left page
                    page.setAlpha(1);
                    page.setTranslationX(0);
                    page.setScaleX(1);
                    page.setScaleY(1);

                } else if (position <= 1) { // (0,1]
                    // Fade the page out.
                    page.setAlpha(1 - position);

                    // Counteract the default slide transition
                    page.setTranslationX(pageWidth * -position);
                    page.setTranslationY(0);

                    // Scale the page down (between MIN_SCALE and 1)
                    float scaleFactor = MIN_SCALE
                            + (1 - MIN_SCALE) * (1 - Math.abs(position));
                    page.setScaleX(scaleFactor);
                    page.setScaleY(scaleFactor);

                } else { // (1,+Infinity]
                    // This page is way off-screen to the right.
                    page.setAlpha(0);
                }
            }
        }
    }

再看看效果是怎么樣的吧:

雙向自動切換

只是一個簡單的Demo,還有一些瑕疵,有空再整理,這里暫時奉上不完善的源碼:

源碼地址

運行后,點擊主頁右下角FloatingActionButton即可。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370

推薦閱讀更多精彩內容

  • 內容抽屜菜單ListViewWebViewSwitchButton按鈕點贊按鈕進度條TabLayout圖標下拉刷新...
    皇小弟閱讀 46,838評論 22 665
  • 走呀走,走呀走 迷路的蜘蛛依然尋不著歸家的路 滴答滴,滴答滴 珍藏的秘密已經被雨水帶去無蹤 今夜的河流寂靜一片 莊...
    知諸閱讀 246評論 0 1
  • 前面我們提到的關于石頭街的賺錢技巧,更多的關于石頭街本地風貌,這里我們就不用介紹了。我們接著談談大阿伯的弟弟。
    時以芩閱讀 160評論 0 0
  • 今天,我剛剛醒來,發現其他人都沒醒!我看了一會兒書,等待其他人醒來。過了一會兒其他人被鬧鐘吵醒了,胡鄭豪還迷迷糊糊...
    candy2008閱讀 690評論 0 0
  • 圖片發自簡書App月光破窗而入 周童 凌晨三點 月亮破窗而入 一如破窗而入的你 一個撒了窗下一地的青輝 疑為初雪降...
    周童的小屋閱讀 369評論 5 2