非常感谢 Jude95 为我们分装的一个开源的轮播图控件。以下则跟着作者的思路阅读一下漂亮的源码~
集成:
最终你会看到这样的图:
工程结构是这样的, 我们主要分析的也是红框标注的主要内容:
- RollPagerView.java
private HintViewDelegate mHintViewDelegate = new HintViewDelegate() {
@Override
public void setCurrentPosition(int position,HintView hintView) {
if(hintView!=null)
hintView.setCurrent(position);
}
@Override
public void initView(int length, int gravity,HintView hintView) {
if (hintView!=null)
hintView.initView(length,gravity);
}
};
这个就是一个监听器,里边的hintView
就是我们看到的 指示器(点 点 点),
public void setHintViewDelegate(HintViewDelegate delegate){
this.mHintViewDelegate = delegate;
}
/**
* 支持自定义hintview
* 只需new一个实现HintView的View传进来
* 会自动将你的view添加到本View里面。重新设置LayoutParams。
* @param hintview
*/
public void setHintView(HintView hintview){
if (mHintView != null) {
removeView(mHintView);
}
this.mHintView = (View) hintview;
if (hintview!=null&&hintview instanceof View){
initHint(hintview);
}
}
public interface HintView {
void initView(int length, int gravity);
void setCurrent(int current);
}
可以看出 HintView
是一个接口,所以我们可以自定义hintview,并且可以在mHintViewDelegate
中实现自定义操作。(mHintViewDelegate
在除了初始化、自动播放的位置会调用,onPageSelected
也会调用,所以hintview可以与viewpager同步),其余的就是一些自定义样式的设置和自动播放定时器的逻辑代码。
接下来我们看下 StaticPagerAdapter.java -- 不可循环的viewpager
public abstract class StaticPagerAdapter extends PagerAdapter {
private ArrayList<View> mViewList = new ArrayList<>();
@Override
public boolean isViewFromObject(View arg0, Object arg1) {
return arg0==arg1;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
@Override
public void notifyDataSetChanged() {
mViewList.clear();
super.notifyDataSetChanged();
}
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
View itemView = findViewByPosition(container,position);
container.addView(itemView);
onBind(itemView,position);
return itemView;
}
private View findViewByPosition(ViewGroup container,int position){
for (View view : mViewList) {
if (((int)view.getTag()) == position&&view.getParent()==null){
return view;
}
}
View view = getView(container,position);
view.setTag(position);
mViewList.add(view);
return view;
}
public void onBind(View view,int position){
}
public abstract View getView(ViewGroup container, int position);
}
比较关键的方法是findViewByPosition
,其实大体思路是这样的,默认情况下,viewpager 加载当前pager和相邻的pager, 所以每当有新的pager被加载时都会进入 instantiateItem
方法,而 非当前pager和相邻pager,会被销毁,即进入destroyItem
, instantiateItem
有些类似ListView#getView
,mViewList
这里也是对view做了缓存机制,getView
则是需要开发者自己去实现,加载什么样的view。
之后,一起来看下LoopPagerAdapter.java -- 可循环的viewpager
What? 咋循环?
我们来看代码:
public abstract class LoopPagerAdapter extends PagerAdapter{
private RollPagerView mViewPager;
private ArrayList<View> mViewList = new ArrayList<>();
private class LoopHintViewDelegate implements RollPagerView.HintViewDelegate{
@Override
public void setCurrentPosition(int position, HintView hintView) {
if (hintView!=null&&getRealCount()>0)
hintView.setCurrent(position%getRealCount());
}
@Override
public void initView(int length, int gravity, HintView hintView) {
if (hintView!=null)
hintView.initView(getRealCount(),gravity);
}
}
@Override
public void notifyDataSetChanged() {
mViewList.clear();
initPosition();
super.notifyDataSetChanged();
}
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
@Override
public void registerDataSetObserver(DataSetObserver observer) {
super.registerDataSetObserver(observer);
initPosition();
}
private void initPosition(){
if (mViewPager.getViewPager().getCurrentItem() == 0&&getRealCount()>0){
int half = Integer.MAX_VALUE/2;
int start = half - half%getRealCount();
setCurrent(start);
}
}
private void setCurrent(int index){
try {
Field field = ViewPager.class.getDeclaredField("mCurItem");
field.setAccessible(true);
field.set(mViewPager.getViewPager(),index);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public LoopPagerAdapter(RollPagerView viewPager){
this.mViewPager = viewPager;
viewPager.setHintViewDelegate(new LoopHintViewDelegate());
}
@Override
public boolean isViewFromObject(View arg0, Object arg1) {
return arg0==arg1;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
int realPosition = position%getRealCount();
View itemView = findViewByPosition(container,realPosition);
container.addView(itemView);
return itemView;
}
private View findViewByPosition(ViewGroup container,int position){
for (View view : mViewList) {
if (((int)view.getTag()) == position&&view.getParent()==null){
return view;
}
}
View view = getView(container,position);
view.setTag(position);
mViewList.add(view);
return view;
}
public abstract View getView(ViewGroup container, int position);
@Deprecated
@Override
public final int getCount() {
return getRealCount()<=0?getRealCount():Integer.MAX_VALUE;
}
public abstract int getRealCount();
}
这里只说与StaticPagerAdapter
的区别,区别在哪呢,注意多了一个getRealCount
方法,而getCount
被重写,看吧,真正的count是Integer.MAX_VALUE
,再看下 instantiateItem
:
int realPosition = position%getRealCount();
所以,真相大白了, getCount
是Integer.MAX_VALUE
, 所以viewpager可以一直滑动,也就是说 instantiateItem
会在一直滑动的过程中被不停的调用,记得之前说的mViewList
吧,这里就起了作用,每个view都有个setTag
是 realPosition
, 比如现在有3个有效的pager,当滑到第四个页面时, realCount
为 3 % 3 = 0, 所以 mViewList
里边tag为0的view就会被取出来复用, 这样,就既简单又完美地达到了viewpager 循环滚动~
以上将RollPagerView的主要部分进行了分析,其余的细节就留给每个人自己吧,多看源码,多些感慨,多些成长,Cheer up!!