1. 程式人生 > >Android實現真正的ViewPager【平滑過渡】+【迴圈滾動】!!!順帶還有【末頁跳轉】。

Android實現真正的ViewPager【平滑過渡】+【迴圈滾動】!!!順帶還有【末頁跳轉】。

實現真正的ViewPager【平滑過渡】+【迴圈滾動】!!!順帶還有【末頁跳轉】。

首先呢, 我要對網上常見的3種ViewPager的迴圈滾動方法做個概述。急需看真正實現方法的同志請選擇性忽略下面這一長段。不過有時間精力的話還是看看,尤其後兩者,我的方法是基於這兩者的:

  1. 不講道理式。特點:不管3721,先在PagerAdapter的getCount();裡返回一個很大的值。保守的返回個100,極端的直接Integer.MAX_VALUE。看到這裡估計就已經有很多孩子笑了,這尼瑪跟大學C語言課本上,陣列長度未知就定義個1000單位的思想簡直如出一轍啊有木有!!!驚恐好吧,那麼這種方法顯然並沒有真的解決問題,雖然不會有幾個使用者真有耐心去滑動Integer.MAX_VALUE / 2次。。。不過鑑於目前網上其它方法都有這樣那樣的缺陷,還是有不少人寧願選擇這個的。那麼我就說詳細點吧。這個方法後續操作中,比較關鍵的步驟有兩點:                

    一、一定要設定初始位置為Integer.MAX_VALUE / 2,

    setCurrentItem(Integer.MAX_VALUE / 2);
    
    

    ,這樣才能保證一開始就能雙向滑動。                二、要在自定義的PagerAdapter的instantiateItem(ViewGroup arg0, int arg1);【較早的Api裡為View arg0,已廢棄】方法裡返回當前位置對總頁面數的模。
    views.get(arg1 % views.size());

  2. 邏輯大神式。特點:試圖通過OnPageChangeListener介面的onPageScrolled();、onPageSelected();、onPageScrollStateChanged();三個方法,來判別出當前是否滑動到了首/末位置,且使用者是否仍在向後/前滑動,然後setCurrentItem();。這種方法其實是比較負責任的,但十分辛苦,且到現在也沒能發現一套比較合適的判別標準。不過不代表沒有,有志於此的同志請繼續努力奮鬥

    這裡列出我個人在大量嘗試後總結出的需要注意的幾點供有志者參考:                一、

    onPageScrolled(int position, float positionOffset, int positionOffsetPixels);

    此方法的幾個引數十分難以把握!!!具體規則我這麼描述:position為當前螢幕上所露出的所有View的Item取下限。比如,當前Item為3,輕輕向右滑動一下,2露出了一點點,那麼position就是2,這點很重要,是我的末頁跳轉方法的基礎!;而如果向左滑動,露出的4比3大,那麼只要3沒完全隱匿,那麼position就一直按照3算。positionOffset是當前Item較大的那個View佔檢視的百分比
    ,0-1,沒有負數!當滑動結束時,onPageScrolled();最後一次呼叫,positionOffset為0。是的,這個方法的呼叫是連續的,也就是說,使用者一次滑動,這個方法會持續呼叫N多次,直至某個View充滿檢視並且穩定住!(但具體呼叫次數也不確定,尤其在首末位置向邊界滑動,如果Log一下,會看到出現呼叫不確定次數的列印,且positionOffset都為0.              二、
    onPageSelected(int position);

    更噁心。。。這個方法是在onPageScrolled();的一系列呼叫中間插入進去的,而不是常識的在其結束並穩定在某個檢視後!大致規律是positionOffset向左向右越過某個值左右時觸發,這個值不總是0.5,它與使用者的滑動速度、拖甩手勢等相關,應該是有一套計算公式的!而一旦這個方法觸發,意味著馬上就要進入position位置的view了(而不是已經),且這一動作不可被撤銷,所以看起來好像是在進入後才呼叫的。                三、
    onPageScrollStateChanged(int state);

    這個方法是唯一比較正常的。在一次滑動中,其狀態值及改變順序為:1-開始滑動、2-越過觸發onPageSelected();的值、0-滑動結束(呃,其實我也記不清了,反正這個方法對我沒啥用。。。)
  3. 接近成功式。是的,這個方法行得通!只是犧牲了一些細節,過渡不太平滑,雖然它看起來可以。這個方法的思想是設定標誌位置。設原本的View切換順序:0->1->2->3,那麼現在,在第一個位置之前加插入3,最後一個位置之後插入0,得到3->0->1->2->3->0,然後如果滑動到了第一個3,則跳轉到第二個3,滑動到第二個0,則轉到第一個0。  

    看程式碼:

    (OnPageChangeListener)
        @Override
        public void onPageSelected(int position) {
            // TODO Auto-generated method stub
    
            if (position == 0) viewPager.setCurrentItem(views.size() - 2);
    
            else if (position == views.size() - 1) viewPager.setCurrentItem(0);
    
        }


    是的,我彷彿聽到了恍然大悟的聲音。這個方法的確巧妙,已經無限接近成功。但可惜的是,這裡面有些注意事項,網上那個發明這方法的人並未列出(網上太亂了,真心不知道原作者是誰,不過也得感謝一下),使得一些情況下莫名其妙的失敗了。更主要的是,這個方法並不能平滑過渡,雖然它看起來好像可以,但你會發現首位連線處丟了半個週期的滑動。但這並不是不可逆轉的,我的方法就是對該方法的一種修正。

好,現在是正文(前戲有點長了啊。。。)

=========================================================

=======================華麗的分割線==========================

=========================================================

  1. 先上盤小菜:【末頁跳轉】

    其實許多的程式引導頁做到這點,就是不知道方法是否和我的一樣。先看程式碼:

    (OnPageChangeListener)
        boolean isEnd;
        @Override
        onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            // TODO Auto-generated method stub
            if (isEnd&&position== views.size() - 1) finish();
            else isEnd = (position== views.size() - 1);
        }


    就是這短短几行程式碼。具體什麼意思呢?首先是一個布林變數,記錄當前是否滑動到了末位值。滑動到末位值的特徵是什麼呢?就是position為views.size() - 1。那麼再看if裡面的條件:如果上次滑動到了末位值,並且position== views.size() - 1,這個並且後面的是怎麼回事呢?如果滑動到了末位值再往回滑動豈不是也會跳轉?如果你看了之前對網上方法的分析第二條,你可能就明白了,是的,position並不是當前view的Item,也不是當前佔據比例較大的Item,它只是當前檢視中說顯示的view裡Item較小的那個的Item。當往回滑動時,由於前一個View的Item較小,position為views.size() - 2,自然觸發不了跳轉。而這時就會進入else,isEnd被置否了。但不用擔心,只要使用者沒有真的回到前一頁,最終onPageScrolled會被最後一次呼叫且引數position回到views.size() - 1,使isEnd 停留在true。當且僅當使用者再向後滑動時,onPageScrolled才會以(views.size() - 1, 0, 0)的引數,觸發if,進而跳轉。

  2. 重頭戲,【平滑過渡】+【迴圈滾動】。

    這個方法是根據網上第三種方法優化而來的:

        //首先得到View的集合:
    
        ArrayList<View> views = new ArrayList<>();
    
        for (Drawable image : images) {
            ImageView imageView = new ImageView(this);
            imageView .setImageDrawable(image );
    
            views.addView(imageView );
    
        }
    
        //錯誤的做法,View不能複用!
    
        //(或者你去PagerAdapter裡面在把View新增進ViewGroup那裡做手腳)
        /*
        //首位插入末位的View
        views .add(0, views .get(views .size() - 1));
        //末尾增加首位(現在是次位)的View
        views .add(0, views .get(1));
        */
    
        //然而圖片是可以複用的
        //首位插入末位的圖片
        ImageView tempImageView = new ImageView(context);
        tempImageView .setImageDrawable(
                      ((ImageView) views .get(views .size() - 1)).getDrawable());
        views.add(0, tempImageView);
        //末尾增加首位(現在是次位)的圖片
        tempImageView = new ImageView(context);
        tempImageView .setImageDrawable(
                      ((ImageView) views .get(1)).getDrawable());
    
        views.add(tempImageView);
    
    
    
        //設定ViewPager
        ViewPager viewPager = new ViewPager(this);
        viewPager .setAdapter(new MyPagerAdapter(views));
        viewPager .setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
              @Override
              public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                    //第二個引數false。預設true是平滑過渡,但現在其實已經過渡完成,自然應當是false
                    if (position == 0 && positionOffset == 0) viewPager .setCurrentItem(views.size() - 2, false);
                    else if (position == views.size() - 1 && positionOffset == 0) viewPager .setCurrentItem(1, false);
          }
    
              @Override
               public void onPageSelected(int position) {}
    
               @Override
               public void onPageScrollStateChanged(int state) { }
        });
        //從隨機位置開始,重要的是一定不能是首末位置(0、views.size() - 1)
        viewPager .setCurrentItem(1 + new Random().nextInt(views.size() - 2));

    關鍵的地方註釋都寫好了。我再簡單解釋一下,為什麼我不在onPageSelected();方法裡做跳轉的判斷呢?如果認真看了之前對網上第二種方法的分析就會明白。onPageSelected方法是在跳轉過程中呼叫的,所以會導致首位連線處的跳轉少半個週期。這就是網上第三種方法出錯的地方。然後就是幾個注意事項,setCurrentItem()要帶上第二個引數,設為false、View不能複用、要設定初始位置不為首末位置。

ok,就這樣了。這片部落格有點長啊,要耐心看完哦~(我是不是該把這句話放開頭?看到這句話的舉爪~~~)

2016-08-20:感謝@suwueng指出,這個方法在從末尾滑動到頁首的時候會出現一下閃爍,大概是那一頁還沒完全加載出來而直接set引起的。所以這個方法還是有缺陷的,如果誰能想出好的解決方法還請務必分享一下。

附上我的小遊戲連結,數碼寶貝2048,在裡面選擇數碼寶貝的地方用到了這個。GitHub