(4.2.44)LoopingViewPager实现循环滚动

一、项目地址

只需要在原来的开发人员写的界面的基础上添加二个界面就可以了,就是原来的count数量上变为count+2

大神Jake Wharton也是用的这种方式:

You can see a sample usage on ViewPagerIndicator fork (by Jake Wharton)
or on PagerSlidingTabStrip fork (by Andreas Stütz)

二、使用

直接替换< android.support.v4.view.ViewPager>为< com.xs.view.LoopViewPager>即可

然后其它的用法和官方的ViewPager的用法一样
  • instantiateItem() 方法父组件的处理:通常我们会直接addView,但这里如果直接这样写,则会抛出IllegalStateException。假设一共有三个view,则当用户滑到第四个的时候就会触发这个异常,原因是我们试图把一个有父组件的View添加到另一个组件

  • destroyItem() 方法:由于我们在instantiateItem()方法中已经处理了remove的逻辑,因此这里并不需要处理。

    • 实际上,实验表明这里如果加上了remove的调用,则会出现ViewPager的内容为空的情况。具体原因可以参考上面的ViewPager的原理,比如说当前是最后一个位置4,向右滑动肯定要到0位置的,但是0位置已经被销毁了,所以View就不存在了
public class MyViewPagerAdapter extends PagerAdapter{
     private List<View> mListViews;
     public MyViewPagerAdapter(List<View> mListViews) {
        this.mListViews = mListViews;//构造方法,参数是我们的页卡,这样比较方便。
     }

    //直接继承PagerAdapter,至少必须重写下面的四个方法,否则会报错
     @Override
     public void destroyItem(ViewGroup container, int position, Object object)  {   
        //container.removeView(mListViews.get(position));//删除页卡
      }

     @Override
     public Object instantiateItem(ViewGroup container, int position){
        //这个方法用来实例化页卡
        if(mListViews.get(position).getParent != null){
        ((ViewGroup)mListViews.get(position).getParent()).removeView(mListViews.get(position));
        }
        container.addView(mListViews.get(position), 0);//添加页卡
        return mListViews.get(position);       
     }

     @Override
     public int getCount() {            
        return  mListViews.size();//返回页卡的数量
     }


     @Override
     public boolean isViewFromObject(View arg0, Object arg1) {          
        return arg0==arg1;//官方提示这样写
     }
 }

需要注意的是:

  1. 如果你的PagerAdapter仅用于创建View(也就是不使用FragmentPagerAdapter or FragmentStatePagerAdapter),那么完全不需要修改相关代码
  2. 如果你想把LoopViewPager用于FragmentPagerAdapter or FragmentStatePagerAdapter,必须在adapter中加入一些自定义的改变
  3. 在显示头尾界面时可能会出现闪烁(譬如你使用了NetworkImageView),你可以通过设置setBoundaryCaching( true ) 来设置缓存,这样头尾界面就不会每次都加载网络数据,而是使用缓存的

三、原理

比如现在有二个View要循环切换,显示的是ONE 和 TWO

| ONE | TWO |

那如何能让它循环呢。其实这时候是用了一个假象:

  • 比如TWO按理再往左边移动。这时候我们应该要能看到ONE。这样我们才能感觉这是循环,所以我们再TWO的右边再加一个ONE。
  • 同理ONE的界面往右移动也要能看到TWO,所以在ONE的左边加一个TWO
| TWO | ONE | TWO | ONE |
   0     1     2     3

//既然我们最左边加了一个<0>位置的TWO。我们原先的ONE就变到了<1>位置,所以在刚开始的时候初始化的位置是1而不是0   
  • 然后当我们的处于<2>位置的TWO界面朝左边移动的时候,先是能看到<3>位置的ONE了。这时候在划动过程中先给你一种感觉,以为是看到的是<1>位置的ONE
    • 然后当划动结束的时候,通过ViewPager.setCurrentItem(1)方法,将页面定位到了<1>位置的ONE,这时候你发现,又可以继续朝右边移动,然后又能看到<2>位置的TWO了

所以,其实划动时候看到的ONE不是你最刚开始看到的<1>位置的ONE界面。但当切换界面的滑动动作全部结束之后。通过ViewPager.setCurrentItem方法,把界面重新移动回到了最刚开始的<1>位置的ONE。

