Android ViewPager无限滑动实现方案之一

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>

运行,看下实现的效果:

未解决闪现问题.gif

从效果上发现,切换位置时会闪现;作为一名有追求的程序员肯定是不能容忍的,于是开始各种分析,最初以为是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>

不过最终效果还是很不错的。


解决闪现问题.gif
只有一张图片.gif

最后贴上一个封装类:
<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>:该封装类中暴露的两个方法,应在组件对应的生命周期方法内开启和结束,以防止内存泄漏。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 197,368评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,941评论 2 374
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 144,369评论 0 326
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,848评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,719评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,505评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,904评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,528评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,819评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,848评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,652评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,468评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,912评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,095评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,389评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,906评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,120评论 2 339

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,181评论 25 707
  • 不会VLOOKUP公式,你就不算一个合格的职场人,VLOOKUP公式在日常办公中非常实用和重要,下面给大家普及下这...
    兰瑟鸢尾阅读 1,706评论 0 1
  • 简名千鸟错, 永正安吉坐。 波及江北阔, 帅以几阔绰。
    月独醉阅读 92评论 0 0