首先扯点别的:今天周天——阴雨天气好久之后的第一个晴天,阳光明媚,温度也还可以,真的适合开车出去玩。但是没有车,好吧,那就坐地铁出去。
经苗哥(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的实现
参考链接