以下一个非常常见的需求场景:
应用顶部的广告使用ViewPager实现自动滑动切换,但是另我们头疼的是ViewPager本身并没有可是实现循环滑动的设置项,看了几篇blog,实现都不是很理想,所以决定自己动手。为ViewPager添加滑动页主要依赖于PagerAdapter,里面有四个主角方法:
@Override
public Object instantiateItem(ViewGroup container, int position) {
//页面显示和滑动过程中添加页面,比如滑动到第2页,会调用该方法提前加载第三页
}
@Override
public int getCount() {
//获得总页数
}
@Override
public boolean isViewFromObject(View view, Object object) {
//Google的建议写法:没有做详细探究
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
//页面显示和滑动过程中删除页面,比如滑动到第2页,会调用该方法将不在前后页(第2页的前后页为1、3页)的第0页移除
}
看完上面的介绍,相信已经有了思路,简单粗暴的方法就是将传入要显示的页面扩大到一个非常大的倍数,导致一直能够滑动下去,但这不是一个好方法。既然是页数决定能够滑动的次数,两个方法(instantiateItem()和destroyItem())决定滑动的过程中该添加哪一页和删去哪一页,那我们可以想到一个基本的思路——利用这三个方法加上除数取模的方式可以搞点事情:将getCount()的返回值放大到一个非常大的倍数,但是实际传入的List页数却并不放大,每次都通过用原本的List来取模的方式增加或删减页数。以下是实现方式:
public class SerialPagerAdapter extends PagerAdapter {
private List imageViewList = new ArrayList<>();
private int viewCount = 0;
public SerialPagerAdapter(Context context, List list) {
viewCount = list.size();
for (int i = 0; i < list.size(); i++) {
ImageView imageView = new ImageView(context);
imageView.setImageBitmap(list.get(i));
imageViewList.add(imageView);
}
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
container.addView(imageViewList.get(position % viewCount), 0);
return imageViewList.get(position % viewCount);
}
@Override
public int getCount() {
return viewCount * 10000;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView(imageViewList.get(position % viewCount));
}
}
我的一个项目Demo中随便引用了4个假的图片数据,“完美”运行,故以上的代码用了很长时间,但是当真的数据(3张宣传页)来临的时候,竟然神奇的发现崩溃了,产生了一个常见错误:
java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child’s parent first.
以上是当页面数量为3的时候出现崩溃原因的一张分析图:其中
通过打印log可以知道,每次切换到后一个页面,都是先删除不在紧邻旁边的页面添加下一页预览页面,而每次切换到前一个页面,过程相反,先add页面,再remove页面,这也就是以上异常崩溃原因(log请自行在两个方法中打印验证)。
分析图中,当从第二步执行到第三步的时候,先remove了页面0,再添加了页面3,但其实该页面在List中的下标为0,即list.size()%3 = 3%3 = 0,所以结果是2->3步骤,先remove页面0,再add页面0,但是往回滑动的时候,是先add页面,再remove页面,而3->2步骤为滑动到第1页,刚好要先add页面0,而上一步add的页面0还没有remove,导致了以上抛出的异常!为什么页数大于等于4的时候不会抛出异常,可以通过该方式分析。
既然原因找到了,那就直接说解决方案,很简单,还是放大List的倍数,当数量为1的时候,放到到4个条目,当为2的时候,也是放大4个条目,当数量为3的时候,很不巧,为了解决这个问题你要放大到6个条目,其实只要添加到4个就不会崩溃,但是选择放大倍数是不想图片滑动过程中顺序错乱,再说最大6个也是可以接受的!以下是解决之后的完整PagerAdapter代码,传入的List泛型为Bitmap,如果想传如其他可以自行修改,此处只是提供了一种解决思路。
完整Demo请访问Github链接,里面还有一个小福利(简单自定义的页面指示器,只是当前滑动到了哪一页的小圆点):
https://github.com/shixiuwen/SerialPagerAdapter
至于连续滑动,相信方法有很多,在这里使用RxJava的interval()方法实现,代码简洁:
vpBitmap.setAdapter(new SerialPagerAdapter(this, getBitmapList()));
pciIndicator.setPageCount(getBitmapList().size()); //设置下标点的个数
//设置自动切换
Observable.interval(2, TimeUnit.SECONDS)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1() {
@Override
public void call(Long aLong) {
vpBitmap.setCurrentItem(vpBitmap.getCurrentItem() + 1, true);
}
});
以下是PagerAdapter完整代码: