此筆記主要是說一下ViewPager如何實(shí)現(xiàn)預(yù)加載欺旧,以及如何實(shí)現(xiàn)懶加載、禁止左右滑動(dòng)蛤签。
- ViewPager 作為平時(shí)開發(fā)時(shí)經(jīng)常使用的控件辞友,我們多是配合TabLayout、嵌套Fragment使用震肮。
預(yù)加載
- 當(dāng)ViewPager中嵌套的Fragment多于2個(gè)的時(shí)候称龙,ViewPager就會(huì)預(yù)加載當(dāng)前顯示Fragment左右兩側(cè)的Fragment。
ViewPager是如何實(shí)現(xiàn)預(yù)加載的戳晌?
翻看ViewPager我們可以發(fā)現(xiàn)一個(gè)常量 private static final int DEFAULT_OFFSCREEN_PAGES = 1; 其實(shí)鲫尊,這就是實(shí)現(xiàn)ViewPager預(yù)加載的值,這個(gè)值的意義也是 默認(rèn)ViewPager當(dāng)前變量的值為1沦偎。
那么ViewPager具體是如何實(shí)現(xiàn)的呢疫向?繼續(xù)翻看源碼。
ViewPager源碼中全局搜查 DEFAULT_OFFSCREEN_PAGES豪嚎,發(fā)現(xiàn)將 DEFAULT_OFFSCREEN_PAGES 賦值給了 mOffscreenPageLimit 搔驼,那我們繼續(xù)搜查 mOffscreenPageLimit ,發(fā)現(xiàn) 又賦值給了 pageLimit 疙渣,好了匙奴,我們終于找到了我們要看的邏輯。源碼中我們發(fā)現(xiàn)妄荔,通過當(dāng)前的Item與pageLimit計(jì)算左右需要預(yù)加載頁面的角標(biāo)泼菌。
計(jì)算的方法如下:
final int startPos = Math.max(0, mCurItem - pageLimit);
final int N = mAdapter.getCount();
final int endPos = Math.min(N - 1, mCurItem + pageLimit);
好了谍肤,我們來模擬一下,假設(shè)此時(shí)ViewPager中有10個(gè)頁面哗伯,當(dāng)前頁面為3荒揣。pageLimit = mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES = 1;
startPos = Math.max(0 , 3 - 1); = Math.max(0 ,2); = 2;
n = 10;
endPos = Math.min(10 - 1 , 3 + 1); = Math.min(9 , 4); = 4;
也就是說:
startPos = 2;
n = 10;
endPos = 4;
這樣就實(shí)現(xiàn)了ViewPager的預(yù)加載功能。
懶加載
ViewPager如何實(shí)現(xiàn)懶加載焊刹?
- 上面我們分析了ViewPager是如何實(shí)現(xiàn)預(yù)加載的系任,可是有時(shí)我們不想實(shí)現(xiàn)ViewPager的預(yù)加載功能,因?yàn)橛脩艨赡懿粫?huì)查看預(yù)加載的頁面就退出了虐块,而且預(yù)加載的頁面如果有聯(lián)網(wǎng)操作俩滥,也會(huì)消耗用戶的流量。
- 那么贺奠,我們?nèi)绾螌?shí)現(xiàn)懶加載呢霜旧?
別怕,上面我們既然找到了ViewPager如何實(shí)現(xiàn)預(yù)加載的方法儡率,我們可以修改 DEFAULT_OFFSCREEN_PAGES = 0 挂据,使其不實(shí)現(xiàn)預(yù)加載。我們可以驗(yàn)證一下:
final int startPos = Math.max(0, mCurItem - pageLimit);
final int N = mAdapter.getCount();
final int endPos = Math.min(N - 1, mCurItem + pageLimit);
同樣儿普,還假設(shè)ViewPager中有10個(gè)頁面崎逃,當(dāng)前頁面為第3個(gè)。pageLimit = mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES = 0;
startPos = Math.max(0 , 3 - 0); = Math.max(0 ,3); = 3;
n = 10;
endPos = Math.min(10 - 1 , 3 + 0); = Math.min(9 , 3); = 3;
也就是說:
startPos = 3;
n = 10;
endPos = 3;
這樣就實(shí)現(xiàn)了ViewPager的懶加載功能眉孩。
但是 DEFAULT_OFFSCREEN_PAGES =1; 是ViewPager中默認(rèn)的值个绍,即便是我們打開ViewPager的源碼將其修改為0,運(yùn)行后還是默認(rèn)為1 勺像,我們?nèi)绻獙?shí)現(xiàn)懶加載障贸,只有將 ViewPager 下的代碼完全復(fù)制一份错森,然后自建一LazyViewPager 繼承于ViewGroup吟宦,然后將代碼粘貼過來,并將 DEFAULT_OFFSCREEN_PAGES 的值修改為0即可涩维,使用的時(shí)候殃姓,直接使用LazyViewPager即可。
ViewPager禁止左右滑動(dòng)
ViewPager如何禁止左右滑動(dòng)瓦阐?
- 我們在使用ViewPager的時(shí)候一般里面嵌套的Fragment會(huì)使用ListView或者RecyclerView 亦或者有輪播圖蜗侈,但是在左右滑動(dòng)的時(shí)候是輪播圖滑動(dòng)呢還是讓ViewPager左右滑動(dòng)呢?
- 這個(gè)時(shí)候我們就要禁止ViewPager的左右滑動(dòng)操作睡蟋。我們?nèi)绾尾僮髂兀?/li>
其實(shí)禁止ViewPager的左右滑動(dòng)也很簡單踏幻,從事件分發(fā)機(jī)制考慮即可。翻看ViewPager的 onInterceptTouchEvent 方法戳杀,也可以看到注釋:
public boolean onInterceptTouchEvent(MotionEvent ev){
/*
* This method JUST determines whether we want to intercept thmotion.
* If we return true, onMotionEvent will be called and we do thactual
* scrolling there.
*/
...
}
什么意思呢该面?大概意思是說:這個(gè)方法決定了我們是否要截?cái)鄤?dòng)作夭苗。如果返回true,onMotionEvent將被調(diào)用,我們在那里執(zhí)行實(shí)際的滾動(dòng)隔缀。
所以题造,我們可以大概理解為:是否要攔截事件,如果攔截就自己處理猾瘸,如果不攔截就將事件傳遞給下面的子View處理界赔。我們可以發(fā)現(xiàn),此方法返回的是boolean類型牵触,所以淮悼,我們要禁止左右滑動(dòng)的話,只需要返回false即可揽思,意思就是不攔截事件敛惊,傳遞給下面的子View。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
- 但是如果viewpage里面子控件不是viewgroup,還是會(huì)調(diào)用 onTouchEvent 方法绰更,所以瞧挤,我們還要處理onTouchEvent這個(gè)方法汞幢。
查看ViewPager的onTouchEvent發(fā)現(xiàn)母赵,喔,里面處理的邏輯好多荆秦,看的頭蒙徐钠,怎么辦呢癌刽?不用急,簡單的說此方法大概意思是:是否自己消費(fèi)事件尝丐。如果自消費(fèi)显拜,事件就結(jié)束。如果不消費(fèi)就傳遞給父控件爹袁。所以远荠,我們想實(shí)現(xiàn)禁止左右滑動(dòng),只需要ViewPager不消費(fèi)事件即可:
@Override
public boolean onTouchEvent(MotionEvent ev) {
return false;
}
綜上所述失息,如果我們想實(shí)現(xiàn)ViewPager的禁止左右滑動(dòng)譬淳,只要覆寫 onInterceptTouchEvent 方法 和 onTouchEvent 方法即可。
- 如果考慮復(fù)用盹兢,即使用同一個(gè)ViewPager 邻梆,有時(shí)可以左右滑動(dòng),有時(shí)禁止绎秒,那么我們可以寫一個(gè)方法讓其實(shí)現(xiàn)是否可以左右滑動(dòng)浦妄。
public void setNoScroll(boolean noScroll) {
mNoScoll = noScroll;
}
然后在 onTouchEvent 方法和 onInterceptTouchEvent方法判斷處理即可:
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (mNoScoll) {
return false;
} else {
return super.onTouchEvent(ev);
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (mNoScoll) {
return false;
} else {
return super.onInterceptTouchEvent(ev);
}
}
簡單說一下,mNoScoll 默認(rèn)false ,如果我們現(xiàn)在的ViewPager 不想實(shí)現(xiàn)左右滑動(dòng)剂娄,只需要 setNoScroll(true) 即可窘问,此時(shí)mNoScoll 為false,然后在 onTouchEvent 方法和 onInterceptTouchEvent 方法的時(shí)候已經(jīng) 攔截 和 不消費(fèi)了宜咒。