背景:
瞎扯一下: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);
}
}