PS:之前也写过关于图片轮播这一块的博客.不过写的很烂,而且不少状况没有考虑到(没有支持无线轮播,和手势点击事件).所以这里写一篇补上.也是当时太年轻了.服务器
注:图片请放大后再看.不然看不清楚.ide
学习内容:oop
1.自定义ViewPager布局
2.图片无限轮播因为ViewPager的预加载机制所致使的问题.学习
之前也写过关于图片轮播的相关博客,不过整体写的很是的烂,而且不可以无线轮播,并且也没法对手势事件进行相关的处理,所以在这里补上,也是属于自定义View的一篇内容吧.而且经过这个过程发现了无限轮播因为ViewPager的预加载机制所致使的问题.也正遇上项目要上一个新的版本,发现了这个bug.个人同事想到了一个比较不错的方案解决了这个问题.整体仍是很不错的.所以在这里记录一下.this
整体实现无限轮播的思想,其实和网上大部分的思路都是相同的,设置一个Integer的最大值的一半,而后根据position和图片的数量去计算,来实现向左向右无限滑动这个功能.整体不是特别的难.自定义ViewPager以后,把相关的图片和跟随图片滑动时的小圆点传递到ViewPager当中,而后设置相关的滑动监听,Adapter就能够完美的实现图片的无限轮播了.spa
i.初始化轮播.线程
初始化轮播须要线程和Handler配合来完成,设置一个无线循环的线程,让这个线程按照必定的周期一直向Activity发送Message,Handler在接收到消息后会不断的对消息进行处理.改变ViewPager当前显示的CurrentItem就能够实现无限轮播了.code
/** * 初始化轮播的线程 */ public void initLoop() { new Thread(new Runnable() { @Override public void run() { while (true) { try { Thread.sleep(3000); if (!stopLoopTag) { Message message = Message.obtain(); message.what = 10; message.arg1 = getCurrentItem() + 1; mHandler.sendMessage(message); } } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } /** * 处理轮播的Handler */ private Handler mHandler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { if (msg.what == 10 && !stopLoopTag) { setCurrentItem(msg.arg1); } else { mHandler.removeMessages(10); } return true; } });
ii.图片轮播时小圆点随之变更orm
有了一个子线程和一个Handler就能够实现无线循环的一个过程,咱们知道通常图片轮播都须要伴随小圆点的移动,小圆点通常是直接布局到ViewPager当中的,由于须要给用户一种更好的体验性,所以在图片轮播的同时伴随着小圆点也随之变更.那么小圆点如何在ViewPager的Item改变的时候也随之变更呢?这就须要addOnPageChangeListener()来实现了.
private OnPageChangeListener onPageChangeListener = new OnPageChangeListener() {
@Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { /** * 改变小圆点的状态 * 三种状况: * 1.在初始位置 * 2.向左滑动 * 3.向右滑动 * 不要放在上一个方法中,会有明显的延迟现象出现 * lastPointPosition 上一个点的位置,初始化为0 * lastPointIndex 上一个点在集合中的位置 * currentPointIndex 当前的点在集合中的位置 * */ if (lastPointPosition == 0) { currentPointIndex = 0; lastPointIndex = 0; } else if (lastPointPosition < position) { if (currentPointIndex == getImages().size() - 1) { currentPointIndex = 0; } else { currentPointIndex += 1; } } else if (lastPointPosition > position) { if (currentPointIndex == 0) { currentPointIndex = getImages().size() - 1; } else { currentPointIndex -= 1; } } dots.get(lastPointIndex).setImageResource(R.drawable.dot_normal); dots.get(currentPointIndex).setImageResource(R.drawable.dot_focus); lastPointPosition = position; lastPointIndex = currentPointIndex; } @Override public void onPageScrollStateChanged(int state) { } };
这里咱们经过addOnPageChangeListener()绑定Page的改变监听来改变小圆点随着Page改变的同时随之改变.图片的改变须要在适配器里去设置,适配器我留到最后说,由于其中有不少的细节.这样有了无限循环,原点移动,那么就须要说一下当咱们手指停留在ViewPager的时候,如何使ViewPager中止播放.由于涉及到了手势事件,所以就要重写相关的方法.
iii.重写手势事件
/** * 手势事件的重写 */ @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: /** * 按下的时候中止轮播 * */ stopLoop(); xDown = (int) event.getX(); yDown = (int) event.getY(); break; case MotionEvent.ACTION_MOVE: /** * 这里不用作任何处理,移动的时候ViewPager内的图片会自动滑动 * */ break; case MotionEvent.ACTION_UP: /** * 记录按下时间 * */ if (timeThread == null) { touchTime = new Date().getTime(); timeTag = true; timeThread = new TimeThread(); timeThread.start(); } else { touchTime = new Date().getTime(); } /** * 判断是不是点击事件 * */ int xUp = (int) event.getX(); int yUp = (int) event.getY(); if (Math.abs(xDown - xUp) < 20 && Math.abs(yDown - yUp) < 20) { if (onImageItemClickListener != null) { onImageItemClickListener.onItemClick(currentPointIndex); } } break; } return super.onTouchEvent(event); }
手势事件的重写就很是的简单了,当点击的时候中止轮播,移动的时候咱们不须要作任何的处理,在手指离开以后,咱们须要计算离开的时间,离开的时间超过两秒,就再次开启轮播.关闭轮播只须要中止最开始的那个子线程,让标记位StopLoopTag为true,再将消息队列中的消息移除队列.这样Handler也接收不到消息了.这样就中止了轮播的效果.计算手指按下和离开的时间也须要一个子线程去处理.
/** * 时间线程,用于记录手指离开点击ViewPager的时间 * 若是离开的时间 >= 2000毫秒,那么ViewPager继续轮播 */ class TimeThread extends Thread { @Override public void run() { while (timeTag) { long currentTime = new Date().getTime(); if (currentTime - touchTime >= 2000) { openLoop(); timeTag = false; timeThread = null; } } } }
iv.PagerAdapter.
PagerAdapter也是最蛋疼的一块.instantiateItem()方法里的那一大堆代码是关键.不少人都用这样的代码来实现图片的加载过程.
@Override public Object instantiateItem(ViewGroup container, int position) { position %= images.size(); if (position < 0) { position = position + images.size(); } ImageView imageView = images.get(initPosition); ViewParent parent = imageView.getParent(); if (parent != null) { ViewGroup viewGroup = (ViewGroup) parent; viewGroup.removeView(imageView); } container.addView(imageView); return imageView; }
其实这样写是有很大的问题的,由于这里没有考虑到ViewPager的预加载机制的问题,这也是咱们项目出现的一个bug,咱们是从服务器上获取相关的图片数据,而后保存在集合当中,若是咱们在将图片加载到ViewPager中的时候,仅调用image.get(position),position为上面代码计算出的position,这样的话实际上会致使图片错位.虽然显示的是5张图片,可是实际上他们在ViewPager中显示的顺序是不对的.好比说咱们在后台定义了这样的顺序, 1,2,3,4,5的顺序发送给咱们,可是若是咱们按照上面的方法从集合中拿数据的时候,ViewPager显示的不是1,2,3,4,5这样的顺序,这也是咱们在项目中发现的问题.由于在点击图片的时候,是须要走不一样的连接的,也正是这个缘由咱们发现了这个bug.所以这里作了不少的处理.
public class PictureAdapter extends PagerAdapter { private List<ImageView> images; /** * initPosition -1为初始化的位置,后面是当前的图片索引位置 * topPosition 记录上一次初始化的索引位,用于计算上次的position和本次position的偏移量 * * */ private int initPosition = -1; private int topPosition = -1; public PictureAdapter(List<ImageView> images) { this.images = images; } @Override public int getCount() { return Integer.MAX_VALUE; } @Override public boolean isViewFromObject(View view, Object object) { return view == object; } @Override public void destroyItem(ViewGroup container, int position, Object object) { } /** * 实例化Item */ @Override public Object instantiateItem(ViewGroup container, int position) { if(images.size() <=1){ ImageView imageView = images.get(topPosition); container.addView(imageView); return imageView; }else{ /** * 初始化状态 * 向左滑动 * 向右滑动 * 因为ViewPager有预加载机制,默认加载一页,所以在第一次初始化的时候,会调用三次这个方法. * (第一次: position=1073741823 第二次: position=1073741822 第三次: position=1073741824) * * 然后续,这个方法仅被执行一次,而且执行的是预加载下一页的请求. * */ Log.e("TAG","position="+position); if (initPosition == -1) { /** * 初始化状态 * topPosition 记录第一次初始化的索引位.用于后续做比较判断下次是向右滑动仍是向左滑动 * initPosition 初始化图片集合的索引值 * */ topPosition = position; initPosition = 0; } else if (topPosition < position) { /** * 向左滑动 * 得出偏移量后比较是否超过图片集合的大小 * */ int value = position - topPosition; initPosition += value; if (initPosition == images.size()) { /** * 滑动到了最后一页 * */ initPosition = 0; } else if (initPosition > images.size()) { /** * 若是超出了图片集合的大小,则 initPosition = 超过的数值 * */ initPosition = (initPosition - images.size()); } topPosition = position; } else if (topPosition > position) { int value = topPosition - position; initPosition -= value; if (initPosition == -1) { /** * 滑动到了第一页 * */ initPosition = images.size() - 1; } else if (initPosition < -1) { /** * 当计算后的值小于了集合大小,则用集合大小减去小于的这部分 * */ initPosition = (images.size() - (Math.abs(initPosition))); } topPosition = position; } Log.e("TAG","topPosition="+topPosition); Log.e("TAG","initPosition="+initPosition); /** * 只用这句话应该会出现问题 * */ // position %= images.size(); // if (position < 0) { // position = position + images.size(); // } ImageView imageView = images.get(initPosition); ViewParent parent = imageView.getParent(); if (parent != null) { ViewGroup viewGroup = (ViewGroup) parent; viewGroup.removeView(imageView); } container.addView(imageView); return imageView; } } }
这就是咱们定义的PagerAdapter.里面作了不少的逻辑处理.一张图解释一下其中的原理.
这张图说明了一种的道理,上面代码不难发现.会出现 (initPosition > images.size())和initPosition < -1这两种状况.这种缘由的致使就是由于position会因为这种预加载机制出现数值跳跃问题.这里你们能够去根据Log信息结合我说的原理,好好的研究一下.相信你们会研究明白的.
最后就是点击事件了.对Activity暴露接口,让Activity去实现就能够了.
/** * 对Activity暴露接口 */ private OnImageItemClickListener onImageItemClickListener; public interface OnImageItemClickListener { void onItemClick(int itemPosition); } public void setOnImageItemClickListener(OnImageItemClickListener onImageItemClickListener) { this.onImageItemClickListener = onImageItemClickListener; }
剩下的就是MainActivity了.这里就不粘贴代码了.里面的内容比较的简单.这样就实现了图片的无线轮播,而且支持点击中止,松开继续播放的功能,还有的图片点击事件.最后放一个源代码: