1. 程式人生 > >真無限迴圈的ViewPager——解決兩端滑動的平滑問題

真無限迴圈的ViewPager——解決兩端滑動的平滑問題

使用ViewPager實現圖片輪播應該是大家很熟悉的做法。但是ViewPager有個缺點,不支援迴圈播放,滾到最右邊不能繼續右滾,同樣,滾到最左邊也不能繼續左滾。這是個令人頭疼的事情,好在程式設計師們神通廣大,大家提出了兩種方法解決這個問題。

1. 假無限迴圈。把PagerAdapter的getCount設為Integer.max,這樣在一般情況下都不可能滾到邊緣,達到無限迴圈的效果。但理論上它不是真的無限迴圈。

2. 真無限迴圈。假設有三張需要顯示的圖片ABC,額外在首尾新增兩張輔助圖片,形成序列CABCA。當向右滾動到最右的A時,立即跳轉到左邊的第二個圖片A,當向左滾動到最左的C時,立即跳轉到右邊的倒數第二個圖片C,這兩次跳轉使用語句setCurrentItem(1, false)和setCurrentItem(viewPager.getAdapter.getCount() - 2, false)。注意,由於第二個引數使用了false,因此跳轉不會產生動畫效果,因此這兩次跳轉對使用者而言是不可見的,於是達到了無限迴圈的效果。這是真正的無限迴圈。

像我這樣的強迫症患者基本都會選擇真無限迴圈,為了避免理論上的一絲絲出錯的可能。可不幸的是,真無限迴圈並沒有想象中那麼完美,在網友的大部分解決方案中,都存在這麼一點點瑕疵,而這一點點瑕疵對於一名追求完美的程式設計師來說都是致命的。問題就出在從最後一個滾動到第一個的過程中。以上面圖片序列CABCA為例,通常的解決方案如下圖所示


當從C到A滾動時,我們會發現C的動畫效果未完成就瞬間切到了A。對應這個圖,其實是因為1的動畫未完成就開始了2過程。而2過程是無動畫的,所以1還沒結束就直接切到了左數第二個圖片A,出現了滾動效果不一致的情況。

之所以會這樣,是因為我們把過程2的觸發動作setCurrentItem(1, false)寫到了onPageSelected方法中。直觀上來看這樣好像沒什麼問題,但是,經過我的測試,onPageSelected方法的呼叫並不表示切換已經完成,它只表示某個頁面已經被選中,當手機離開螢幕時會呼叫這個事件。但是我們可以在任何位置離開螢幕,因此,在這個方法中呼叫setCurrentItem是不合適的。

我們的目標是1的動畫效果全部完成再執行2,好在OnPageChangeListener中還有另一個回撥onPageScrollStateChanged(int state)。這個方法在滾動狀態改變時被呼叫,滾動狀態共有三種:IDLE(空閒狀態,沒有任何滾動正在進行),DRAGGING(正在拖動圖片),Settling(手指離開螢幕,自動完成剩餘的動畫效果)。詳細講解推薦博文OnPageChangeListener引數變化詳細總結。我們可以等到到達IDLE狀態後再執行setCurrentItem跳轉,這樣不就毫無痕跡了~

於是,實現程式碼非常簡單

private final class MyPageChangeListener implements OnPageChangeListener {

    private int currentPosition;

    @Override
    public void onPageScrollStateChanged(int state) {
        if (state == ViewPager.SCROLL_STATE_IDLE) {
            if (currentPosition == viewPager.getAdapter().getCount() - 1) {
                viewPager.setCurrentItem(1, false);
            }
            else if (currentPosition == 0) {
                viewPager.setCurrentItem(viewPager.getAdapter().getCount() - 2, false);
            }
        }
    }

    @Override
    public void onPageScrolled(int scrolledPosition, float percent, int pixels) {
        //empty
    }

    @Override
    public void onPageSelected(int position) {
        currentPosition = position;
    }

}
在onPageSelected方法中只需要記錄新的位置索引,而跳轉操作放到onPageScrollStateChanged中進行。好啦,大功告成!

經過測試,這個方案在非極端情況下表現的很好。所以,什麼是極端情況?

當我們滑動頻率非常高時(不排除有些無聊的人為了測試手機的流暢程度滑來滑去),該方案的跳轉仍然存在瑕疵。就是在過程1進行中的所有滑動操作都得不到響應,導致1過程形成卡頓。這個問題仍待解決,但我相信會有解決方案的。不管怎樣,當前的方案已經足夠正常使用了,特別是在將其用於自動輪播的時候。

最後,感謝stackoverflow中的@tobi_b同學,他的方案給了我靈感,在此表示感謝。