瞎扯一下:ViewPager在Android中经常用于作为一个图片浏览的展示控件,几乎很多的项目都会运用到这个控件,大家对其也一定不陌生。另外伴随这个控件的还有一个词汇:无限循环。
这个是本篇博客的目的。
ViewPager控件的应用需求一般有:
各个需求分别对应的各个难点如下:
难点对应的分析如下:
目前左右无限循环的方案在网上有多种,大概可以分为真左右无限循环和假象左右无限循环两种。本博客采用的是后者(关于前者的内容和原理大家有兴趣可以搜一下)。
为什么说是假象无限左右循环呢?因为这个方案达到无限左右循环的一个突破口是:自定义PagerAdapter,在其重写方法getCount()中,返回值不是数据源的size,而是Integer.MAX_VALUE,这就让Android系统在使用这个Adapter的时候,以为其数据源有Integer.MAX_VALUE项数据,从而在左右切换时能够不断的进行切换,称之为假象的原因正是如此。
切换定时:我们可以采用Handler的SentMessageDelayed机制,隔一定的时间发送一个切换信息进行定时切换。
取消定时:需要在自定义ViewPager控件中对手势操作进行监听,当被按下或者进行滑动时,清空Handler的切换消息队列
重启定时:当用户把手从ViewPager控件上拿开时,重新在Handler中加入Delayed消息即可完成重启定时
默认的ViewPager切换是一闪而过的,所以要实现这个需求,我们需要做点什么:
自定义Scroller,在其startScroll方法中,填入需要过渡切换的时长,如mDuration;再利用相关反射方法,让ViewPager利用这个Scroller进行匀速缓慢切换。
内置在诸如ScrollView中的ViewPager,如果不做一定的处理,那么ViewPager将会给用户带来极其糟糕的操作体验:除非近乎水平的滑动ViewPager,否则很难进行左右切换,却容易误触发ScrollView的上下滚动事件。
处理方案是:自定义ViewPager,在其中的dispatchTouchEvent事件中,对触摸事件进行拦截,当触发ACTION_DOWN、ACTION_MOVE时,要求父类控件(如ScrollView)不响应触摸事件:getParent().requestDisallowInterceptTouchEvent(true); 当触发ACTION_UP或ACTION_CANCEL时,重新恢复父类控件对触摸事件的响应:getParent().requestDisallowInterceptTouchEvent(false);
经过这样的处理之后:用户在Viewpager控件区域进行触摸,那么就只会产生左右切换事件而不会触发上下滚动事件。
代码只贴出关键部分,相关详情可下载附件进行查看
1.自定义的PagerAdapter:
public class InfiniteLoopViewPagerAdapter extends PagerAdapter { private PagerAdapter adapter;//该adapter为真正绑定数据源的适配器 public InfiniteLoopViewPagerAdapter(PagerAdapter adapter) { super(); this.adapter = adapter; } @Override public int getCount() { return Integer.MAX_VALUE;//关键处 } public int getRealCount() { return adapter.getCount(); } public int getRealItemPosition(Object object) { return adapter.getItemPosition(object); } @Override public void destroyItem(ViewGroup container, int position, Object object) { //取得实际的索引位置 int realPosition = position % getRealCount(); adapter.destroyItem(container, realPosition, object); } @Override public Object instantiateItem(ViewGroup container, int position) { int realPosition = position % getRealCount(); return adapter.instantiateItem(container, realPosition); } /* * start */ @Override public void finishUpdate(ViewGroup container) { adapter.finishUpdate(container); } @Override public boolean isViewFromObject(View view, Object object) { return adapter.isViewFromObject(view, object); } @Override public void restoreState(Parcelable state, ClassLoader loader) { adapter.restoreState(state, loader); } @Override public Parcelable saveState() { return adapter.saveState(); } @Override public void startUpdate(ViewGroup container) { adapter.startUpdate(container); }2.Handler的切换定时
private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == 0) { this.removeMessages(0);//每次切换开始前,清空队列 viewPager.setCurrentItem(viewPager.getCurrentItem() + 1, true);//进行切换 this.sendEmptyMessageDelayed(0, flipDuration);//下一次切换定时 } super.handleMessage(msg); } }; handler.sendEmptyMessageDelayed(0, flipDuration);//一般在ViewPager初始化完成后开始启动切换定时
public class InfiniteLoopViewPager extends ViewPager { private Handler handler; private int flipDuration= 2*1000; public InfiniteLoopViewPager(Context context, AttributeSet attrs) { super(context, attrs); } public InfiniteLoopViewPager(Context context) { super(context); } public void setInfinateAdapter(Context context, Handler handler, PagerAdapter adapter,int flipDuration) { this.handler = handler; this.flipDuration = flipDuration; setAdapter(adapter); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { int action = ev.getAction(); if (null == handler) { return true; } if (action == MotionEvent.ACTION_DOWN) { handler.removeMessages(0);//触摸按下时清空定时队列,停止切换 } else if (action == MotionEvent.ACTION_MOVE) { handler.removeMessages(0);//触摸滑动时清空定时队列,停止切换 } else if (action == MotionEvent.ACTION_UP) { handler.sendEmptyMessageDelayed(0, flipDuration);//触摸抬起时重启定时队列 } else { handler.sendEmptyMessageDelayed(0, flipDuration);//其他情况,重启定时队列 } return super.dispatchTouchEvent(ev); } }
自定义的Scroller:
public class FixedSpeedScroller extends Scroller { private int mDuration = 222; public FixedSpeedScroller(Context context) { super(context); } public FixedSpeedScroller(Context context, Interpolator interpolator) { super(context, interpolator); } @Override public void startScroll(int startX, int startY, int dx, int dy, int duration) { // Ignore received duration, use fixed one instead super.startScroll(startX, startY, dx, dy, mDuration); } @Override public void startScroll(int startX, int startY, int dx, int dy) { // Ignore received duration, use fixed one instead super.startScroll(startX, startY, dx, dy, mDuration); } public void setmDuration(int time) { mDuration = time; } public int getmDuration() { return mDuration; }
private void initViewPagerSmoothScroll() { try { Field mField = ViewPager.class.getDeclaredField("mScroller"); mField.setAccessible(true); mScroller = new FixedSpeedScroller(viewPager.getContext(), new AccelerateInterpolator());//mScroller即为自定义的Scroller实例 mField.set(viewPager, mScroller); } catch (Exception e) { e.printStackTrace(); } }
4.上下手势和左右手势的区分
public class InfiniteLoopViewPager extends ViewPager { public InfiniteLoopViewPager(Context context, AttributeSet attrs) { super(context, attrs); } public InfiniteLoopViewPager(Context context) { super(context); } public void setInfinateAdapter(Context context, Handler handler,PagerAdapter adapter,int flipDuration) { this.handler = handler; this.flipDuration = flipDuration; setAdapter(adapter); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { int action = ev.getAction(); if (null == handler) { return true; } if (action == MotionEvent.ACTION_DOWN) { getParent().requestDisallowInterceptTouchEvent(true);//按下:要求父类控件不要响应滑动操作 } else if (action == MotionEvent.ACTION_MOVE) { getParent().requestDisallowInterceptTouchEvent(true);//滑动:要求父类控件不要响应滑动操作 } else if (action == MotionEvent.ACTION_UP) { getParent().requestDisallowInterceptTouchEvent(false);//抬起:父类控件可以响应滑动操作 } else { getParent().requestDisallowInterceptTouchEvent(false);//其他:父类控件可以响应滑动操作 } return super.dispatchTouchEvent(ev); } }
http://download.csdn.net/detail/junjun071308/9120935