亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

ViewPager源碼分析

2019-11-07 23:55:26
字體:
來源:轉載
供稿:網友

1.問題

由于Android Framework源碼很龐大,所以讀源碼必須帶著問題來讀!沒有問題,創造問題再來讀!否則很容易迷失在無數的方法與屬性之中,最后無功而返。 那么,關于ViewPager有什么問題呢? 1. setOffsreenPageLimit()方法是如何實現頁面緩存的? 2. 在布局文件中,ViewPager布局內部能否添加其他View? 3. 為什么ViewPager初始化時,顯示了一個頁面卻不會觸發onPageSelected回調?

問題肯定不止這三個,但是有這三個問題基本可以找到本次分析的重點了。讀者朋友也可以自己先提出一些問題,再看下面的分析,看看是否可以從分析過程中找到答案。

2.從onMeasure()下手

ViewPager繼承自ViewGroup,是Android Framework提供的一個控件,而Android系統顯示控件的流程就是: Activity加載布局實例化所有控件 —> rootView遍歷所以控件 —> 對需要重繪的控件執行測量,布局,繪制的操作。 而轉化到某個控件來說,它的流程就是:構造方法 —> onMeasure —> onLayout —> onDraw 由于ViewPager的構造方法中只是初始化了一些與本文主題無關的屬性就略過不講,那么自然而然onMeasure方法就來到了我們眼前。 那么在onMeasure中ViewPager做了些什么呢?先把源碼擺出來,我進行了一些刪減。

@Override PRotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //測量ViewPager自身大小 setMeasuredDimension(getDefaultSize(0, widthMeasureSpec), getDefaultSize(0, heightMeasureSpec)); final int measuredWidth = getMeasuredWidth(); // child的寬高,占滿父控件 int childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight(); int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom(); //1.測量Decor int size = getChildCount(); for (int i = 0; i < size; ++i) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (lp != null && lp.isDecor) {//僅對Decor進行測量 //省略若干代碼,主要負責對Decor控件的測量 ... } } } mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY); mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY); // 2.從Adapter中獲取childView mInLayout = true; populate(); mInLayout = false; // 3.測量非Decor的childView size = getChildCount(); for (int i = 0; i < size; ++i) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (lp == null || !lp.isDecor) { final int widthSpec = MeasureSpec.makeMeasureSpec( (int) (childWidthSize * lp.widthFactor), MeasureSpec.EXACTLY); child.measure(widthSpec, mChildHeightMeasureSpec); } } } }

簡單總結就是三件事情。

2.1 測量Decor控件

可能很多人有些懵x了,Decor是個啥? 其實Decor是一個接口,在ViewPager內部定義的,并且該接口是沒有定義任何內容的。唯一的作用就是如果你的控件實現了Decor接口,那么你的控件就屬于DecorView了。 我們知道ViewPager的數據是通過Adapter管理的,但其實還有一種方式給ViewPager添加childView.

#layout.xml<ViewPager> <DecorView /></ViewPager>

上面這種直接在ViewPager布局內部添加控件也是可以的,但是要求DecorView必須實現Decor接口,否則將不予顯示。 在ViewPager的addView方法中會對childView進行判斷,也看一下代碼吧!

