Android實現真正的ViewPager【平滑過渡】+【迴圈滾動】!!!順帶還有【末頁跳轉】。
實現真正的ViewPager【平滑過渡】+【迴圈滾動】!!!順帶還有【末頁跳轉】。
首先呢, 我要對網上常見的3種ViewPager的迴圈滾動方法做個概述。急需看真正實現方法的同志請選擇性忽略下面這一長段。不過有時間精力的話還是看看,尤其後兩者,我的方法是基於這兩者的:
-
不講道理式。特點:不管3721,先在PagerAdapter的getCount();裡返回一個很大的值。保守的返回個100,極端的直接Integer.MAX_VALUE。看到這裡估計就已經有很多孩子笑了,這尼瑪跟大學C語言課本上,陣列長度未知就定義個1000單位的思想簡直如出一轍啊有木有!!!好吧,那麼這種方法顯然並沒有真的解決問題,雖然不會有幾個使用者真有耐心去滑動Integer.MAX_VALUE / 2次。。。不過鑑於目前網上其它方法都有這樣那樣的缺陷,還是有不少人寧願選擇這個的。那麼我就說詳細點吧。這個方法後續操作中,比較關鍵的步驟有兩點:
setCurrentItem(Integer.MAX_VALUE / 2);
,這樣才能保證一開始就能雙向滑動。 二、要在自定義的PagerAdapter的instantiateItem(ViewGroup arg0, int arg1);【較早的Api裡為View arg0,已廢棄】方法裡返回當前位置對總頁面數的模。即views.get(arg1 % views.size());
-
邏輯大神式。特點:試圖通過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佔檢視的百分比onPageSelected(int position);
更噁心。。。這個方法是在onPageScrolled();的一系列呼叫中間插入進去的,而不是常識的在其結束並穩定在某個檢視後!大致規律是positionOffset向左向右越過某個值左右時觸發,這個值不總是0.5,它與使用者的滑動速度、拖甩手勢等相關,應該是有一套計算公式的!而一旦這個方法觸發,意味著馬上就要進入position位置的view了(而不是已經),且這一動作不可被撤銷,所以看起來好像是在進入後才呼叫的。 三、onPageScrollStateChanged(int state);
這個方法是唯一比較正常的。在一次滑動中,其狀態值及改變順序為:1-開始滑動、2-越過觸發onPageSelected();的值、0-滑動結束(呃,其實我也記不清了,反正這個方法對我沒啥用。。。) -
接近成功式。是的,這個方法行得通!只是犧牲了一些細節,過渡不太平滑,雖然它看起來可以。這個方法的思想是設定標誌位置。設原本的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); }
是的,我彷彿聽到了恍然大悟的聲音。這個方法的確巧妙,已經無限接近成功。但可惜的是,這裡面有些注意事項,網上那個發明這方法的人並未列出(網上太亂了,真心不知道原作者是誰,不過也得感謝一下),使得一些情況下莫名其妙的失敗了。更主要的是,這個方法並不能平滑過渡,雖然它看起來好像可以,但你會發現首位連線處丟了半個週期的滑動。但這並不是不可逆轉的,我的方法就是對該方法的一種修正。
好,現在是正文(前戲有點長了啊。。。)
=========================================================
=======================華麗的分割線==========================
=========================================================
-
先上盤小菜:【末頁跳轉】。
其實許多的程式引導頁做到這點,就是不知道方法是否和我的一樣。先看程式碼:
(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,進而跳轉。
-
重頭戲,【平滑過渡】+【迴圈滾動】。
這個方法是根據網上第三種方法優化而來的:
//首先得到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