1.引言
viewpager2是JetPack组件库推出的组件之一,是原生viewPager的升级但是不兼容,因为ViewPager2的Adapter 继承于RecycleView.Adpater。
ViewPager+Fragment实现无限滚动的方案主要有2种
方案一:将viewpager上限设置成一个很大的数,第一个页面设置到中间。然后滑动的时候,用当前的序号与viewpager页面数取余得到目标页面的序号,然后显示出来。理论上一个人不会无聊到一直左滑或者右滑。因此可以模拟无限循环。
方案二:假设viewpager中有四个页面,分别为A、B、C、D。然后在A左边添加一个页面D,在D右边添加一个页面A,变成 D、A、B、C、D、A。当滑到D时跳转到D,滑到A时跳转到A
本方案使用的是ViewPager2+Fragment 加 方案二 的实现方式。
2. 方案实现
1. 无限的实现
public class ViewActivity extends AppCompatActivity {
private ViewPager2 vp2;
private List fragments = new ArrayList<>();
private List datas = new ArrayList<>();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view);
vp2 = (ViewPager2) findViewById(R.id.vp2);
fragments.add(ItemFragment.newInstance("1"));
fragments.add(ItemFragment.newInstance("2"));
fragments.add(ItemFragment.newInstance("3"));
fragments.add(ItemFragment.newInstance("4"));
vp2.setOffscreenPageLimit(2);
vp2.setAdapter(new FragmentPager2Adapter(this, fragments));
MyOnPageChangeCallback callback = new MyOnPageChangeCallback(vp2);
vp2.registerOnPageChangeCallback(callback);
}
}
public class FragmentPager2Adapter extends FragmentStateAdapter {
private final List fragments;
public FragmentPager2Adapter(@NonNull FragmentActivity fragmentActivity, List fragments) {
super(fragmentActivity);
this.fragments = fragments;
}
@NonNull
@Override
public Fragment createFragment(int position) {
return fragments.get(position);
}
@Override
public int getItemCount() {
return fragments.size();
}
}
public class MyOnPageChangeCallback1 extends ViewPager2.OnPageChangeCallback {
private static final String TAG = "MyOnPageChangeCallback";
private final ViewPager2 vp2;
private final int fragmentSize;
private int realPosition = 0;
public MyOnPageChangeCallback1(ViewPager2 vp2, int fragmentSize) {
this.vp2 = vp2;
this.fragmentSize = fragmentSize;
}
@Override
public void onPageScrollStateChanged(int state) {
super.onPageScrollStateChanged(state);
if (state == ViewPager2.SCROLL_STATE_IDLE) {
if (realPosition == (fragmentSize - 1)) {
Log.i(TAG, "最后一页:" + 0);
vp2.setCurrentItem(1, false);
} else if (realPosition == 0) {
Log.i(TAG, "第一页:" + (fragmentSize - 1));
vp2.setCurrentItem(fragmentSize - 2, false);
}
}
}
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
realPosition = position;
}
}
2. 如何获取真实的position和滑动的虚拟position
1. 获取真实的position简单
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
realPosition = position;
}
2.难点在于如何滑动的虚拟position
实现思路:根据滑动方向+ 是否翻过页 来判断是向左还是向右翻页了。
public class MyOnPageChangeCallback2 extends ViewPager2.OnPageChangeCallback {
private static final String TAG = "MyOnPageChangeCallback";
//记录上一次滑动的positionOffsetPixels值
private int lastValue = -1;
// 滑动方向
private boolean turnToLeft = false;
private boolean isScrolling = false;
private int mState;
private ChangeViewCallback changeViewCallback;
private int lastPosition = -1;
private int virtualPosition = 0;
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels);
if (isScrolling) {
if (lastValue > positionOffsetPixels) {
// 递减,向右侧滑动
turnToLeft = false;
} else if (lastValue < positionOffsetPixels) {
// 递减,向右侧滑动
turnToLeft = true;
}
}
lastValue = positionOffsetPixels;
}
@Override
public void onPageScrollStateChanged(int state) {
super.onPageScrollStateChanged(state);
isScrolling = state == ViewPager.SCROLL_STATE_DRAGGING;
this.mState = state;
}
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
if (mState == ViewPager2.SCROLL_STATE_SETTLING) {
Log.i(TAG, "lastPosition:" + lastPosition + ",currentPosition:" + position);
if (changeViewCallback != null && lastPosition != position) {
if (turnToLeft) {
virtualPosition++;
}
if (!turnToLeft) {
virtualPosition--;
}
changeViewCallback.changeView(turnToLeft, virtualPosition);
lastPosition = position;
}
turnToLeft = false;
}
}
/**
* 滑动状态改变回调
*
* @author zxy
*/
public interface ChangeViewCallback {
/**
* 切换视图 ?决定于left和right 。
*
* @param turnToLeft
* @param virtualPosition
*/
public void changeView(boolean turnToLeft, int virtualPosition);
}
public void setChangeViewCallback(ChangeViewCallback callback) {
changeViewCallback = callback;
}
}
3. n 滚动 + 获取滑动的虚拟position
如果实现无限滚动 那虚拟position 就没有任何意义了,所有限定了从第1页翻到n页。
public class MyOnPageChangeCallback extends ViewPager2.OnPageChangeCallback {
private static final String TAG = "MyOnPageChangeCallback";
private ViewPager2 vp2;
// fragment的个数 4个滑动效果最佳
private int fragmentSize;
// 真正数据个数 也就滑动的次数
private int dataSize;
//记录上一次滑动的positionOffsetPixels值
private int lastValue = -1;
// 滑动方向
private boolean turnToLeft = false;
private boolean isScrolling = false;
private ChangeViewCallback changeViewCallback;
private int lastPosition = -1;
private int mState;
private int realPosition = 0;
private int virtualPosition = 0;
public MyOnPageChangeCallback(ViewPager2 vp2, int dataSize, int fragmentSize) {
this.vp2 = vp2;
this.fragmentSize = fragmentSize;
this.dataSize = dataSize;
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels);
if (isScrolling) {
if (lastValue > positionOffsetPixels) {
// 递减,向右侧滑动
turnToLeft = false;
} else if (lastValue < positionOffsetPixels) {
// 递减,向右侧滑动
turnToLeft = true;
}
}
lastValue = positionOffsetPixels;
}
@Override
public void onPageScrollStateChanged(int state) {
super.onPageScrollStateChanged(state);
isScrolling = state == ViewPager.SCROLL_STATE_DRAGGING;
this.mState = state;
if (state == ViewPager2.SCROLL_STATE_IDLE) {
// 没有做无限滚动哦,只能从 1页翻到 n页
if (virtualPosition <= 0 || virtualPosition >= dataSize - 1) {
return;
}
// 小于4 个没有必要,4个fragment 翻页效果最好
if (fragmentSize < 4) {
return;
}
Log.i(TAG, "realPosition:" + realPosition + ",fragments.size():" + (fragmentSize - 1));
//此处为你需要的情况,再加入当前页码判断可知道是第一页还是最后一页
if (realPosition == (fragmentSize - 1)) {
Log.i(TAG, "最后一页:" + 0);
vp2.setCurrentItem(1, false);
} else if (realPosition == 0) {
Log.i(TAG, "第一页:" + (fragmentSize - 1));
vp2.setCurrentItem(fragmentSize - 2, false);
}
}
}
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
realPosition = position;
if (mState == ViewPager2.SCROLL_STATE_SETTLING) {
Log.i(TAG, "lastPosition:" + lastPosition + ",currentPosition:" + position);
if (changeViewCallback != null && lastPosition != position) {
if (turnToLeft) {
virtualPosition++;
}
if (!turnToLeft) {
virtualPosition--;
}
changeViewCallback.changeView(turnToLeft, virtualPosition);
lastPosition = position;
}
turnToLeft = false;
}
}
/**
* 滑动状态改变回调
*
* @author zxy
*/
public interface ChangeViewCallback {
/**
* 切换视图 ?决定于left和right 。
*
* @param turnToLeft
* @param virtualPosition
*/
public void changeView(boolean turnToLeft, int virtualPosition);
}
/**
* set ...
*
* @param callback
*/
public void setChangeViewCallback(ChangeViewCallback callback) {
changeViewCallback = callback;
}
}
public class ViewActivity extends AppCompatActivity {
private ViewPager2 vp2;
private List fragments = new ArrayList<>();
private List datas = new ArrayList<>();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view);
vp2 = (ViewPager2) findViewById(R.id.vp2);
for (int i = 0; i < 30; i++) {
datas.add("数据:" + (i+1));
}
fragments.add(ItemFragment.newInstance("1"));
fragments.add(ItemFragment.newInstance("2"));
fragments.add(ItemFragment.newInstance("3"));
fragments.add(ItemFragment.newInstance("4"));
vp2.setOffscreenPageLimit(2);
vp2.setAdapter(new FragmentPager2Adapter(this, fragments));
MyOnPageChangeCallback callback = new MyOnPageChangeCallback(vp2, 30, 4);
callback.setChangeViewCallback((turnToLeft, virtualPosition) -> {
// datas 通过virtualPosition 拿到对于的数据 再传给 fragment
String bean = datas.get(virtualPosition);
for (Fragment fragment : fragments) {
ItemFragment fragment1 = (ItemFragment) fragment;
fragment1.setData(bean);
}
});
vp2.registerOnPageChangeCallback(callback);
}
}
3. 其他
在实际开发过程中遇见过ViewPager2+Fragment使用FragmentStateAdapter时, Fragment数量变动,需要刷新的时候 notifyDataSetChanged()无效。看了下FragmentStateAdapter源码研究后找到解决之道。
需要重写
@Override
public long getItemId(int position) {
}
@Override
public boolean containsItem(long itemId) {
}
其源码中 getItemId 使用的是position,fragment复用导致数据错乱。可以给fragment绑定一个唯一Id。