ViewPager實現輪播廣告欄(BannerPager)

實現這個功能的目前已經有很多種方式了,譬如說繼承ViewGroup仿ViewPager通過adapter循環add、dstory指定的item,使用fragment添加移除view的方式,設置viewpager的索引條目達到上限==。以上幾種方式屬於Banner的常用選擇,其中動效做的非常出色的如代碼家的ImageSlider,曾經在項目中也引用過,不過在使用的過程中出現了內存開銷過大的問題,1張圖片也可以左右輪迴切換的問題,不得不說是個硬傷。後來遇到了一種新的方式,於是分享出來給大家。

Banner和viewpgaer的最大區別是Banner可以在itemPosition=0和itemPosition=size-1之間來回無縫切換,因此我們理想狀態是作如下處理的:當滑動到itemPosition=0的時候,左側有對應itemPosition=0到itemPosition=size-1的view;當滑動到itemPosition=size-1的時候,右側有對應itemPosition=0到itemPosition=size-1的view。這是我們一般直接的做法:設置viewpager的索引條目達到上限。然而開銷的問題讓我們會想到複用,結合listview的adapter的viewholder形式,可能會採取第i屏的第itemPosition=position的view複用第1屏的itemPosition=position的view。這樣的做法看似不錯,但是對於擁有着程序員思維的我們覺得這僅僅是一個邏輯不夠嚴密,過程不夠完美,存在潛藏的隱患的idea。於是兩條道路,優化or另闢蹊徑。

其實再做上述過程中的考慮時候,我們想到了複用,想到了設置size=Integer.MAX_VALUE實現itemPosition=0和itemPosition=size-1之間的切換,但是仔細考慮一下,我們也僅僅需要實現itemPosition=0和itemPosition=size-1之間的切換,就可以交給viewpager本身的機制來完成剩下頁卡之間的輪播。假設一共需要展示4個view,於是畫圖如下:

... item=2 -> item=3 -> item=0 -> item=1 -> item=2 -> item=3 -> item=0 -> item=1 ...

也就是說我們發現可以考慮當itemPosition=3的時候下一屏itemPosition=0的view就是當前屏被滑動過去itemPosition=0的View,用來代替無限size=Integer.MAX_VALUE的實現方式,然而這樣難點又回到瞭如和實現兩者之間的無動畫切換。於是我們想到實現size=Integer.MAX_VALUE的本質其實就是對viewpager不停的addView和destoryView,當滑動到itemPosition=0的時候在左側添加一個itemPosition=3的view,當滑動到itemPosition=3的時候在右側添加一個itemPosition=0的view,但是後續的itemPosition=1和itemPosition=2的view就可以不用我們手動add進去,因爲viewpager的本質會自動默認itemPosition=3左側、itemPosition=0右側存在itemPosition=1、2的view,因此上述圖我們可以簡化成:

                       |<—————————————|     
itemPosition    3  ->  0  ->  1  ->  2  ->  3  ->  0
                |—————————————>|

於是我們把addView簡化成了addView(0,item3View)和addView(5,item0View),而當加載到這兩個view的時候我們認爲它加載的其實就是itemPosition=3和itemPosition=0的view。認爲這個詞語在代碼中的體現就是set,因此當我們在首尾各添加了一個view之後可以寫出如下代碼:

if(getCurrentPosition() == 0){
    setCurrentPosition(4);
}
if(getCurrentPosition() == 5){
    setCurrentPosition(1);
}

抽取該思路方法:

@Override
public int getCount(){
    if(mDatas != null && mDatas.size() >= 0){
        if(mDatas.size() > 0){
            return mDatas.size() + 1;
        }else{
            return 0;
        }
    }
}
addOnPageChangeListener(new OnPageChangeListener(){
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    }

    @Override
    public void onPageSelected(int position) {
        if(getCurrentPosition() == 0){
            setCurrentPosition(4);
        }
        if(getCurrentPosition() == 5){
            setCurrentPosition(1);
        }
    }

    @Override
    public void onPageScrollStateChanged(int state) {
    }
});

