ViewPager实现无限循环轮播图控件

首先扯点别的:今天周天——阴雨天气好久之后的第一个晴天,阳光明媚,温度也还可以,真的适合开车出去玩。但是没有车,好吧,那就坐地铁出去。

经苗哥(csdn博客http://blog.csdn.net/roly_yu/article/details/53123617),介绍,发现了github上一个比较牛逼的ViewPager实现无限循环轮播控件BGABanner-Android,效果也是没谁了。如果没有什么特殊需求,可以直接拿来用,至于怎么用,大家可以自己看。
没图没真相,看图说话。但是gif录制的效果不好。
这里写图片描述

ViewPager实现无限循环轮播图控件(下文一律叫做Banner)

1.当轮播的图片只有一张的时候,ViewPager不能滚动,多张的时候可以滚动。

自定义ViewPager,重写onTouchEvent和onInterceptTouchEvent来实现这个功能

public class BannerViewPager extends ViewPager {
	
	//标志是否可以滚动
    private boolean scrollable = true;

    public BannerViewPager(Context context) {
        super(context);
    }

    public BannerViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }


    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return this.scrollable && super.onTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return this.scrollable && super.onInterceptTouchEvent(ev);
    }

    public void setScrollable(boolean scrollable) {
        this.scrollable = scrollable;
    }

    /**
     * 设置调用setCurrentItem(int item, boolean smoothScroll)方法时,page切换的时间长度
     *
     * @param duration page切换的时间长度
     */
    public void setPageChangeDuration(int duration) {
        try {
            Field scrollerField = ViewPager.class.getDeclaredField("mScroller");
            scrollerField.setAccessible(true);
            scrollerField.set(this, new BGABannerScroller(getContext(), duration));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

当轮播图的图片只有一张的时候设置scrollable为false,多张的时候设置scrollable 为true

自定义 BGABannerScroller

public class BGABannerScroller extends Scroller {
	
	//控制ViewPager的切换时间
    private int mDuration = 1000;

    public BGABannerScroller(Context context, int duration) {
        super(context);
        mDuration = duration;
    }

    @Override
    public void startScroll(int startX, int startY, int dx, int dy) {
        super.startScroll(startX, startY, dx, dy, mDuration);
    }

    @Override
    public void startScroll(int startX, int startY, int dx, int dy, int duration) {
        super.startScroll(startX, startY, dx, dy, mDuration);
    }
}

2.ViewPager的适配器很重要

/**
 * ViewPager 的适配器
 */
class BannerPagerAdapter extends PagerAdapter {

    /**
     * 这个size一定要比较大才行,默认为轮播图片张数的30倍。
     */
    private final int FAKE_BANNER_SIZE = count * 30;
    //轮播图片的地址
    private List imgUrls;

    public BannerPagerAdapter(List imgUrls) {
        this.imgUrls = imgUrls;
    }

    @Override
    public int getCount() {
        if (count == 1) {
            return 1;
        }
        return FAKE_BANNER_SIZE;
    }

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

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        position %= count;
        final int pos = position;
        View view = LayoutInflater.from(context)
                    .inflate(R.layout.item_banner, container, false);
        ImageView imageView = (ImageView) view.findViewById(R.id.img_banner);
        Glide.with(context)
             .load(imgUrls.get(position))
            .placeholder(R.drawable.img_bg)
            .error(R.drawable.img_bg)
            .into(imageView);
        if (mOnBannerClickListener != null) {
            imageView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    mOnBannerClickListener.OnBannerClick(pos);
                }
            });
        }
        container.addView(view);
        return view;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        container.removeView((View) object);
    }

    @Override
    public void finishUpdate(ViewGroup container) {
        if (count > 1) {
            int position = viewPager.getCurrentItem();
            Log.e(tag, "finishUpdate" + position);
            if (position == 0) {
                position = count;
                viewPager.setCurrentItem(position, false);
            } else if (position == FAKE_BANNER_SIZE - 1) {
                position = count - 1;
                viewPager.setCurrentItem(position, false);
            }
        }
    }
}

一些比较重要的点

@Override
public int getCount() {
    if (count == 1) {
        return 1;
    }
    return FAKE_BANNER_SIZE;
}
  

要想实现无限轮播,首先,要让PagerAdapter 的getCount()方法返回一个比较大的size。当轮播的图片只有一张的时候,就返回1,不让ViewPage滚动,当轮播图片的个数大于1的时候,就返回轮播图片个数的30倍的一个size。让ViewPager可以滚动。

然后在instantiateItem方法中通过取余来实现。

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        position %= count;
        final int pos = position;
        View view = LayoutInflater.from(context)
                    .inflate(R.layout.item_banner, container, false);
        ImageView imageView = (ImageView) view.findViewById(R.id.img_banner);
        Glide.with(context)
             .load(imgUrls.get(position))
            .placeholder(R.drawable.img_bg)
            .error(R.drawable.img_bg)
            .into(imageView);
        if (mOnBannerClickListener != null) {
            imageView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    mOnBannerClickListener.OnBannerClick(pos);
                }
            });
        }
        container.addView(view);
        return view;
    }

最后在finishUpdate方法中,要进行小小的作弊。finishUpdate这个方法的调用时机是:当显示的页面中的更改已完成时调用。我的理解是给ViewPager设置adapter以后,或者ViewPager显示的页面切换完成以后会调用这个方法。

@Override
public void finishUpdate(ViewGroup container) {
    if (count > 1) {
        int position = viewPager.getCurrentItem();
        if (position == 0) {
	        //count代表轮播图片的数量。
            position = count;
            viewPager.setCurrentItem(position, false);
        } else if (position == FAKE_BANNER_SIZE - 1) {
            position = count - 1;
            viewPager.setCurrentItem(position, false);
        }
    }
}
  

我们假设轮播图片一共有4张,当viewPager显示第1张图片的时候,position为0,我们调用

    position = count;
    viewPager.setCurrentItem(position, false);

这时候,我们就让ViewPager显示position=4时候的图片,因为我们在instantiateItem方法中通过取余操作,这是候加载的依然是第一张图片,当一直向右滑动到达FAKE_BANNER_SIZE -1,就是4的30倍120减去1=119的时候,我们调用

position = count - 1;
viewPager.setCurrentItem(position, false);

让viewpager显示position为3也就是第4张图片。通过这种方式就可以达到无限轮播的效果。

这个算法思想参考自https://github.com/Rolyyu/banner

但是上面实现的轮播图存在两个问题
1.当Banner作为RecyclerView的头布局的时候,RecyclerView滑动到底部再滑到顶部,Banner的第一次切换没有动画效果。
2.当Banner在切换过程中,RecyclerView滑动到底部再滑上来会导致Banner卡在中间(前一张图片没有完全消失,后面一张图片没有完全展现出来)

自己研究了也没有研究出来,后来请教了shucc,参考了链接使用RecyclerView + ViewPager 的两个大坑!,才找到解决办法。
关于解决办法的详细过程可以参看使用RecyclerView + ViewPager 的两个大坑!。这里想说一下第二个问题的原因和解决办法。

问题2的原因

直接来看ViewPager的onDetachedFromWindow方法

@Override
protected void onDetachedFromWindow() {
    removeCallbacks(mEndScrollRunnable);
    // To be on the safe side, abort the scroller
    if ((mScroller != null) && !mScroller.isFinished()) {
	    //停掉动画
        mScroller.abortAnimation();
    }
    super.onDetachedFromWindow();
}

ViewPager直接把动画停掉了
问题2的解决方法

//onDetachedFromWindow的时候是否停止动画
private boolean abortAnimation = false;
public void setAbortAnimation(boolean abortAnimation) {
    this.abortAnimation = abortAnimation;
}
@Override
protected void onDetachedFromWindow() {
    //解决在RecyclerView中使用,Banner切换到一半的时候,
    //RecyclerView滑上去,Banner会卡住的问题
    if (abortAnimation) {
        super.onDetachedFromWindow();
    }
}

在自定义的BannerViewPager中,我们定义一个abortAnimation 变量,我们在适当的时机来设置abortAnimation 的值(比如说在Banner stop的时候设置abortAnimation 为true,在Banner resume的时候设置abortAnimation 为false),这样就可以解决第2个问题。
完整的代码 见github

实现了无限轮播,其他的就是一些细节方面的东西了
比如实现自动轮播,用户也可以左右滑动。
显示指示器,有数字指示器和底部小圆点指示器。

Another way

另一种实现无限轮播的方式感觉更赞,请参考

Android 无限轮播ViewPager的实现

参考链接

  1. BGABanner-Android
  2. banner
  3. Carousel
  4. 使用RecyclerView + ViewPager 的两个大坑!
  5. Android 无限轮播ViewPager的实现
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值