Android实现ViewPager无限循环滚动回绕

Android系统提供的ViewPager标准方式是左右可以自由滑动,但是滑动到最左边的极限位置是第一个page,滑动到最右边的位置是最后一个page,当滑动到最左或者最右时候,就不能再滑动/滚动了,这是Android系统默认的ViewPager实现方式。
但是有些情况下开发者可能希望ViewPager能够智能的无限循环滚动回绕,比如现在总共有编号1, 2, 3, 4, 5的5个Page。
(1)当用户手指从右往左滚动到最右边/最后面的页面5时候,如果此时用户继续拖住ViewPager往左边滑动,那么ViewPager将回绕、循环到第一个Page -> 1,接着就是2,3,4,5;
(2)反过来,如果当用户手指从左往右,滑到最左边的第一个Page:1时候,如果此时继续拖住ViewPager继续从左往右滑动,那么将回绕到5,接着就是4,3,2,1.
我们把这种ViewPager称之谓“无限循环滚动回绕”的ViewPager。
这种类型的ViewPager网上有较多实现方式,现在给出一个流程较广的代码实现。
写一个测试的MainActivity.java:

package zhangphil.demo;import java.util.Random;import android.app.Activity;import android.graphics.Color;import android.os.Bundle;import android.support.v4.view.PagerAdapter;import android.view.Gravity;import android.view.View;import android.view.ViewGroup;import android.widget.TextView;public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);LoopViewPager viewpager = (LoopViewPager) findViewById(R.id.viewpager);viewpager.setAdapter(new SamplePagerAdapter());}public class SamplePagerAdapter extends PagerAdapter {private final Random random = new Random();private int mSize;public SamplePagerAdapter() {mSize = 5;}public SamplePagerAdapter(int count) {mSize = count;}@Overridepublic int getCount() {return mSize;}@Overridepublic boolean isViewFromObject(View view, Object object) {return view == object;}@Overridepublic void destroyItem(ViewGroup view, int position, Object object) {view.removeView((View) object);}@Overridepublic Object instantiateItem(ViewGroup view, int position) {TextView textView = new TextView(view.getContext());textView.setText(position + 1 + "");textView.setBackgroundColor(0xff000000 | random.nextInt(0x00ffffff));textView.setGravity(Gravity.CENTER);textView.setTextColor(Color.WHITE);textView.setTextSize(50);view.addView(textView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);return textView;}// 增加itempublic void addItem() {mSize++;notifyDataSetChanged();}// 删除itempublic void removeItem() {mSize--;mSize = mSize < 0 ? 0 : mSize;notifyDataSetChanged();}}}


MainActivity.java需要的布局文件activity_main.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="zhangphil.demo.MainActivity" >    <zhangphil.demo.LoopViewPager        android:id="@+id/viewpager"        android:layout_width="match_parent"        android:layout_height="match_parent"/></RelativeLayout>


核心关键代码LoopViewPager.java类和LoopViewPager.java依赖的LoopPagerAdapterWrapper.java!

LoopViewPager.java:

package zhangphil.demo;import android.content.Context;import android.support.v4.view.PagerAdapter;import android.support.v4.view.ViewPager;import android.util.AttributeSet;import java.util.ArrayList;import java.util.List;public class LoopViewPager extends ViewPager {private static final boolean DEFAULT_BOUNDARY_CASHING = false;private static final boolean DEFAULT_BOUNDARY_LOOPING = true;private LoopPagerAdapterWrapper mAdapter;private boolean mBoundaryCaching = DEFAULT_BOUNDARY_CASHING;private boolean mBoundaryLooping = DEFAULT_BOUNDARY_LOOPING;private List<OnPageChangeListener> mOnPageChangeListeners;/** * helper function which may be used when implementing FragmentPagerAdapter * * @return (position-1)%count */public static int toRealPosition(int position, int count) {position = position - 1;if (position < 0) {position += count;} else {position = position % count;}return position;}/** * If set to true, the boundary views (i.e. first and last) will never be * destroyed This may help to prevent "blinking" of some views */public void setBoundaryCaching(boolean flag) {mBoundaryCaching = flag;if (mAdapter != null) {mAdapter.setBoundaryCaching(flag);}}public void setBoundaryLooping(boolean flag) {mBoundaryLooping = flag;if (mAdapter != null) {mAdapter.setBoundaryLooping(flag);}}@Overridepublic void setAdapter(PagerAdapter adapter) {mAdapter = new LoopPagerAdapterWrapper(adapter);mAdapter.setBoundaryCaching(mBoundaryCaching);mAdapter.setBoundaryLooping(mBoundaryLooping);super.setAdapter(mAdapter);setCurrentItem(0, false);}@Overridepublic PagerAdapter getAdapter() {return mAdapter != null ? mAdapter.getRealAdapter() : mAdapter;}@Overridepublic int getCurrentItem() {return mAdapter != null ? mAdapter.toRealPosition(super.getCurrentItem()) : 0;}public void setCurrentItem(int item, boolean smoothScroll) {int realItem = mAdapter.toInnerPosition(item);super.setCurrentItem(realItem, smoothScroll);}@Overridepublic void setCurrentItem(int item) {if (getCurrentItem() != item) {setCurrentItem(item, true);}}@Overridepublic void setOnPageChangeListener(OnPageChangeListener listener) {addOnPageChangeListener(listener);}@Overridepublic void addOnPageChangeListener(OnPageChangeListener listener) {if (mOnPageChangeListeners == null) {mOnPageChangeListeners = new ArrayList<>();}mOnPageChangeListeners.add(listener);}@Overridepublic void removeOnPageChangeListener(OnPageChangeListener listener) {if (mOnPageChangeListeners != null) {mOnPageChangeListeners.remove(listener);}}@Overridepublic void clearOnPageChangeListeners() {if (mOnPageChangeListeners != null) {mOnPageChangeListeners.clear();}}public LoopViewPager(Context context) {super(context);init(context);}public LoopViewPager(Context context, AttributeSet attrs) {super(context, attrs);init(context);}private void init(Context context) {if (onPageChangeListener != null) {super.removeOnPageChangeListener(onPageChangeListener);}super.addOnPageChangeListener(onPageChangeListener);}private OnPageChangeListener onPageChangeListener = new OnPageChangeListener() {private float mPreviousOffset = -1;private float mPreviousPosition = -1;@Overridepublic void onPageSelected(int position) {int realPosition = mAdapter.toRealPosition(position);if (mPreviousPosition != realPosition) {mPreviousPosition = realPosition;if (mOnPageChangeListeners != null) {for (int i = 0; i < mOnPageChangeListeners.size(); i++) {OnPageChangeListener listener = mOnPageChangeListeners.get(i);if (listener != null) {listener.onPageSelected(realPosition);}}}}}@Overridepublic void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {int realPosition = position;if (mAdapter != null) {realPosition = mAdapter.toRealPosition(position);if (positionOffset == 0 && mPreviousOffset == 0&& (position == 0 || position == mAdapter.getCount() - 1)) {setCurrentItem(realPosition, false);}}mPreviousOffset = positionOffset;if (mOnPageChangeListeners != null) {for (int i = 0; i < mOnPageChangeListeners.size(); i++) {OnPageChangeListener listener = mOnPageChangeListeners.get(i);if (listener != null) {if (realPosition != mAdapter.getRealCount() - 1) {listener.onPageScrolled(realPosition, positionOffset, positionOffsetPixels);} else {if (positionOffset > .5) {listener.onPageScrolled(0, 0, 0);} else {listener.onPageScrolled(realPosition, 0, 0);}}}}}}@Overridepublic void onPageScrollStateChanged(int state) {if (mAdapter != null) {int position = LoopViewPager.super.getCurrentItem();int realPosition = mAdapter.toRealPosition(position);if (state == ViewPager.SCROLL_STATE_IDLE && (position == 0 || position == mAdapter.getCount() - 1)) {setCurrentItem(realPosition, false);}}if (mOnPageChangeListeners != null) {for (int i = 0; i < mOnPageChangeListeners.size(); i++) {OnPageChangeListener listener = mOnPageChangeListeners.get(i);if (listener != null) {listener.onPageScrollStateChanged(state);}}}}};}


LoopPagerAdapterWrapper.java:

package zhangphil.demo;import android.os.Parcelable;import android.support.v4.app.FragmentPagerAdapter;import android.support.v4.app.FragmentStatePagerAdapter;import android.support.v4.view.PagerAdapter;import android.util.SparseArray;import android.view.View;import android.view.ViewGroup;public class LoopPagerAdapterWrapper extends PagerAdapter {private PagerAdapter mAdapter;private SparseArray<ToDestroy> mToDestroy = new SparseArray<>();private static final boolean DEFAULT_BOUNDARY_CASHING = true;private static final boolean DEFAULT_BOUNDARY_LOOPING = true;private boolean mBoundaryCaching = DEFAULT_BOUNDARY_CASHING;private boolean mBoundaryLooping = DEFAULT_BOUNDARY_LOOPING;void setBoundaryCaching(boolean flag) {mBoundaryCaching = flag;}void setBoundaryLooping(boolean flag) {mBoundaryLooping = flag;}LoopPagerAdapterWrapper(PagerAdapter adapter) {this.mAdapter = adapter;}@Overridepublic void notifyDataSetChanged() {mToDestroy = new SparseArray<>();super.notifyDataSetChanged();}int toRealPosition(int position) {int realPosition = position;int realCount = getRealCount();if (realCount == 0)return 0;if (mBoundaryLooping) {realPosition = (position - 1) % realCount;if (realPosition < 0)realPosition += realCount;}return realPosition;}public int toInnerPosition(int realPosition) {int position = (realPosition + 1);return mBoundaryLooping ? position : realPosition;}private int getRealFirstPosition() {return mBoundaryLooping ? 1 : 0;}private int getRealLastPosition() {return getRealFirstPosition() + getRealCount() - 1;}@Overridepublic int getCount() {int count = getRealCount();return mBoundaryLooping ? count + 2 : count;}public int getRealCount() {return mAdapter.getCount();}public PagerAdapter getRealAdapter() {return mAdapter;}@Overridepublic Object instantiateItem(ViewGroup container, int position) {int realPosition = (mAdapter instanceof FragmentPagerAdapter || mAdapter instanceof FragmentStatePagerAdapter)? position : toRealPosition(position);if (mBoundaryCaching) {ToDestroy toDestroy = mToDestroy.get(position);if (toDestroy != null) {mToDestroy.remove(position);return toDestroy.object;}}return mAdapter.instantiateItem(container, realPosition);}@Overridepublic void destroyItem(ViewGroup container, int position, Object object) {int realFirst = getRealFirstPosition();int realLast = getRealLastPosition();int realPosition = (mAdapter instanceof FragmentPagerAdapter || mAdapter instanceof FragmentStatePagerAdapter)? position : toRealPosition(position);if (mBoundaryCaching && (position == realFirst || position == realLast)) {mToDestroy.put(position, new ToDestroy(container, realPosition, object));} else {mAdapter.destroyItem(container, realPosition, object);}}/* * Delegate rest of methods directly to the inner adapter. */@Overridepublic void finishUpdate(ViewGroup container) {mAdapter.finishUpdate(container);}@Overridepublic boolean isViewFromObject(View view, Object object) {return mAdapter.isViewFromObject(view, object);}@Overridepublic void restoreState(Parcelable bundle, ClassLoader classLoader) {mAdapter.restoreState(bundle, classLoader);}@Overridepublic Parcelable saveState() {return mAdapter.saveState();}@Overridepublic void startUpdate(ViewGroup container) {mAdapter.startUpdate(container);}@Overridepublic void setPrimaryItem(ViewGroup container, int position, Object object) {mAdapter.setPrimaryItem(container, position, object);}/* * End delegation *//** * Container class for caching the boundary views */static class ToDestroy {ViewGroup container;int position;Object object;public ToDestroy(ViewGroup container, int position, Object object) {this.container = container;this.position = position;this.object = object;}}}



如果读者有兴趣使用,直接将上面两个核心关键代码放入到自己的项目代码包中,当作自己写的类直接使用即可,写布局时候不要再写ViewPager,而是直接像我上面写的那个布局文件一样用LoopViewPager。

更多相关文章

  1. 没有一行代码,「2020 新冠肺炎记忆」这个项目却登上了 GitHub 中
  2. GitHub 标星 2.5K+!教你通过玩游戏的方式学习 VIM!
  3. 大约Android远程监控APP源代码
  4. android如何利用基于Http 协议的WebService服务来获取远程数据库
  5. Android(安卓)NDK 中使用C++源文件和使用C文件的不同
  6. Android(安卓)把个Excel 搞成sqlite数据库文件 并放在Assets里打
  7. 重磅来袭!2020 年需要关注的 5 大 Android(安卓)开发技术,抓破脑袋
  8. Android的Linux“心”
  9. Android(安卓)自动点击

随机推荐

  1. android Theme使用四
  2. Android(安卓)邮件发送(一键发送, 163邮
  3. 仿照利用android系统源码资源文件,修改See
  4. Android(安卓)文件读写 + sdcard + 文件
  5. Android系统权限和root权限
  6. Android(安卓)静默安装
  7. android-单元测试(Android(安卓)JUnit Te
  8. 控件属性:
  9. Android类加载器ClassLoader
  10. Android(安卓)SDK 实例代码分析---Accele