ViewPager图片自动+手动左右无限轮播

写在前面:
最近做的一个小项目有图片轮播的需求,各种查资料发现大部分都是通过设置adapter的getCount方法返回Integer.MAX_VALUE实现的。很显然,这种方法有很多弊端,比如很容易ANR。或者采用其他方法,但在首页和尾页的跳转不够自然。那么有没有比较好的方法呢?在现有方法的基础上,加上自己的思考和改进,我终于很好的实现了ViewPager图片轮播。趁着周末分享给大家,欢迎批评和指正。

一、主要思想

首先,很容易想到的是,要想让尾页左滑到首页、首页右滑到尾页有一个平稳的过渡过程,那么在首页尾页的两边必须存在着对应要跳转的页面,然后跳转完成后我们再“偷偷”替换为要显示的真实页面(两者页面显示内容一样,只是位置不一样,“偷偷”指的是用户不可见,在内容不变的情况下替换了页面的位置)。
ViewPager图片自动+手动左右无限轮播_第1张图片
如上图所示,假定现在想显示4张图片,分别为view0到view3。根据上面的思想:
(1)我们可以在adapter中额外多加两个view,即如图中的红色的view3和view0。这样position1的view0很容易过渡到position0的view3,同理尾页到首页的过渡也很自然。
(2)第1步之后,我们很轻易的发现一个问题:滑到position0时,右滑之后并没有对应的view可以显示,同理position5。这就需要把position0的view3偷偷换成position4的view3,这样position0的右滑也就是position4的右滑,即滑到view2,很好的满足了设想的需求。

二、如何实现左右轮播

(1)要实现第一步,只要重写adapter的一些方法即可。

private class ImagePagerAdapter extends PagerAdapter{
        private String[] images;
        private LayoutInflater inflater;

        public ImagePagerAdapter(String[] images) {
            this.images = images;
            this.inflater = getLayoutInflater();
        }

        @Override
        public int getCount() {
            //返回实际要显示的图片数+2
            return images.length + 2;
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            //注意不要remove  否则容易闪屏
            //  ((ViewPager)container).removeView((View) object);
        }

        @Override
        public Object instantiateItem(ViewGroup container, int position) {

            View mView = View.inflate(getApplicationContext(),R.layout.activity_uil_viewpager_item,null);
            //这是重点
            int realPosition = (position - 1 + images.length)%images.length;

            ImageView imageView = (ImageView) mView.findViewById(R.id.myimage);
            final ProgressBar bar = (ProgressBar) mView.findViewById(R.id.loading);

            mView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Log.d(TAG,"hahaha  click");
                }
            });

            //通过UIL加载图片
            imageLoader.displayImage(images[realPosition], imageView, ImageLoaderHelper.getInstance(getApplicationContext()).getSimpleDisplayImageOptions(),
                    new SimpleImageLoadingListener() {

                        @Override
                        public void onLoadingStarted(String imageUri, View view) {//开始加载的时候执行
                            bar.setVisibility(View.VISIBLE);
                        }

                        @Override
                        public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {//加载成功的时候执行
                            bar.setVisibility(View.GONE);
                        }

                        @Override
                        public void onLoadingCancelled(String imageUri, View view) {//加载取消的时候执行
                            super.onLoadingCancelled(imageUri, view);
                        }

                        @Override
                        public void onLoadingFailed(String imageUri, View view, FailReason failReason) {//加载失败的时候执行
                            String message = null;
                            switch (failReason.getType()){//加载失败类型
                                case IO_ERROR:// 文件I/O错误
                                    message = "IO_ERROR";
                                    break;
                                case DECODING_ERROR:// 解码错误
                                    message = "DECODING_ERROR";
                                    break;
                                case NETWORK_DENIED:// 网络延迟
                                    message = "NETWORK_DENIED";
                                    break;
                                case OUT_OF_MEMORY:// 内存不足
                                    message = "OUT_OF_MEMORY";
                                    break;
                                case UNKNOWN:// 原因不明
                                default:
                                    message = "UNKNOWN";
                                    break;
                            }

                            bar.setVisibility(View.GONE);
                        }
                    }, new ImageLoadingProgressListener() {
                        @Override
                        public void onProgressUpdate(String s, View view, int i, int i1) {//在这里更新 ProgressBar的进度信息

                            int progress = 100*i/i1;
                            bar.setProgress(progress);

                        }
                    });

            ((ViewPager)container).addView(mView,0);
            return mView;
        }
    }