看起來這樣的邏輯是不錯的,但是因爲setCurrentPosition這個方法默認是帶動畫效果切換view的,所以我們得通過

setCurrentPosition(position,false);

來設置我們的認爲。其次我們在實際切換過程中發現了切換即將結束的時候被強行提前結束切換效果。通過分析發現OnPageChangeListener.onPageSelected總是在畫面未切換結束、界面上含有一個view部分內容的時候便已經執行了。因此我們需要判斷切換的狀態。而OnPageChangeListener.onPageScrollStateChanged的參數state提供了我們切換的狀態,按照Google的API所說,state提供了三種狀態:

SCROLL_STATE_IDLE=0:什麼都沒做 
SCROLL_STATE_DRAGGING=1:開始滑動 
SCROLL_STATE_SETTLING=2:滑動結束

在SCROLL_STATE_IDLE和SCROLL_STATE_SETTLING之間判斷後發現真正的界面停留是SCROLL_STATE_IDLE狀態,所以通過判斷SCROLL_STATE_IDLE狀態我們進行無縫界面切換,避免提前技術切換和僵直切換。

換代碼如下:

@Override
public void onPageScrollStateChanged(int state) {
    if(state == SCROLL_STATE_IDLE){
        if(getCurrentPosition() == 0){
            setCurrentPosition(getChildCount() - 2);
        }
        if(getCurrentPosition() == getChildCount() - 1){
            setCurrentPosition(1);
        }
    }
}

好了這個時候我們發現循環動畫切換已經徹底實現了,接下來實現頁卡指示器。我們可以通過在onPageScrollStateChanged()裏面設置回調監聽。然而這個時候我們通過onPageScrollStateChanged變換了頁卡,同時通過計算也能獲取當前正確的頁卡座標,但是當手指快速滑動的時候我們的頁卡指示器竟然沒有自動切換,當且僅當手指離開屏幕的時候跳轉到當前的頁卡指示器。通過邏輯梳理我們發現,SCROLL_STATE_IDLE的狀態僅在手指離開屏幕的時候被出發,即快速連續滑動屏幕過程中不會觸發該狀態,通過該狀態SCROLL_STATE_IDLE得到的itemPosition也是不及時的,而非當前狀態的itemPosition則很難得到判斷。於是用最原始的方式onPageSelected方法設置頁卡指示器。

通過分析,onPageSelected的參數position是當前view的position,因此這個position與數據的positon座標器非同一個座標器。但因爲我們在計算數據總數的時候頭尾分別加了一個數據展示,因此view的position比數據的position永遠超前一位,即
dataPosition = viewPosition - 1;
而我們的viewPosition在onPageSelected方法裏面就是參數position,故而,我們應該在onPageSelected方法裏面設置回調監聽,設置頁卡器。
不過通過剛纔的問題我們會發現,因爲onPageScrollStateChanged的state判斷,當連續滑動不觸發SCROLL_STATE_IDLE狀態,即當處於重置頁面座標的時候如果不觸發該狀態,最終結果就是不能夠在view的itemPosition=0和itemPosition=size-1的時候不能夠向左向右滑動,除非鬆開手指觸發SCROLL_STATE_IDLE。針對這一點也未想到更好的解決方法。

接下來提供已經封裝好的BannerPager,涵蓋:
1. 頁卡指示器功能;
2. 自動輪播功能(當手指滑動時重置當前頁卡的切換間隔時間);
3. 新增獲取正確數據的currentItemPosition的方法和設置currentItemPosition:getRealCurrentItem()和setRealCurrentItem(int),setRealCurrentItem(int,boolean)方法。(用以代替Android的API提供的setCurrentItem(int),setCurrentItem(int,boolean)方法和getCurrentItem()方法);
4. 新增addPageIndicator(OnIndicatorChangedListener)方法,用來代替addOnPageChangeListener(OnPageChangeListener)方法;
5. 配置自定義屬性來設置指示器的位置和背景圖片。

點擊我跳轉到GitHub項目地址

推薦:
代碼家的AndroidImageSlider

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章