給自己的忠告:雖然輪子很好用,但是使用輪子的前提是:如果不去封裝一些複雜的功能,自己會用最基本的方法寫一個,不然再好的輪子那也是別人的,當自己專案遇到和輪子不一樣的地方,那就只能束手無策或者改人家的原始碼,當然能看懂輪子的封裝思想自己學以致用並且能夠很輕鬆的更改原始碼那是最好不過的了。
1. 實現思路
兩種方案:
(1)採用Adapter內的getCount()方法返回Integer.MAX_VALUE。
(2)在列表的最前面插入最後一條資料,在列表末尾插入第一個資料,造成迴圈的假象
2. 具體實現
2.1 方案一:getCount()返回Integer.MAX_VALUE
2.1.1 ViewPager無限迴圈
在ViewPager的Adapter內的getCount方法中,返回一個很大的數Integer.MAX_VALUE,理論上可以無限滑動。當顯示完一個真實列表的週期後,又從真實列表的0位置顯示資料,造成無限迴圈輪播的假象。因為ViewPager第一頁不能向左滑動迴圈,所以我們要通過mViewPager.setCurrentItem(Integer.MAX_VALUE/2)設定選中的位置,這樣最開始就可以向左滑動,但是因為要顯示第一頁所以該值%資料個數==0。因為設定為Integer.MAX_VALUE後會在setCurrentItem()的時候發生ANR,所以這裡使用一個自定義的較大的數比較好,這裡我是用500
//當前選中頁
private int currentPosition;
//資料項個數
private List<Integer> itemList;
public static final int mLooperCount = 500;
//設定當前選中的item
currentPosition = getStartItem();
viewPager1.setCurrentItem(currentPosition1);
private int getStartItem() {
if(getRealCount() == 0){
return 0;
}
// 我們設定當前選中的位置為Integer.MAX_VALUE / 2,這樣開始就能往左滑動
// 但是要保證這個值與getRealPosition 的 餘數為0,因為要從第一頁開始顯示
int currentItem = getRealCount() * BannerAdapter.mLooperCount / 2;
if(currentItem % getRealCount() ==0 ){
return currentItem;
}
// 直到找到從0開始的位置
while (currentItem % getRealCount() != 0){
currentItem++;
}
return currentItem;
}
//獲取資料項個數
private int getRealCount() {
return itemList == null ? 0 : itemList.size();
}
複製程式碼
Adapter只需將getCount()返回Integer.MAX_VALUE即可(這裡我們改為具體的數值),其他的操作是正常的操作。
@Override
public int getCount() {
return getRealCount() * mLooperCount;
}
複製程式碼
2.1.2 加入輪播功能
採用Handler的postDelayed方法
private Handler mHandler = new Handler();
@Override
protected void onResume() {
super.onResume();
//開始輪播
mHandler.postDelayed(mLoopRunnable, mDelayedTime);
}
@Override
protected void onPause() {
super.onPause();
//停止輪播
mHandler.removeCallbacks(mLoopRunnable);
}
複製程式碼
private final Runnable mLoopRunnable = new Runnable() {
@Override
public void run() {
if (mIsAutoPlay) {
//方案一
currentPosition1 = viewPager1.getCurrentItem();
currentPosition1++;
if (currentPosition1 == bannerAdapter.getCount() - 1) { //滑到最後一個時
currentPosition1 = 0; //切換到第0個
viewPager1.setCurrentItem(currentPosition1, false);
mHandler.postDelayed(this, mDelayedTime);
} else {
viewPager1.setCurrentItem(currentPosition1);
mHandler.postDelayed(this, mDelayedTime);
}
} else {
mHandler.postDelayed(this, mDelayedTime);
}
}
};
複製程式碼
2.1.3 使用Integer.MAX_VALUE的爭議
有人會覺得會影響記憶體,大家可以參考這篇文章Android ViewPager 無限輪播Integer.MAX_VALUE 爭議(看原始碼)就能夠解決疑惑。
2.1.4 注意
使用Integer.MAX_VALUE會在setCurrentItem()的時候發生ANR,所以還是設定為一個比較大的數比較好。在程式碼中我已經更改為返回getRealCount()*500這一數值,如果文章中有返回Integer.MAX_VALUE的,那就是我還沒更正,大家請自行更改。
2.2 方案二:資料項首尾新增兩條資料
2.2.1 ViewPager無限迴圈
假設有三條資料,分別編號1、2、3,我們再建立一個新的列表,長度為真實列表的長度+2,在最前面插入最後一條資料3,在最後面插入第一條資料1,新列表就變為3、1、2、3、1,當viewpager滑動到位置0時就通過setCurrentItem(int item,boolean smoothScroll)
方法將頁面切換到位置3,同理當滑動到位置4時,通過該方法將頁面切換到位置1,這樣給我們的感覺就是無限迴圈。
private int currentPosition2;
private void initData2() {
itemList2 = new ArrayList<>();
itemList2.add(R.drawable.ic_pic4);
itemList2.add(R.drawable.ic_pic1);
itemList2.add(R.drawable.ic_pic2);
itemList2.add(R.drawable.ic_pic3);
itemList2.add(R.drawable.ic_pic4);
itemList2.add(R.drawable.ic_pic1);
bannerAdapter2 = new BannerAdapter2(itemList2);
viewPager2.setAdapter(bannerAdapter2);
currentPosition2 = 1;
viewPager2.setCurrentItem(currentPosition2);
viewPager2.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int i, float v, int i1) {
}
@Override
public void onPageSelected(int i) {
currentPosition2 = i;
}
@Override
public void onPageScrollStateChanged(int state) {
//驗證當前的滑動是否結束
if (state == ViewPager.SCROLL_STATE_IDLE) {
if (currentPosition2 == 0) {
viewPager2.setCurrentItem(itemList2.size() - 2, false);//切換,不要動畫效果
} else if (currentPosition2 == itemList2.size() - 1) {
viewPager2.setCurrentItem(1, false);//切換,不要動畫效果
}
}
}
});
}
複製程式碼
2.2.2 加入輪播功能
private Handler mHandler2 = new Handler();
@Override
protected void onResume() {
super.onResume();
//開始輪播
mHandler2.postDelayed(mLoopRunnable2, mDelayedTime);
}
@Override
protected void onPause() {
super.onPause();
//停止輪播
mHandler2.removeCallbacks(mLoopRunnable2);
}
複製程式碼
private final Runnable mLoopRunnable2 = new Runnable() {
@Override
public void run() {
if (mIsAutoPlay) {
//方案二:多添兩條資料
currentPosition2 = viewPager2.getCurrentItem();
currentPosition2++;
//不需要為了迴圈輪播來判斷是否到達最後一頁,在監聽器中已經為我們做了此操作
viewPager2.setCurrentItem(currentPosition2);
mHandler2.postDelayed(this, mDelayedTime);
} else {
mHandler2.postDelayed(this, mDelayedTime);
}
}
};
複製程式碼
與方案一不同的地方就是當滑動到最後一個時,切換到下標為1的頁面,當滑動下標為0的頁面時,切換到最後一個
2.3 比較
依然範特稀西 在文章中這樣說到:第二種方案在切換動畫的時候,因為當滑到位置4時,我們通過setCurrentItem(int item,boolean smoothScroll)方法,來將其切換到位置1才有了無限迴圈的效果,但為了不被發現,第二個引數smoothScroll設定為false,這樣就沒有了切換動畫,導致生硬,所以不用這個。
本來沒想實現方案二(想著會一種方法就行),但好奇心使我想看下到底有多生硬,但沒有發現生硬的效果。因為我們在onPageScrollStateChanged()方法裡監聽了動畫結束的狀態,所以當滑動到第四張,再次開啟一個週期的時候,我們其實是滑動到了第五張,就是我們往尾部新增的那張圖片,此時是有動畫的,並不是itemlist下標為0的位置,而且在此監聽器中,當判斷其實最後一張的時候,我們已經通過setCurrentItem()不帶動畫效果的方式偷偷的把它切換到下標為1的位置了,所以在handler通過currentItem++方式再次滑動時,它滑動到的是下標為2的圖片,也是帶效果的,所以不存在什麼生硬的效果。 以下我一共放了四張圖,大家可以仔細看下效果:
3. 總結
以上就是最基本的方法來實現ViewPager的無限輪播的全部內容。具體程式碼見Github。
其實我們常見的Banner圖還有Indicator指示器(就是底部的小點),這個我用的其實還是依然範特稀西自定義的Indicator,因為確實很好用,而且封裝的話也很簡單,雖然一樣的,但是我還是想下一篇再記錄一下封裝的過程,讓自己加深下印象,下篇文章見。
4. 參考文章
Android ViewPager 無限輪播Integer.MAX_VALUE 爭議(看原始碼)
ViewPager系列之 仿魅族應用的廣告BannerView
Android 使用ViewPager實現無限輪播出現空白bug原因及解決方案(Integer.MAX_VALUE實現方式)