以上代码,关于图片资源的UIL获取可忽略。比较重要的是getCount()返回实际要显示的图片数+2,这样才能在首尾附加两个view。另外一个是在instantiateItem()方法中int realPosition = (position - 1 + images.length)%images.length;这里的realPosition对应要显示View的index。由上图,这个关系也是显而易见的。此外,还要注意的是在destroyItem()中不要removeView否则快速滑动的时候容易出现闪屏。
PS:由上图可以发现,应该初始化ViewPager.setCurrentItem(1);才能从预设的第一页开始播放。
(2)至于第二步,“偷偷”替换的过程,主要和PageChangeListener的三个要实现的方法有关,这里先把主要代码附上。

 mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            /**
             * 当页面在滑动了调用
             * @param position 当前页面,即点击滑动的页面
             * @param positionOffset 当前页面偏移的百分比
             * @param positionOffsetPixels 当前页面偏移的像素位置
             */
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                if (position == Constants.images.length && positionOffset > 0.99) {
                //在position4左滑且左滑positionOffset百分比接近1时,偷偷替换为position1(原本会滑到position5)
                    mViewPager.setCurrentItem(1, false);
                } else if (position == 0 && positionOffset < 0.01) {
                //在position1右滑且右滑百分比接近0时,偷偷替换为position4(原本会滑到position0)
                    mViewPager.setCurrentItem(4, false);
                }
            }


            /**
             * This method will be invoked when a new page becomes selected. Animation is not
             * necessarily complete.   一般在滑动30%的时候就会调用
             *
             * @param position Position index of the new selected page.
             */

            @Override
            public void onPageSelected(int position) {
                //当有手动操作时,remove掉之前auto的runnable。延迟将由手动的这次决定。
                //总之,一个页面selected之后  最多只有一个runnable,要把多的remove掉
                handler.removeCallbacks(runnable);
                Log.d(TAG, "onPageSelected,page:" + position);
                if (position != Constants.images.length+1 && position != 0){
                    handler.postDelayed(runnable,3*1000);
                }
            }

            @Override
            public void onPageScrollStateChanged(int state) {
                switch (state) {
                    case 0://什么都没做  空闲状态
                        break;
                    case 1://正在滑动
                        break;
                    case 2://滑动完毕
                        break;
                }
            }
        });

onPageScrolled:
页面滚动的时候就会调用,三个参数如注释所示。值得注意的是:
关于左滑:在一个页面中,比如position4,左滑过程中onPageScrolled的position参数一直为4,positionOffset由0递增无限接近1。
关于右滑:比如当前页面在position1,右滑过程中,position参数立刻变为0,且positionOffset由1递减至0.
(这里为了便于帮助理解首尾页跳转逻辑,特地选了position1和position4,其实对所有位置都是这样。大家可以自己左右滑动观察打印信息,这一点对页面跳转的条件的理解很重要),这里positionOffset > 0.99和positionOffset < 0.01的限制条件是为了在整个页面几乎全部显示出来之后默默替换,否则页面转换不够自然。此外,不能够限定为1和0,所以我取了比较接近的数值。
此外,mViewPager.setCurrentItem(1, false);中第二个参数为false表示页面切换无动画,这里由于当前显示内容和待切换内容一致,因而用户察觉不到位置替换过程。参数为true,则为平滑过渡。
PS:这里易犯的错误是,在onPageSelected方法中实现页面切换。其实这个方法在页面还在滑动的过程中就会调用(可见官方文档说明,我这里多次观察在偏移量为30%左右的时候就会调用),这样会有明显不自然的切换效果。

三、如何实现自动轮播

关于自动轮播,一般都是采用handler+timer的方法。还应当注意的是,手动滑动后应该重新计时。

Handler handler = new Handler(){

        @Override
        public void handleMessage(Message msg) {
            Log.d(TAG, "have received a msg");
            int curindex = (mViewPager.getCurrentItem()+1)%(Constants.images.length+2);
            mViewPager.setCurrentItem(curindex,true);
        }
    };

    Runnable runnable = new Runnable() {
        @Override
        public void run() {

            Message message = new Message();
            handler.sendMessage(message);
        }
    };

页面加载的时候先调用一次 handler.postDelayed(runnable, 3 * 1000);实现一次页面自动切换,然后重点在onPageSelected()方法中的处理,实现自动轮播。

 @Override
            public void onPageSelected(int position) {
                //当有手动操作时,remove掉之前auto的runnable。延迟将由手动的这次决定。
                //总之,一个页面selected之后  最多只有一个runnable,要把多的remove掉
                handler.removeCallbacks(runnable);
                Log.d(TAG, "onPageSelected,page:" + position);
                if (position != Constants.images.length+1 && position != 0){
                    handler.postDelayed(runnable,3*1000);
                }
            }

注意前面的handler.removeCallbacks(runnable);是很必要的,防止手动+自动会产生多余的post从而加快页面切换间隔(之前忽略了这点纠结了很久)。

源码见:https://github.com/YangLili1994/MyUniversalImageLoader

你可能感兴趣的:(ViewPager图片自动+手动左右无限轮播)