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=mCurItemView.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;curIndexfinalItemInfoii=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左边的对象
//只有在posfor(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;ifinalViewld=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;