ViewPager实现循环滑动

来源:互联网 发布:截图软件百度云 编辑:程序博客网 时间:2024/05/16 05:00

ViewPager实现循环滑动

以下一个非常常见的需求场景:
应用顶部的广告使用ViewPager实现自动滑动切换,但是另我们头疼的是ViewPager本身并没有可是实现循环滑动的设置项,看了几篇blog,实现都不是很理想,所以决定自己动手。为ViewPager添加滑动页主要依赖于PagerAdapter,里面有四个主角方法:

@Overridepublic Object instantiateItem(ViewGroup container, int position) {  //页面显示和滑动过程中添加页面,比如滑动到第2页,会调用该方法提前加载第三页}@Overridepublic int getCount() {  //获得总页数}@Overridepublic boolean isViewFromObject(View view, Object object) {  //Google的建议写法:没有做详细探究}@Overridepublic 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<ImageView> imageViewList = new ArrayList<>();    private int viewCount = 0;    public SerialPagerAdapter(Context context, List<Bitmap> 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的滑动情况分析图

以上是当页面数量为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<Long>() {               @Override               public void call(Long aLong) {                  vpBitmap.setCurrentItem(vpBitmap.getCurrentItem() + 1, true);                    }                });

以下是PagerAdapter完整代码:

/** * Created by ShiXiuwen on 2016/12/08. * <p> * Description: 对应主界面自动滑动的ViewPager * <p> * 为了实现连续滑动,将数组count放大10000倍,实际上未放大10000View的 * 个数,只是用了除数取余的方法由于ViewPager会缓存前后两个View,所以导 * 致在个数小于3的时候会出现重复添加的情况,故如果内容页面小于等于三,将 * 数组放大到大于等于三即可(4,6),如果内容页大于3,则可直接使用 */public class SerialPagerAdapter extends PagerAdapter {    private List<ImageView> imageViewList = new ArrayList<>();    private int oldViewCount = 0;    private int newViewCount;  //如果内容页小于等于三,放大倍数到大于3    public SerialPagerAdapter(Context context, List<Bitmap> list) {        oldViewCount = list.size();        List<Bitmap> newList = plusImageViewList(list);//将图片数量增加到>=4        newViewCount = newList.size();        for (int i = 0; i < newList.size(); i++) {            ImageView imageView = new ImageView(context);            imageView.setImageBitmap(newList.get(i));            imageViewList.add(imageView);        }    }    /**     * 扩大图片列表数量到 >=4 的状态     */    private List<Bitmap> plusImageViewList(List<Bitmap> list) {        if (list.size() == 1) {   //添加到4个数据            for (int i = 0; i < 3; i++) {                list.add(list.get(0));            }        }        if (list.size() == 2) {   //添加到4个数据            list.add(list.get(0));            list.add(list.get(1));        }        if(list.size() == 3){   //添加到6个数据            list.add(list.get(0));            list.add(list.get(1));            list.add(list.get(2));        }        return list;    }    @Override    public Object instantiateItem(ViewGroup container, int position) {        Log.e("amos->add", "position:" + position + " " + "position % newViewCount:" + position % newViewCount);        container.addView(imageViewList.get(position % newViewCount), 0);        return imageViewList.get(position % newViewCount);    }    @Override    public int getCount() {        return oldViewCount * 10000;    }    @Override    public boolean isViewFromObject(View view, Object object) {        return view == object;    }    @Override    public void destroyItem(ViewGroup container, int position, Object object) {        Log.e("amos->remove", "position:" + position + " " + "position % newViewCount:" + position % newViewCount);        container.removeView(imageViewList.get(position % newViewCount));    }}
1 0
原创粉丝点击