第一次写博客,如果有什么问题,麻烦各位给个意见。
OK,进入正题,这次博客的内容是实现一个无限自动循环的ViewPager广告轮询控件。这几天公司要做一个商场的APP,刚好要用到这个功能,以前倒是写过自动轮询的ViewPager,而且github上也有不少这种开源控件,不过,我用的时候发现viewpager里面加载的ImageView只有二或三项的时候,自动向前滑是没问题,当自己手动往后滑的时候,突然就抛了个异常退出了,就像这样。
java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.,这个跟我们没有重写destoryItem()方法去移除控件,报的是一样的异常,然而我的destoryItem()里面是有移除掉当前view。这也是我打算写这篇博客的主要原因。下面我会提供一下解决方法。OK,接下来我们看代码。
下面是ViewPager的代码:
public class CircularViewpager extends ViewPager {
private static final int START_CIRCULAR = 0;
private static final int STOP_CIRCULAR = 1;
private boolean flag = false;
private int intervalTime = 3 * 1000;
private OnPagerClickListener onPagerClickListener;
private int allPagerCount;
public void setOnPagerClickListener(int allPagerCount, OnPagerClickListener onPagerClickListener) {
this.allPagerCount = allPagerCount;
this.onPagerClickListener = onPagerClickListener;
}
/**
* handler实现自动轮询
*/
private Handler mhandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case START_CIRCULAR:
if (flag) {
setCurrentItem(getCurrentItem() + 1);
mhandler.sendEmptyMessageDelayed(START_CIRCULAR, intervalTime);
}
break;
case STOP_CIRCULAR:
mhandler.removeCallbacksAndMessages(null);
break;
default:
break;
}
}
};
public CircularViewpager(Context context) {
this(context, null);
}
public CircularViewpager(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* 开始自动轮询
*/
public void start() {
flag = true;
mhandler.sendEmptyMessageDelayed(START_CIRCULAR, intervalTime);
}
/**
* 停止自动轮询
*/
public void stop() {
mhandler.sendEmptyMessage(STOP_CIRCULAR);
flag = false;
}
/**
* 设置循环间隔时间
*/
public void setIntervalTime(int intervalTime) {
this.intervalTime = intervalTime;
}
private long downTime;
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
downTime = System.currentTimeMillis();
stop();
break;
case MotionEvent.ACTION_UP:
long upTime = System.currentTimeMillis();
if ((upTime - downTime) < 100) {
if (onPagerClickListener != null) {
onPagerClickListener.onPagerClick(getCurrentItem() % allPagerCount);
}
}
start();
break;
}
return super.onTouchEvent(ev);
}
/**
* 点击事件回调接口
*/
public interface OnPagerClickListener {
void onPagerClick(int pagerPosition);
}
}
上面通过继承ViewPager来扩展其功能,用一个handler来实现自动轮询效果的,viewpager调用start()方法,往自身发送message,令设置viewpager当前item,即调用setCurrentItem()设置当前的item为当前item项加一。
如果要让viewpager停止自动轮询的话,就发送信息去调用handler的removeCallbacksAndMessages()方法把所有的回调跟信息移除。同时重写onTouchEvent()方法,实现用户手动滑动时,停止自动滑动。以及用户点击的监听事件(通过回调方法,反馈到viewpager里)。
接下来看一下Activitiy怎么使用这个ViewPager吧:
imageViews = new ArrayList<>();
int[] imgs = new int[]{R.drawable.a2, R.drawable.a3, R.drawable.a4, R.drawable.a5, R.drawable.a6};
for (int img : imgs) {
//新建ImageView,并添加到集合中
ImageView imageView = new ImageView(this);
imageView.setImageResource(img);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageViews.add(imageView);
}
CircularPagerAdaper adapter = new CircularPagerAdaper(imageViews);
viewpager.setAdapter(adapter);
//开始自动循环
viewpager.start();
viewpager.setOnPagerClickListener(imageViews.size(), new CircularViewpager.OnPagerClickListener() {
@Override
public void onPagerClick(int pagerPosition) {
Toast.makeText(MainActivity.this, "点击的是第" + pagerPosition + "项", Toast.LENGTH_SHORT).show();
}
});
其实用法跟普通的viewpager一模一样,只是多了一步viewpage.start()方法去开启自动轮询,以及多了个viewpager的点击监听方法。
接下来我们看一下adapter吧,我开头说的那个问题也是在这个地方解决的。
class CircularPagerAdaper extends PagerAdapter {
private List<ImageView> list;
public CircularPagerAdaper(List<ImageView> list) {
this.list = list;
}
@Override
public int getCount() {
return Integer.MAX_VALUE;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
//当list的数量小于等于三时,此处会报异常,此处把异常catch到不处理,然后destroyItem不移除
@Override
public Object instantiateItem(ViewGroup container, final int position) {
try {
container.addView(list.get(position % list.size()));
} catch (Exception e) {
}
return list.get(position % list.size());
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (list.size() > 3) {
container.removeView(list.get(position % list.size()));
}
}
}
这里对于开头那个问题我这里再重复一次吧,是这样的,viewpager里面默认的预加载项是一项,即当前Item的左右各预加载一项,一共要加载三个Imageview,当你通过setOffscreenPageLimit()方法来设置预加载项时,设置的值小于1的话,默认为1,即预加载项不能小于1,我们看一下源码吧。
private static final int DEFAULT_OFFSCREEN_PAGES = 1;
public void setOffscreenPageLimit(int limit) {
if (limit < DEFAULT_OFFSCREEN_PAGES) {
Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
DEFAULT_OFFSCREEN_PAGES);
limit = DEFAULT_OFFSCREEN_PAGES;
}
if (limit != mOffscreenPageLimit) {
mOffscreenPageLimit = limit;
populate();
}
}
因为这个原因,当我往后滑动时,之前的view还未移除出去,就需要重新加载,所以就报错了。我的解决思路是这样的,我用一个try{}catch{}把报错的地方包裹起来,catch不进行处理,然后,当我加载的项小于等于三项时,就不进行removeView()。这样的就能能避免小于等于三项时Viewpager抛异常退出了。
OK,这个的实现思路就这样了,我的源码已经发布到github上了,上面添加了滑动时,有个小圆点随着ViewPager的滑动而切换的效果,各位想看的话也可以去下载,地址是:https://github.com/mnb65482/CircularViewpager
第一次写博客,有误的地方大家提个醒,多谢了!!!