ViewPager無限滑動虎忌,定時切換在項目是比較常見的功能先较,這篇文章的目的就是實現(xiàn)這樣一個功能敞贡。
實現(xiàn)思路
1.在數(shù)據(jù)源上<b>增加兩個一頭一尾的數(shù)據(jù)</b> 例如:[1 2 3 ]----->[<b>3</b> 1 2 3 <b>1</b>]稍走,此時數(shù)據(jù)源索引0的內容會等于索引length-2的內容皂林,數(shù)據(jù)源索引length-1的內容共的會等于索引1位置的內容
2.有了上面的思路再接下來就是利用ViewPager的滑動監(jiān)聽,在用戶滑到第一個或最后一個頁面戴质,調用ViewPager.setCurrentItem()方法進行位置的切換度宦。
ViewPager的使用不是文章的重點,文章只貼出重要的邏輯代碼告匠。
代碼實現(xiàn)
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.準備數(shù)據(jù):
<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 />
//創(chuàng)建數(shù)據(jù)源
List<Integer> pictures = new ArrayList<>();
pictures.add(R.drawable.picture1);
pictures.add(R.drawable.picture2);
pictures.add(R.drawable.picture3);
//額外增加兩個數(shù)據(jù)
<b>pictures.add(pictures.get(0));
pictures.add(0,pictures.get(pictures.size()-2));</b><br />
mViewPager = (ViewPager) findViewById(R.id.viewPager);
//安裝ViewPager頁面滑動監(jiān)聽
setupPageChangeListener();
mViewPager.setAdapter(new ViewPagerPictureAdapter(this,pictures));<br />
//由于在數(shù)據(jù)源頭部增加了一個戈抄,所以初始化定位到1
<b>mViewPager.setCurrentItem(1);</b>
}
}
</pre>
3.創(chuàng)建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后专,當一個頁面發(fā)生切換划鸽,其中會調用onPagerScrolled()方法以及onPageSelected()方法,在一個滑動過程中onPageScrolled()會回調多次,在其中可以監(jiān)聽到滑動進度裸诽;而onPageSelected()方法被調用是在手指釋放時并且達到了切換到其他頁面的條件(滑動的偏移量/速度)立即調用而且只調用一次嫂用,也就意味著松開手之后不是等到滑動進度完成百分之百才調用這方法。所以可以得出如果在onPageSelected()做切換肯定不能達到滿意的效果丈冬。所以要做的切換只能考慮放到onPageScrolled()方法嘱函。<b>后期補充</b>:<u>如果把切換放在onPageScrollStateChanged()方法內,那么判斷條件就是state == SCROLL_STATE_IDLE的條件埂蕊,但經過一番測試之后發(fā)現(xiàn)在ViewPager滑到頭尾兩端時往弓,回到SCROLL_STATE_IDLE狀態(tài)稍慢,就是說不會在onPageScrolled的postionOffset為0時就能回到這個狀態(tài)回調這個方法蓄氧,所以導致如果快速滑動滑到兩端時會出現(xiàn)滑動不流暢的現(xiàn)象函似。所以最終還是在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>
運行喉童,看下實現(xiàn)的效果:
從效果上發(fā)現(xiàn)撇寞,切換位置時會閃現(xiàn);作為一名有追求的程序員肯定是不能容忍的堂氯,于是開始各種分析蔑担,最初以為是Adapter里的instantiateItem()方法的創(chuàng)建View并顯示的過程所消耗的時間會比destroyItem()方法執(zhí)行的慢,結果大失所望祖灰,instantiateItem()方法無論何種情況都會比destoryItem()方法先調用钟沛;最終經各種嘗試之后畔规,無厘頭的還是在onPageScrolled()方法內切換頁面時采用延時策略局扶。再進一步發(fā)現(xiàn),更狗血的是延時策略的延時間可以為0叁扫,即只需要將切換的代碼放到Handler里面執(zhí)行就不會出現(xiàn)閃現(xiàn)的現(xiàn)象三妈。此時,一口老血以迅雷不及掩耳盜鈴之勢直線噴出...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畴蒲,并且數(shù)據(jù)源不小于3
public void startLoop() {
isAutoSwitchStatus = true;
loop();
}
// 取消定時切換
public void cancelLoop() {
cancel();
isAutoSwitchStatus = false;
}
// 用于數(shù)據(jù)加載完之后loop
public void loop() {
if (isAutoSwitchStatus && getVisibility() == VISIBLE && getAdapter() != null && getAdapter().getCount() >= 3) {
//保證只有一個handler消息被發(fā)送
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方法來設置監(jiān)聽器模燥。
<b>注意</b>:該封裝類中暴露的兩個方法,應在組件對應的生命周期方法內開啟和結束掩宜,以防止內存泄漏蔫骂。