4个fragment实现viewpager2的1-N次翻页

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<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

实现思路:根据滑动方向+ 是否翻过页 来判断是向左还是向右翻页了。

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<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("数据:" + (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。

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

推荐阅读更多精彩内容