背景:

瞎扯一下:ViewPager在Android中经常用于作为一个图片浏览的展示控件,几乎很多的项目都会运用到这个控件,大家对其也一定不陌生。另外伴随这个控件的还有一个词汇:无限循环。

这个是本篇博客的目的。

需求:

ViewPager控件的应用需求一般有:

1.加载多张图片,可以无限左右切换进行浏览(重点)

2.能够自动定时进行切换,同时不影响用户手势触碰的体验(触碰时自动停止定时,由用户掌控;放开时重新自动定时切换)

3.切换时需要一个匀速缓慢的过程,而不是一闪而过

4.能够内置在滚动控件如ScrollView中,同时能够准确区分左右滑动切换和上下滑动滚动的手势

难点:

各个需求分别对应的各个难点如下:

1.左右无限循环

2.切换定时、取消定时和重启定时

3.匀速缓慢切换的过程

4.上下手势和左右手势的区分

分析:

难点对应的分析如下:

1.左右无限循环:

         目前左右无限循环的方案在网上有多种,大概可以分为真左右无限循环和假象左右无限循环两种。本博客采用的是后者(关于前者的内容和原理大家有兴趣可以搜一下)。

         为什么说是假象无限左右循环呢?因为这个方案达到无限左右循环的一个突破口是:自定义PagerAdapter,在其重写方法getCount()中,返回值不是数据源的size,而是Integer.MAX_VALUE,这就让Android系统在使用这个Adapter的时候,以为其数据源有Integer.MAX_VALUE项数据,从而在左右切换时能够不断的进行切换,称之为假象的原因正是如此。


2.切换定时、取消定时和重启定时:

切换定时:我们可以采用Handler的SentMessageDelayed机制,隔一定的时间发送一个切换信息进行定时切换。

取消定时:需要在自定义ViewPager控件中对手势操作进行监听,当被按下或者进行滑动时,清空Handler的切换消息队列

重启定时:当用户把手从ViewPager控件上拿开时,重新在Handler中加入Delayed消息即可完成重启定时

3.匀速缓慢切换的过程

默认的ViewPager切换是一闪而过的,所以要实现这个需求,我们需要做点什么:

自定义Scroller,在其startScroll方法中,填入需要过渡切换的时长,如mDuration;再利用相关反射方法,让ViewPager利用这个Scroller进行匀速缓慢切换。

4.上下手势和左右手势的区分

         内置在诸如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初始化完成后开始启动切换定时



Handler的取消定时和重启定时

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);
	}

}



3.匀速缓慢切换过程

自定义的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;
	}



让ViewPager匀速缓慢切换:

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);
	}

}