1.引言
viewpager2是JetPack組件庫推出的組件之一夕膀,是原生viewPager的升級但是不兼容,因為ViewPager2的Adapter 繼承于RecycleView.Adpater烙荷。
ViewPager+Fragment實現(xiàn)無限滾動的方案主要有2種
方案一:將viewpager上限設置成一個很大的數(shù),第一個頁面設置到中間檬寂。然后滑動的時候终抽,用當前的序號與viewpager頁面數(shù)取余得到目標頁面的序號,然后顯示出來桶至。理論上一個人不會無聊到一直左滑或者右滑昼伴。因此可以模擬無限循環(huán)。
方案二:假設viewpager中有四個頁面镣屹,分別為A圃郊、B、C女蜈、D持舆。然后在A左邊添加一個頁面D,在D右邊添加一個頁面A伪窖,變成 D逸寓、A、B覆山、C席覆、D、A汹买。當滑到D時跳轉到D佩伤,滑到A時跳轉到A
本方案使用的是ViewPager2+Fragment 加 方案二 的實現(xiàn)方式。
2. 方案實現(xiàn)
1. 無限的實現(xiàn)
public class ViewActivity extends AppCompatActivity {
private ViewPager2 vp2;
private List<Fragment> fragments = new ArrayList<>();
private List<String> 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<Fragment> fragments;
public FragmentPager2Adapter(@NonNull FragmentActivity fragmentActivity, List<Fragment> 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
實現(xiàn)思路:根據(jù)滑動方向+ 是否翻過頁 來判斷是向左還是向右翻頁了晦毙。
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;
}
}
/**
* 滑動狀態(tài)改變回調
*
* @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
如果實現(xiàn)無限滾動 那虛擬position 就沒有任何意義了,所有限定了從第1頁翻到n頁须揣。
public class MyOnPageChangeCallback extends ViewPager2.OnPageChangeCallback {
private static final String TAG = "MyOnPageChangeCallback";
private ViewPager2 vp2;
// fragment的個數(shù) 4個滑動效果最佳
private int fragmentSize;
// 真正數(shù)據(jù)個數(shù) 也就滑動的次數(shù)
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;
}
}
/**
* 滑動狀態(tài)改變回調
*
* @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<Fragment> fragments = new ArrayList<>();
private List<String> 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("數(shù)據(jù):" + (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 拿到對于的數(shù)據(jù) 再傳給 fragment
String bean = datas.get(virtualPosition);
for (Fragment fragment : fragments) {
ItemFragment fragment1 = (ItemFragment) fragment;
fragment1.setData(bean);
}
});
vp2.registerOnPageChangeCallback(callback);
}
}
3. 其他
在實際開發(fā)過程中遇見過ViewPager2+Fragment使用FragmentStateAdapter時谤碳, Fragment數(shù)量變動,需要刷新的時候 notifyDataSetChanged()無效溢豆⊙鸭颍看了下FragmentStateAdapter源碼研究后找到解決之道。
需要重寫
@Override
public long getItemId(int position) {
}
@Override
public boolean containsItem(long itemId) {
}
其源碼中 getItemId 使用的是position漩仙,fragment復用導致數(shù)據(jù)錯亂搓茬。可以給fragment綁定一個唯一Id队他。