四、 原码分析

这里主要有两个类LoopPagerAdapterWrapper和LoopViewPager

4.1 LoopPagerAdapterWrapper

其实是类似代理模式的实现,LoopPagerAdapterWrapper持有真正的PagerAdapter,但是重写了相关方法来实现数据源的映射关系

public class LoopPagerAdapterWrapper extends PagerAdapter {
    //构造函数,既LoopPagerAdapterWrapper里面的mAdapter就是我们传入的PagerAdapter
    LoopPagerAdapterWrapper(PagerAdapter adapter) {
        this.mAdapter = adapter;
    }

    //在getCount方法我们发现跟我们前面说的一样,因为要增加头尾二个界面,所以count这时候要在我们传入的PagerAdapter的个数基础上再加上2
    @Override
    public int getCount() {
        return mAdapter.getCount() + 2;
    }

    //实现映射规则,将其转换为实际的PageAdapter中的显示项
    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        int realPosition = (mAdapter instanceof FragmentPagerAdapter || mAdapter instanceof FragmentStatePagerAdapter)
                ? position
                : toRealPosition(position);

        if (mBoundaryCaching) {
            ToDestroy toDestroy = mToDestroy.get(position);
            if (toDestroy != null) {
                mToDestroy.remove(position);
                return toDestroy.object;
            }
        }
        return mAdapter.instantiateItem(container, realPosition);
    }
    public int toInnerPosition(int realPosition) {
        int position = (realPosition + 1);
        return position;
    }
    int toRealPosition(int position) {
        int realCount = getRealCount();
        if (realCount == 0)
            return 0;
        int realPosition = (position-1) % realCount;
        if (realPosition < 0)
            realPosition += realCount;

        return realPosition;
    }
    //实现映射规则
    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        int realFirst = getRealFirstPosition();
        int realLast = getRealLastPosition();
        int realPosition = (mAdapter instanceof FragmentPagerAdapter || mAdapter instanceof FragmentStatePagerAdapter)
                ? position
                : toRealPosition(position);

        if (mBoundaryCaching && (position == realFirst || position == realLast)) {
            mToDestroy.put(position, new ToDestroy(container, realPosition,
                    object));
        } else {
            mAdapter.destroyItem(container, realPosition, object);
        }
    }
    /*
     * 代理模式
     */
    @Override
    public void finishUpdate(ViewGroup container) {
        mAdapter.finishUpdate(container);
    }
    @Override
    public boolean isViewFromObject(View view, Object object) {
        return mAdapter.isViewFromObject(view, object);
    }
    @Override
    public void restoreState(Parcelable bundle, ClassLoader classLoader) {
        mAdapter.restoreState(bundle, classLoader);
    }
    @Override
    public Parcelable saveState() {
        return mAdapter.saveState();
    }
    @Override
    public void startUpdate(ViewGroup container) {
        mAdapter.startUpdate(container);
    }
    @Override
    public void setPrimaryItem(ViewGroup container, int position, Object object) {
        mAdapter.setPrimaryItem(container, position, object);
    }
}

4.1.1 映射规则

  • PageAdapter原始的数据源
| A | B | C | D |
  0   1   2   3
  • LoopPagerAdapterWrapper中的数据源是
| D | A | B | C | D | A |
  0   1   2   3   4   5
  • 在LoopPagerAdapterWrapper中需要根据当前下标,推算出实际的PageAdapter对应数据下标,映射关系如下
    • realadpater.position=(loopadapter.position-1)%count
0->3  D
1->0  A
2->1  B
3->2  C
4->3  D
5->0  A

