ViewPager源码分析.docx

上传人:b****8 文档编号:12958060 上传时间:2023-06-09 格式:DOCX 页数:17 大小:22.31KB
下载 相关 举报
ViewPager源码分析.docx_第1页
第1页 / 共17页
ViewPager源码分析.docx_第2页
第2页 / 共17页
ViewPager源码分析.docx_第3页
第3页 / 共17页
ViewPager源码分析.docx_第4页
第4页 / 共17页
ViewPager源码分析.docx_第5页
第5页 / 共17页
ViewPager源码分析.docx_第6页
第6页 / 共17页
ViewPager源码分析.docx_第7页
第7页 / 共17页
ViewPager源码分析.docx_第8页
第8页 / 共17页
ViewPager源码分析.docx_第9页
第9页 / 共17页
ViewPager源码分析.docx_第10页
第10页 / 共17页
ViewPager源码分析.docx_第11页
第11页 / 共17页
ViewPager源码分析.docx_第12页
第12页 / 共17页
ViewPager源码分析.docx_第13页
第13页 / 共17页
ViewPager源码分析.docx_第14页
第14页 / 共17页
ViewPager源码分析.docx_第15页
第15页 / 共17页
ViewPager源码分析.docx_第16页
第16页 / 共17页
ViewPager源码分析.docx_第17页
第17页 / 共17页
亲,该文档总共17页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

ViewPager源码分析.docx

《ViewPager源码分析.docx》由会员分享,可在线阅读,更多相关《ViewPager源码分析.docx(17页珍藏版)》请在冰点文库上搜索。

ViewPager源码分析.docx

ViewPager源码分析

ViewPager源码分析

1.问题

由于AndroidFramework源码很庞大,所以读源码必须带着问题来读!

没有问题,创造问题再来读!

否则很容易迷失在无数的方法与属性之中,最后无功而返。

那么,关于ViewPager有什么问题呢?

1.setOffsreenPageLimit()方法是如何实现页面缓存的?

2.在布局文件中,ViewPager布局内部能否添加其他View?

3.为什么ViewPager初始化时,显示了一个页面却不会触发onPageSelected回调?

问题肯定不止这三个,但是有这三个问题基本可以找到本次分析的重点了。

读者朋友也可以自己先提出一些问题,再看下面的分析,看看是否可以从分析过程中找到答案。

2.从onMeasure()下手

ViewPager继承自ViewGroup,是AndroidFramework提供的一个控件,而Android系统显示控件的流程就是:

Activity加载布局实例化所有控件—>rootView遍历所以控件—>对需要重绘的控件执行测量,布局,绘制的操作。

而转化到某个控件来说,它的流程就是:

构造方法—>onMeasure—>onLayout—>onDraw

由于ViewPager的构造方法中只是初始化了一些与本文主题无关的属性就略过不讲,那么自然而然onMeasure方法就来到了我们眼前。

那么在onMeasure中ViewPager做了些什么呢?

先把源码摆出来,我进行了一些删减。

@Override

protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){

//测量ViewPager自身大小

setMeasuredDimension(getDefaultSize(0,widthMeasureSpec),

getDefaultSize(0,heightMeasureSpec));

finalintmeasuredWidth=getMeasuredWidth();

//child的宽高,占满父控件

intchildWidthSize=measuredWidth-getPaddingLeft()-getPaddingRight();

intchildHeightSize=getMeasuredHeight()-getPaddingTop()-getPaddingBottom();

//1.测量Decor

intsize=getChildCount();

for(inti=0;i

finalViewchild=getChildAt(i);

if(child.getVisibility()!

=GONE){

finalLayoutParamslp=(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(inti=0;i

finalViewchild=getChildAt(i);

if(child.getVisibility()!

=GONE){

finalLayoutParamslp=(LayoutParams)child.getLayoutParams();

if(lp==null||!

lp.isDecor){

finalintwidthSpec=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必须实现Decor接口,否则将不予显示。

在ViewPager的addView方法中会对childView进行判断,也看一下代码吧!

@Override

publicvoidaddView(Viewchild,intindex,ViewGroup.LayoutParamsparams){

if(!

checkLayoutParams(params)){

params=generateLayoutParams(params);

}

finalLayoutParamslp=(LayoutParams)params;

lp.isDecor|=childinstanceofDecor;//在此处给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并不会做太详细的介绍,直接给一个示例代码吧。

publicclassAutoScrollAdapterextendsPagerAdapter{

//省略构造方法代码

...

@Override

publicvoiddestroyItem(ViewGroupcontainer,intposition,Objectobject){

}

@Override

publicintgetCount(){

returnmData.size();

}

@Override

publicbooleanisViewFromObject(Viewview,Objectobject){

returnview==object;

}

@Override

publicObjectinstantiateItem(ViewGroupcontainer,intposition){

ViewitemView=newTextView(mContext);//通过各种方法新建一个childView

container.addView(itemView);//将childView添加到ViewPager中

returnitemView;

}

}

这四个方法是必须要重写的,方法的含义根据方法名就能看出来。

这里主要要讲一下最后这个方法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.1ItemInfo是什么?

staticclassItemInfo{

Objectobject;//childView

intposition;//childView在Adapter中的位置

booleanscrolling;//是否在滚动

floatwidthFactor;//宽度的倍数,默认情况下是1

floatoffset;//页面的偏移参数,粗暴的理解就是第几个页面

}

这是ViewPager内部定义的一个静态类,将childView相关的属性进行了包装,主要是为了方便对childView的管理。

并且在ViewPager内部还维护了一个ArrayList,由ItemInfo对象组成,属性名是mItems。

这个list的长度就是由mOffscreenPageLimit来决定的,这个在后面的代码分析中会看到。

好了,了解了基本对象之后,就可以开始分析populate方法了。

注意:

由于代码比较长,为了方便阅读博主打算将populate()方法的代码分段讲解,如过代码中没有方法声明,则表示该段代码属于populate()方法。

3.2获取当前的ItemInfo对象

从这里开始,对populate()方法的源码进行分析,分析内容主要在代码的注释中编写。

voidpopulate(intnewCurrentItem){

ItemInfooldCurInfo=null;

intfocusDirection=View.FOCUS_FORWARD;

if(mCurItem!

=newCurrentItem){

focusDirection=mCurItem

View.FOCUS_RIGHT:

View.FOCUS_LEFT;

oldCurInfo=infoForPosition(mCurItem);//获取旧的ItemInfo对象

mCurItem=newCurrentItem;//更新mCurItem的值,就是在Adapter中的position

}

//省略无关代码

...

//mOffscreenPageLimit就是setOffscreenPageLimit方法设置的值

finalintpageLimit=mOffscreenPageLimit;

//根据下面三行代码可知:

mItems的长度就是2*pageLimit+1

//这里声明的startPos和endPos在后面会起作用,大家注意一下

finalintstartPos=Math.max(0,mCurItem-pageLimit);

finalintN=mAdapter.getCount();

finalintendPos=Math.min(N-1,mCurItem+pageLimit);

//遍历mItems列表,找出mCurItem对应的ItemInfo对象,是根据position来判断的

intcurIndex=-1;

ItemInfocurItem=null;

for(curIndex=0;curIndex

finalItemInfoii=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方法,它的代码如下所示。

ItemInfoaddNewItem(intposition,intindex){

ItemInfoii=newItemInfo();//新建一个ItemInfo对象

ii.position=sition;

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);

}

returnii;

}

不管是从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左边的对象

floatextraWidthLeft=0.f;

//curIndex是curItem在mItems中的索引

//itemIndex就是curItem左边的ItemInfo的索引

intitemIndex=curIndex-1;

//获取左边的ItemInfo对象

ItemInfoii=itemIndex>=0?

mItems.get(itemIndex):

null;

finalintclientWidth=getClientWidth();

//curItem左边需要的宽度,默认情况下为1.0f

finalfloatleftWidthNeeded=clientWidth<=0?

0:

2.f-curItem.widthFactor+(float)getPaddingLeft()/(float)clientWidth;

//遍历mItems左半部分,即curIndex左边的对象

//只有在pos

for(intpos=mCurItem-1;pos>=0;pos--){

//建议大家先从下面的elseif开始看,因为这里的逻辑是准备退出循环了

if(extraWidthLeft>=leftWidthNeeded&&pos

//当pos

//此时的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

}elseif(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中

finalintchildCount=getChildCount();

for(inti=0;i

finalViewld=getChildAt(i);

finalLayoutParamslp=(LayoutParams)child.getLayoutParams();

lp.childIndex=i;

if(!

lp.isDecor&&lp.widthFactor==0.f){

finalItemInfoii=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

protectedvoidonLayout(booleanchanged,intl,intt,intr,intb){

finalintcount=getChildCount();

intwidth=r-l;

intheight=b-t;

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 总结汇报 > 学习总结

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2