此筆記主要是說(shuō)一下ViewPager如何實(shí)現(xiàn)預(yù)加載,以及如何實(shí)現(xiàn)懶加載、禁止左右滑動(dòng)。
- ViewPager 作為平時(shí)開(kāi)發(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),通過(guò)當(dāng)前的Item與pageLimit計(jì)算左右需要預(yù)加載頁(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);
好了,我們來(lái)模擬一下,假設(shè)此時(shí)ViewPager中有10個(gè)頁(yè)面,當(dāng)前頁(yè)面為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;
也就是說(shuō):
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è)面就退出了,而且預(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è)頁(yè)面,當(dāng)前頁(yè)面為第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;
也就是說(shuō):
startPos = 3;
n = 10;
endPos = 3;
這樣就實(shí)現(xiàn)了ViewPager的懶加載功能。
但是 DEFAULT_OFFSCREEN_PAGES =1; 是ViewPager中默認(rèn)的值,即便是我們打開(kāi)ViewPager的源碼將其修改為0,運(yùn)行后還是默認(rèn)為1 ,我們?nèi)绻獙?shí)現(xiàn)懶加載,只有將 ViewPager 下的代碼完全復(fù)制一份,然后自建一LazyViewPager 繼承于ViewGroup,然后將代碼粘貼過(guò)來(lái),并將 DEFAULT_OFFSCREEN_PAGES 的值修改為0即可,使用的時(shí)候,直接使用LazyViewPager即可。
ViewPager禁止左右滑動(dòng)
ViewPager如何禁止左右滑動(dòng)?
- 我們?cè)谑褂肰iewPager的時(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)也很簡(jiǎn)單,從事件分發(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.
*/
...
}
什么意思呢?大概意思是說(shuō):這個(gè)方法決定了我們是否要截?cái)鄤?dòng)作。如果返回true,onMotionEvent將被調(diào)用,我們?cè)谀抢飯?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),喔,里面處理的邏輯好多,看的頭蒙,怎么辦呢?不用急,簡(jiǎn)單的說(shuō)此方法大概意思是:是否自己消費(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);
}
}
簡(jiǎn)單說(shuō)一下,mNoScoll 默認(rèn)false ,如果我們現(xiàn)在的ViewPager 不想實(shí)現(xiàn)左右滑動(dòng),只需要 setNoScroll(true) 即可,此時(shí)mNoScoll 為false,然后在 onTouchEvent 方法和 onInterceptTouchEvent 方法的時(shí)候已經(jīng) 攔截 和 不消費(fèi)了。