4.1.2 缓存真实的头尾界面用于显示假循环

  • mBoundaryCaching 标示是否需要缓存
    如果需要缓存,则
    • destroyItem中并不实际销毁,而是放入缓存列表
    • instantiateItem中并不新建而是直接拿到数据
    private int getRealFirstPosition() {
        return 1;
    }

    private int getRealLastPosition() {
        return getRealFirstPosition() + getRealCount() - 1;
    }

     @Override
    public Object instantiateItem(ViewGroup container, int position) {
        ...
        if (mBoundaryCaching) {
            ToDestroy toDestroy = mToDestroy.get(position);
            if (toDestroy != null) {
                mToDestroy.remove(position);
                return toDestroy.object;
            }
        }
        ...
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        ...
        int realFirst = getRealFirstPosition();
        int realLast = getRealLastPosition();

        if (mBoundaryCaching && (position == realFirst || position == realLast)) {
            mToDestroy.put(position, new ToDestroy(container, realPosition,
                    object));
        } else {
            mAdapter.destroyItem(container, realPosition, object);
        }
        ...
    }

4.2 LoopViewPager

通过继承Viewpager,并设置了一个内部的PagechangeListener,在onPageScrolled的回调中,发现当内部的pagerAdpater的position滑动到边界的时候,通过调用setCurrentItem,将position又设置到正确的位置

public class LoopViewPager extends ViewPager {
    @Override
    public void setAdapter(PagerAdapter adapter) {
        mAdapter = new LoopPagerAdapterWrapper(adapter);
        mAdapter.setBoundaryCaching(mBoundaryCaching);
        super.setAdapter(mAdapter);
        setCurrentItem(0, false);
    }

   @Override
    public void setCurrentItem(int item) {
        if (getCurrentItem() != item) {
            setCurrentItem(item, true);
        }
    }
    //setCurrentItem(0)其实应该是setCurrentItem(1)
    //因为左边额外加了一个界面(就是上图的<0>位置),所以我们的起始时候是从<1>位置开始。所以如果用户在activity代码里面执行LoopViewPager.setCurrentItem(N, smoothScroll);实际上应该跳到的都是N+1的位置
    public void setCurrentItem(int item, boolean smoothScroll) {
        int realItem = mAdapter.toInnerPosition(item);
        super.setCurrentItem(realItem, smoothScroll);
    }

    @Override
    public int getCurrentItem() {
        return mAdapter != null ? mAdapter.toRealPosition(super.getCurrentItem()) : 0;
    }

    private OnPageChangeListener onPageChangeListener = new OnPageChangeListener() {
        private float mPreviousOffset = -1;
        private float mPreviousPosition = -1;

        @Override
        public void onPageSelected(int position) {

            int realPosition = mAdapter.toRealPosition(position);
            if (mPreviousPosition != realPosition) {
                mPreviousPosition = realPosition;
                if (mOuterPageChangeListener != null) {
                    mOuterPageChangeListener.onPageSelected(realPosition);
                }
            }
        }

        @Override
        public void onPageScrolled(int position, float positionOffset,
                int positionOffsetPixels) {
            int realPosition = position;
            if (mAdapter != null) {
                realPosition = mAdapter.toRealPosition(position);

                if (positionOffset == 0
                        && mPreviousOffset == 0
                        && (position == 0 || position == mAdapter.getCount() - 1)) {
                    setCurrentItem(realPosition, false);
                }
            }

            mPreviousOffset = positionOffset;
            if (mOuterPageChangeListener != null) {
                if (realPosition != mAdapter.getRealCount() - 1) {
                    mOuterPageChangeListener.onPageScrolled(realPosition,
                            positionOffset, positionOffsetPixels);
                } else {
                    if (positionOffset > .5) {
                        mOuterPageChangeListener.onPageScrolled(0, 0, 0);
                    } else {
                        mOuterPageChangeListener.onPageScrolled(realPosition,
                                0, 0);
                    }
                }
            }
        }

        @Override
        public void onPageScrollStateChanged(int state) {
            if (mAdapter != null) {
                int position = LoopViewPager.super.getCurrentItem();
                int realPosition = mAdapter.toRealPosition(position);
                if (state == ViewPager.SCROLL_STATE_IDLE
                        && (position == 0 || position == mAdapter.getCount() - 1)) {
                    setCurrentItem(realPosition, false);
                }
            }
            if (mOuterPageChangeListener != null) {
                mOuterPageChangeListener.onPageScrollStateChanged(state);
            }
        }
    };

}

唯一不足的地方就是需要监听pageChangeListener的pageScroll方法,重新设置position的值(具体的方法是调用scrollTo进行重绘一遍,比较浪费性能)

参考文献

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值