ViewPager无限滑动,定时切换在项目是比较常见的功能,这篇文章的目的就是实现这样一个功能。
实现思路
1.在数据源上<b>增加两个一头一尾的数据</b> 例如:[1 2 3 ]----->[<b>3</b> 1 2 3 <b>1</b>],此时数据源索引0的内容会等于索引length-2的内容,数据源索引length-1的内容共的会等于索引1位置的内容
2.有了上面的思路再接下来就是利用ViewPager的滑动监听,在用户滑到第一个或最后一个页面,调用ViewPager.setCurrentItem()方法进行位置的切换。
ViewPager的使用不是文章的重点,文章只贴出重要的逻辑代码。
代码实现
1.布局代码
<pre>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="watson.code.infiniteviewpager.MainActivity">
<android.support.v4.view.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="200dp"></android.support.v4.view.ViewPager>
<TextView
android:id="@+id/currentPicturePostionTV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="@android:color/black"
android:textSize="20sp"/>
</LinearLayout>
</pre>
2.准备数据:
<pre>
public class MainActivity extends AppCompatActivity {
private ViewPager mViewPager;
private TextView currentPicturePostionTV;<br />
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
currentPicturePostionTV = (TextView) findViewById(R.id.currentPicturePostionTV);<br />
//创建数据源
List<Integer> pictures = new ArrayList<>();
pictures.add(R.drawable.picture1);
pictures.add(R.drawable.picture2);
pictures.add(R.drawable.picture3);
//额外增加两个数据
<b>pictures.add(pictures.get(0));
pictures.add(0,pictures.get(pictures.size()-2));</b><br />
mViewPager = (ViewPager) findViewById(R.id.viewPager);
//安装ViewPager页面滑动监听
setupPageChangeListener();
mViewPager.setAdapter(new ViewPagerPictureAdapter(this,pictures));<br />
//由于在数据源头部增加了一个,所以初始化定位到1
<b>mViewPager.setCurrentItem(1);</b>
}
}
</pre>
3.创建Adapter:
<pre>
public class ViewPagerPictureAdapter extends PagerAdapter {
private List<Integer> mData;
private Context mContext;
public ViewPagerPictureAdapter(Context context, List<Integer> data){
mData = data;
mContext = context;
}
@Override
public int getCount() {
return mData.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return object == view;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(),mData.get(position));
ImageView imageView = new ImageView(mContext);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setImageBitmap(bitmap);
container.addView(imageView,new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return imageView;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((ImageView)object);
}
}
</pre>
4.处理OnPageChangeListener的回调方法。you know,当一个页面发生切换,其中会调用onPagerScrolled()方法以及onPageSelected()方法,在一个滑动过程中onPageScrolled()会回调多次,在其中可以监听到滑动进度;而onPageSelected()方法被调用是在手指释放时并且达到了切换到其他页面的条件(滑动的偏移量/速度)立即调用而且只调用一次,也就意味着松开手之后不是等到滑动进度完成百分之百才调用这方法。所以可以得出如果在onPageSelected()做切换肯定不能达到满意的效果。所以要做的切换只能考虑放到onPageScrolled()方法。<b>后期补充</b>:<u>如果把切换放在onPageScrollStateChanged()方法内,那么判断条件就是state == SCROLL_STATE_IDLE的条件,但经过一番测试之后发现在ViewPager滑到头尾两端时,回到SCROLL_STATE_IDLE状态稍慢,就是说不会在onPageScrolled的postionOffset为0时就能回到这个状态回调这个方法,所以导致如果快速滑动滑到两端时会出现滑动不流畅的现象。所以最终还是在onPageScrolled()做处理。</u>
<pre>
private void setupPageChangeListener() {
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
//页面过度滑动动画完成判断
<b>if (positionOffset == 0) {
if (position == 0) {
mViewPager.setCurrentItem(mViewPager.getAdapter().getCount() - 2, false);
} else if (position == mViewPager.getAdapter().getCount() - 1) {
mViewPager.setCurrentItem(1, false);
}
}</b>
}
@Override
public void onPageSelected(int position) {
if(position == 0){
position = mViewPager.getAdapter().getCount()-2;
}else if(position == mViewPager.getAdapter().getCount() - 1){
position = 1;
}
currentPicturePostionTV.setText(String.valueOf(position));
}
@Override
public void onPageScrollStateChanged(int state) {}
});
}
</pre>
运行,看下实现的效果:
从效果上发现,切换位置时会闪现;作为一名有追求的程序员肯定是不能容忍的,于是开始各种分析,最初以为是Adapter里的instantiateItem()方法的创建View并显示的过程所消耗的时间会比destroyItem()方法执行的慢,结果大失所望,instantiateItem()方法无论何种情况都会比destoryItem()方法先调用;最终经各种尝试之后,无厘头的还是在onPageScrolled()方法内切换页面时采用延时策略。再进一步发现,更狗血的是延时策略的延时间可以为0,即只需要将切换的代码放到Handler里面执行就不会出现闪现的现象。此时,一口老血以迅雷不及掩耳盗铃之势直线喷出...what's the fuck!!
<pre>
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 1:
mViewPager.setCurrentItem(mViewPager.getAdapter().getCount() - 2, false);
break;
case 2:
mViewPager.setCurrentItem(1, false);
break;
}
}
};
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
//页面过度滑动动画完成
if (positionOffset == 0) {
if (position == 0) {
handler.sendEmptyMessageDelayed(1,0);
} else if (position == mViewPager.getAdapter().getCount() - 1) {
handler.sendEmptyMessageDelayed(2,0);
}
}
}
@Override
public void onPageSelected(int position) {
if(position == 0){
position = mViewPager.getAdapter().getCount()-2;
}else if(position == mViewPager.getAdapter().getCount() - 1){
position = 1;
}
currentPicturePostionTV.setText(String.valueOf(position));
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
}
</pre>
不过最终效果还是很不错的。
最后贴上一个封装类:
<pre>
package watson.code.infiniteviewpager;
/**
- Created by watson on 2017/5/31.
*/
public class InfiniteViewPager extends ViewPager {
private static final String TAG = InfiniteViewPager.class.getName();
private OnPageChangeListener mOnPageChangeListener;
public InfiniteViewPager(Context context) {
this(context, null);
}
public InfiniteViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
setCurrentItem(1, false);
break;
case 1:
setCurrentItem(getAdapter().getCount() - 2, false);
break;
case 2:
setCurrentItem(getCurrentItem() + 1);
loop();
break;
}
}
};
public void init() {
addOnPageChangeListener(mOnPageChangeListener = new OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (positionOffset == 0) {
if (position == getAdapter().getCount() - 1) {
handler.sendEmptyMessage(0);
} else if (position == 0) {
handler.sendEmptyMessage(1);
}
}
if (mOnPageChangeListener != null && mOnPageChangeListener != this) {
mOnPageChangeListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
}
}
@Override
public void onPageSelected(int position) {
if (mOnPageChangeListener != null && mOnPageChangeListener != this) {
mOnPageChangeListener.onPageSelected(position);
}
}
@Override
public void onPageScrollStateChanged(int state) {
//Log.i(TAG, "onPageScrollStateChanged: " + state);
if (mOnPageChangeListener != null && mOnPageChangeListener != this) {
mOnPageChangeListener.onPageScrollStateChanged(state);
}
}
});
}
@Override
public void addOnPageChangeListener(OnPageChangeListener listener) {
if (listener == mOnPageChangeListener) {
super.addOnPageChangeListener(listener);
} else {
mOnPageChangeListener = listener;
}
}
public int getDuration() {
return duration;
}
public void setDuration(int duration) {
this.duration = duration;
}
private int duration = 5000;
private boolean isAutoSwitchStatus;
// 开启自动切换,条件必须是要先设置Adapter,并且数据源不小于3
public void startLoop() {
isAutoSwitchStatus = true;
loop();
}
// 取消定时切换
public void cancelLoop() {
cancel();
isAutoSwitchStatus = false;
}
// 用于数据加载完之后loop
public void loop() {
if (isAutoSwitchStatus && getVisibility() == VISIBLE && getAdapter() != null && getAdapter().getCount() >= 3) {
//保证只有一个handler消息被发送
handler.removeMessages(2);
handler.sendEmptyMessageDelayed(2, duration);
}
}
private void cancel() {
if (isAutoSwitchStatus) {
handler.removeMessages(2);
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
cancel();
break;
case MotionEvent.ACTION_UP:
loop();
break;
}
return super.dispatchTouchEvent(ev);
}
@Override
public void setAdapter(PagerAdapter adapter) {
super.setAdapter(adapter);
setCurrentItem(1);
}
}
</pre>
在上面的封装里没有增加额外的接口,只是复写了addOnPageChangeListener方法做了一些处理。所以使用时依然可以调用addOnPageChangeListener方法来设置监听器。
<b>注意</b>:该封装类中暴露的两个方法,应在组件对应的生命周期方法内开启和结束,以防止内存泄漏。