@Override public void addView(View child, int index, ViewGroup.LayoutParams params) { if (!checkLayoutParams(params)) { params = generateLayoutParams(params); } final LayoutParams lp = (LayoutParams) params; lp.isDecor |= child instanceof Decor; //在此處給isDecor賦值 //省略無關代碼 ... }

至于addView()方法是如何調用,可以參考本人博客 ViewGroup如何加載布局中的View? 而上面的代碼我們要注意的是lp.isDecor,這是ViewPager為它的childView準備的LayoutParams,在onMeasure的第一步中就是根據lp.isDecor來挑選出Decor控件來測量的。 至于Decor的測量過程與本文主題無關,在此就不詳述了,有興趣的可以自己去查看源碼。

2.2 從Adapter中創建ChildView(populate方法)

ViewPager也是采用Observable模式來設計的,數據通過PagerAdapter來管理,并且childView也是通過PagerAdapter來創建的,ViewPager主要負責界面交互相關的工作。 對PagerAdapter并不會做太詳細的介紹,直接給一個示例代碼吧。

public class AutoScrollAdapter extends PagerAdapter { //省略構造方法代碼 ... @Override public void destroyItem(ViewGroup container, int position, Object object) { } @Override public int getCount() { return mData.size(); } @Override public boolean isViewFromObject(View view, Object object) { return view == object; } @Override public Object instantiateItem(ViewGroup container, int position) { View itemView = new TextView(mContext); //通過各種方法新建一個childView container.addView(itemView);//將childView添加到ViewPager中 return itemView; }}

這四個方法是必須要重寫的,方法的含義根據方法名就能看出來。這里主要要講一下最后這個方法instantiateItem()。它負責向ViewPager提供childView,這里調用的addView方法是被ViewPager重寫過的,所以會對lp.isDecor賦值,并且我們可以知道,這里的isDecor=false。

有些人可能要問,這一步的主角不應該是populate()方法嗎?的確應該是populate方法,但是由于這個方法比較復雜,為了閱讀的連貫性考慮,博主決定單獨提出來,一會兒再講它。 在這里主要告訴大家,populate()方法內部會調用Adapter.instantiateItem()方法,也就是將Adapter中的childView添加到ViewPager中來,為下一步做準備。

2.3 測量ChildView

有了上面的分析,這一步的內容就很好理解了。 簡單來說就是,遍歷所有的childView,挑選出lp.isDecor==false的childView,然后調用view.measure()方法讓childView自己去完成測量。 還有一點需要注意,就是childView的寬度 width= childWidthSize * lp.widthFactor。 childWidthSize就是ViewPager的寬度,lp.widthFactor代表這個childView占幾個頁面。 lp.widthFactor默認情況下是1.0,可以重寫PagerAdapter.getPageWidth(pos)方法來修改這個值。 到此,ViewPager的測量過程就完成了。

3.populate()方法

可以說這是ViewPager最核心的一個方法,所以單獨作為一個小節來分析。 在分析源碼之前,必須先介紹一個類——ItemInfo

3.1 ItemInfo是什么?

static class ItemInfo { Object object; //childView int position; //childView在Adapter中的位置 boolean scrolling; //是否在滾動 float widthFactor; //寬度的倍數,默認情況下是1 float offset; //頁面的偏移參數,粗暴的理解就是第幾個頁面 }

這是ViewPager內部定義的一個靜態類,將childView相關的屬性進行了包裝,主要是為了方便對childView的管理。 并且在ViewPager內部還維護了一個ArrayList,由ItemInfo對象組成,屬性名是mItems。 這個list的長度就是由mOffscreenPageLimit來決定的,這個在后面的代碼分析中會看到。 好了,了解了基本對象之后,就可以開始分析populate方法了。 注意:由于代碼比較長,為了方便閱讀博主打算將populate()方法的代碼分段講解,如過代碼中沒有方法聲明,則表示該段代碼屬于populate()方法。

3.2 獲取當前的ItemInfo對象

從這里開始,對populate()方法的源碼進行分析,分析內容主要在代碼的注釋中編寫。

void populate(int newCurrentItem) { ItemInfo oldCurInfo = null; int focusDirection = View.FOCUS_FORWARD; if (mCurItem != newCurrentItem) { focusDirection = mCurItem < newCurrentItem ? View.FOCUS_RIGHT : View.FOCUS_LEFT; oldCurInfo = infoForPosition(mCurItem); //獲取舊的ItemInfo對象 mCurItem = newCurrentItem; //更新mCurItem的值,就是在Adapter中的position } //省略無關代碼 ... //mOffscreenPageLimit就是setOffscreenPageLimit方法設置的值 final int pageLimit = mOffscreenPageLimit; //根據下面三行代碼可知:mItems的長度就是 2 * pageLimit + 1 //這里聲明的startPos和endPos在后面會起作用,大家注意一下 final int startPos = Math.max(0, mCurItem - pageLimit); final int N = mAdapter.getCount(); final int endPos = Math.min(N-1, mCurItem + pageLimit); // 遍歷mItems列表,找出mCurItem對應的ItemInfo對象,是根據position來判斷的 int curIndex = -1; ItemInfo curItem = null; for (curIndex = 0; curIndex < mItems.size(); curIndex++) { final ItemInfo ii = mItems.get(curIndex); if (ii.position >= mCurItem) { if (ii.position == mCurItem) curItem = ii; break; } } // 如果mItems中還未保存該ItemInfo,則創建一個IntemInfo對象 if (curItem == null && N > 0) { curItem = addNewItem(mCurItem, curIndex); } ...

這里要注意的一點是,在新建ItemInfo對象時,我們是調用的addNewItem方法,它的代碼如下所示。

ItemInfo addNewItem(int position, int index) { ItemInfo ii = new ItemInfo(); //新建一個ItemInfo對象 ii.position = position; ii.object = mAdapter.instantiateItem(this, position);//用Adapter創建一個childView ii.widthFactor = mAdapter.getPageWidth(position);//默認返回1.0f if (index < 0 || index >= mItems.size()) { //添加到mItems中 mItems.add(ii); } else { mItems.add(index, ii); } return ii; }

不管是從mItems中提取還是新建一個ItemInfo對象,總之我們已經得到了curItem,即當前的IntemInfo對象。

3.3 管理mItems中的其余對象

因為我們的mItems長度是有限的,并且與pageLimit有關,所以很可能出現頁面總數大于mItems長度的情況。當顯示的頁面改變時,我們必須將一些ItemInfo添加進來,將另一些ItemInfo移除。 以保證我們的mItems中的ItemInfo.position是這樣的: [ startPos … mCurItem … endPos ] 其中: mCurItem = curItem.position startPos = mCurItem - pagLimit endPos = mCurItem + pagLimit

具體如何操作,我們來看代碼 if (curItem != null) { //1.調整curItem左邊的對象 float extraWidthLeft = 0.f; // curIndex是curItem在mItems中的索引 // itemIndex就是curItem左邊的ItemInfo的索引 int itemIndex = curIndex - 1; //獲取左邊的ItemInfo對象 ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; final int clientWidth = getClientWidth(); //curItem左邊需要的寬度,默認情況下為1.0f final float leftWidthNeeded = clientWidth <= 0 ? 0 : 2.f - curItem.widthFactor + (float) getPaddingLeft() / (float) clientWidth; //遍歷mItems左半部分,即curIndex左邊的對象 //只有在pos < startPos時才能退出循環,否則會一直遍歷到pos=0 for (int pos = mCurItem - 1; pos >= 0; pos--) { // 建議大家先從下面的else if開始看,因為這里的邏輯是準備退出循環了 if (extraWidthLeft >= leftWidthNeeded && pos < startPos) { //當pos < startPos,說明mItems左邊部分已經調整完畢了 //此時的ii代表的是,startPos左邊的對象了 if (ii == null) { break; } //如果startPos左邊還有對象,需要從mItems中移除 if (pos == ii.position && !ii.scrolling) { mItems.remove(itemIndex); mAdapter.destroyItem(this, pos, ii.object); itemIndex--; curIndex--; ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; } //如果curIndex左邊的ItemInfo對象不為null } else if (ii != null && pos == ii.position) { extraWidthLeft += ii.widthFactor; //累加curItem左邊需要的寬度 itemIndex--; //再往curIndex左邊移一個位置 ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; //取出ItemInfo對象 //如果curIndex左邊的ItemInfo為null } else { //新建一個ItemInfo對象,添加到itemIndex的右邊 ii = addNewItem(pos, itemIndex + 1); extraWidthLeft += ii.widthFactor; //累加左邊寬度 curIndex++; //由于往mItems中插入了一個對象,故curIndex需要加1 ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; //去除ItemInfo } } //2.調整curItem右邊的對象,邏輯與上面類似 //代碼省略 ... // 3.計算mItems中的偏移參數 calculatePageOffsets(curItem, curIndex, oldCurInfo); }

代碼主要是一些邏輯,需要大家靜下心來讀,也不知道講清除了沒有。(發現要把代碼翻譯成文字真是累,一句代碼要用一大段文字來說明) 對于calculatePageOffsets方法,就不貼源碼分析了,主要說一下它做了哪些事情吧

根據oldItem.position與curItem.position的大小關系,來確定curItem的offset值再分別對curItem的左邊和右邊的Item寫入offset值 mPageMargin是頁面之間的間隔, marginOffset = mPageMargin / childWidth每個頁面的offset = mAdapter.getPageWidth(pos) + marginOffset

參照上面的四點提示,大家去讀源碼應該也沒啥難度的,關鍵是都是一些邏輯處理很難文字化說明。

3.4 一些收尾工作

// 將ItemInfo的內容更新到childView的LayoutParams中 final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); lp.childIndex = i; if (!lp.isDecor && lp.widthFactor == 0.f) { final ItemInfo ii = infoForChild(child); if (ii != null) { lp.widthFactor = ii.widthFactor; lp.position = ii.position; } } } //根據lp.position的大小對所有childView進行排序,另外DecorView是排在其他child之前的 sortChildDrawingOrder();

OK,populate方法分析到此就結束了。

4. onLayout

布局也是先布局Decor,再布局Adapter創建的childView,直接上源碼吧。

@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int count = getChildCount(); int width = r - l; int height = b - t; //1.布局Decor,根據lp.isDecor來篩選DecorView //代碼略 ... final int childWidth = width - paddingLeft - paddingRight; for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); ItemInfo ii; //此處將DecorView過濾掉,并且根據view從mItems中查找ItemInfo對象 //如果ViewPager布局中添加了未實現Decor接口的控件,將不會被布局 //因為無法從mItems中查找到ItemInfo對象 if (!lp.isDecor && (ii = infoForChild(child)) != null) { //計算當前page的左邊界偏移值,此處的offset會隨著頁面增加而增加 int loff = (int) (childWidth * ii.offset); int childLeft = paddingLeft + loff; int childTop = paddingTop; if (lp.needsMeasure) {//如果需要重新測量,則重新測量之 lp.needsMeasure = false; final int widthSpec = MeasureSpec.makeMeasureSpec( (int) (childWidth * lp.widthFactor), MeasureSpec.EXACTLY); final int heightSpec = MeasureSpec.makeMeasureSpec( (int) (height - paddingTop - paddingBottom), MeasureSpec.EXACTLY); child.measure(widthSpec, heightSpec); } //child調用自己的layout方法來布局自己 child.layout(childLeft, childTop, childLeft + child.getMeasuredWidth(), childTop + child.getMeasuredHeight()); } } } mTopPageBounds = paddingTop; mBottomPageBounds = height - paddingBottom; mDecorChildCount = decorCount; //如果是首次布局,則會調用scrollToItem方法 if (mFirstLayout) { scrollToItem(mCurItem, false, 0, false); } mFirstLayout = false; }

布局這一塊的代碼相對來說要簡單一些,就是根據offset偏移量來計算出left,right, top, bottom值,然后直接調用View.layout方法進行布局。 但是,這里需要插一句,在用ViewPager實現輪播控件時,有一種方法是將Adapter.getCount返回Integer.MAX_VALUE,已達到偽循環播放的目的。從上面的代碼可以看到,此時這個offset值會不斷的變大,那么

int loff = (int) (childWidth * ii.offset);

這個loff很可能會超出int的最大值邊界。 所以,以后大家實現輪播控件時,還是不要采用這種方法了。

然后,回過頭來再說下scrollToItem方法 注意上面調用scrollToItem時,最后一個參數傳遞的是false,而這個參數就是決定是否調用onPageSelected回調函數的。 看代碼:

private void scrollToItem(int item, boolean smoothScroll, int velocity, boolean dispatchSelected) { final ItemInfo curInfo = infoForPosition(item); int destX = 0; if (curInfo != null) { final int width = getClientWidth(); destX = (int) (width * Math.max(mFirstOffset, Math.min(curInfo.offset, mLastOffset))); } if (smoothScroll) { smoothScrollTo(destX, 0, velocity); if (dispatchSelected) { dispatchOnPageSelected(item); } } else { if (dispatchSelected) { //是否需要分發OnPageSelected回調 dispatchOnPageSelected(item); } completeScroll(false); scrollTo(destX, 0); pageScrolled(destX); } }

也就是說,第一次布局ViewPager時雖然會顯示一個頁面,卻不會調用onPageSelected方法。

onLayout的分析也到此結束了,至于onDraw方法ViewPager并沒有做什么,只是編寫了繪制Page之間間隔的代碼,就不做分析了。

當然,ViewPager的代碼還不止這些,此文分析的僅僅是它的骨架,還有許多其他處理如onInterceptTouchEvent方法,pageScrolled方法等等,這些就留給讀者自己去分析吧。 理解了這邊文章之后,對ViewPager的工作原理也有一定程度的了解了,相信再去讀那些代碼難度不會很大。 至于篇頭提到的三個問題,相信各位也已經有了答案。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产精品专区第二| 国产精品久久久久久av| 91av福利视频| 成人免费激情视频| 伊人久久精品视频| 欧美精品激情在线| 日韩中文字幕免费视频| 欧美极品美女电影一区| 中文字幕9999| 成人免费视频在线观看超级碰| 亚洲男人的天堂网站| 久久免费精品日本久久中文字幕| 亚洲精品在线观看www| 深夜成人在线观看| 国产精品网站大全| 夜夜嗨av一区二区三区免费区| 中文字幕成人精品久久不卡| 一区二区在线视频播放| 欧美一区三区三区高中清蜜桃| 欧美亚洲国产精品| 97视频人免费观看| 疯狂做受xxxx欧美肥白少妇| 国产精品91一区| 日韩精品极品视频| 亚洲国产成人精品一区二区| 欧美一区二区三区……| 亚洲一区二区三区成人在线视频精品| 九九综合九九综合| 日韩中文字幕在线精品| 国产精品99久久久久久久久| 日韩精品一区二区三区第95| 国产成人精品一区| 久久久国产视频91| 精品亚洲一区二区三区在线观看| 91久久国产婷婷一区二区| 欧美自拍视频在线| 欧美精品video| 久久久97精品| 美女少妇精品视频| 韩国19禁主播vip福利视频| 国产精品久久久久久久美男| 国产精品福利久久久| 亚洲国产成人久久综合| 欧美激情在线观看视频| 中文字幕亚洲欧美在线| 欧美激情乱人伦一区| 中文字幕在线看视频国产欧美| 日韩成人av在线播放| 欧美亚洲激情在线| 美女久久久久久久久久久| 51色欧美片视频在线观看| 奇米4444一区二区三区| 亚洲国产精品久久久久秋霞蜜臀| 亚洲欧美在线看| 欧美黄色三级网站| 亚洲成人a级网| 久久成人av网站| 亚洲一区中文字幕在线观看| 91牛牛免费视频| 久久国产精品偷| 91亚洲国产成人久久精品网站| 中文字幕无线精品亚洲乱码一区| 亚洲护士老师的毛茸茸最新章节| 另类色图亚洲色图| 日韩成人久久久| 日韩中文字幕在线观看| 欧美一级在线亚洲天堂| 亚洲国产精久久久久久久| 亚洲视频视频在线| 国产精品成人va在线观看| 成人午夜在线影院| 亚洲一区二区在线播放| 国产一区视频在线播放| 亚洲国产日韩一区| 日韩免费视频在线观看| 日韩电影第一页| 欧美黑人一区二区三区| 色无极亚洲影院| 久久精品国产2020观看福利| 一区二区三区在线播放欧美| 国产成人精品视频| 久久视频在线播放| 中文字幕日韩精品在线观看| 中文字幕av一区二区三区谷原希美| 国产+成+人+亚洲欧洲| 亚洲码在线观看| 国产不卡精品视男人的天堂| 日韩精品免费在线视频| 91社区国产高清| 国产精品久久久久影院日本| 欧美成人剧情片在线观看| 成人高清视频观看www| 日韩欧美国产视频| 亚洲国产精彩中文乱码av| 国产成人欧美在线观看| 欧美香蕉大胸在线视频观看| 日韩电影在线观看免费| 一本一本久久a久久精品牛牛影视| 国产情人节一区| 久久综合久久88| 久久久久久久久久久免费精品| 日韩av在线免费播放| 欧美视频免费在线观看| 亚洲男人的天堂在线| 成人国产精品日本在线| 一区二区三欧美| 久久成年人视频| 91精品国产99久久久久久| 欧美日韩国产一区二区| 亚洲欧美国产精品专区久久| 日韩成人高清在线| 国产精品成人aaaaa网站| 国产精品精品久久久久久| 亚洲电影免费观看高清完整版| 国内精品久久久久| 国产精品极品在线| 日韩av电影手机在线| 国产在线精品成人一区二区三区| 久久综合色88| 国产精欧美一区二区三区| 亚洲黄色免费三级| 国产69精品久久久久9| 久久成人国产精品| 久久久免费高清电视剧观看| 亚洲亚裔videos黑人hd| 狠狠色香婷婷久久亚洲精品| 欧美日韩在线第一页| 日韩视频中文字幕| 91久久久国产精品| 亚洲va电影大全| www.日本久久久久com.| 777777777亚洲妇女| 国产日韩一区在线| 国产精品久久久久久久天堂| 国产精品午夜视频| 欧美性受xxx| 日本亚洲欧洲色α| 亚洲精品www久久久久久广东| 日本精品免费观看| 久久久亚洲国产天美传媒修理工| 一本色道久久综合狠狠躁篇怎么玩| 国产精品免费久久久| 欧美成人黑人xx视频免费观看| 91精品视频网站| 精品亚洲va在线va天堂资源站| 理论片在线不卡免费观看| 欧美成人免费观看| 91精品啪在线观看麻豆免费| 国产精品一区专区欧美日韩| 91情侣偷在线精品国产| 亚洲国产精品高清久久久| 亚洲黄色有码视频| 另类天堂视频在线观看| 国产欧美精品日韩精品| 亚洲欧洲自拍偷拍| 欧美日韩黄色大片| 欧美黑人xxxⅹ高潮交| 精品国产1区2区| 国产精品一区=区| 亚洲欧美日韩一区二区三区在线| 日韩风俗一区 二区| 中文字幕日韩欧美在线视频| 亚洲精品av在线播放| 欧美激情一区二区三级高清视频|