ViewPager2的使用方式

一、ViewPager2介绍

 ViewPager2是Google 在 androidx 组件包里增加的一个组件,目前已经到了1.0.0-beta02版本。

谷歌为什么要出这个组件呢?官方是这么说的:

ViewPager2 replaces ViewPager, addressing most of its predecessor’s pain-points, 
including right-to-left layout support, vertical orientation, modifiable Fragment collections, etc.

2 具体改动:

New features:

  • 支持竖向滚动

  • 完整支持notifyDataSetChanged

  • 能够关闭用户输入 (setUserInputEnabled, isUserInputEnabled)

  • API changes:

  • FragmentStateAdapter 替代 FragmentStatePagerAdapter

  • RecyclerView.Adapter 替代 PagerAdapter

  • registerOnPageChangeCallback 替代 addPageChangeListener

  • 3 附上官方链接:

    https://developer.android.google.cn/jetpack/androidx/releases/viewpager2#1.0.0-alpha01

    官方Demo
    https://github.com/googlesamples/android-viewpager2

    二、ViewPager2的使用

    1. 准备工作

  • AndroidX适配参考文档:
    https://developer.android.com/jetpack/androidx/migrate
    https://www.jianshu.com/p/41de8689615d
  • 修改gradle.properties
  • android.useAndroidX=true
    android.enableJetifier=true
    

    android.useAndroidX=true 表示当前项目启用 AndroidX

    android.enableJetifier=true 表示将依赖包也迁移到AndroidX 。如果取值为 false ,表示不迁移依赖包到AndroidX,但在使用依赖包中的内容时可能会出现问题,当然了,如果你的项目中没有使用任何三方依赖,那么,此项可以设置为 false

    implementation 'androidx.appcompat:appcompat:1.1.0-alpha05'
    implementation 'com.android.support:design:28.0.0'
    implementation 'androidx.viewpager2:viewpager2:1.0.0-beta02'
    

    2. xml文件

    <androidx.viewpager2.widget.ViewPager2
            android:id="@+id/viewpager2"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal" />
    

    3. 常用Api

  • void setOrientation(int orientation)
  • void setUserInputEnabled(boolean enabled)
  • int getCurrentItem()
  • void setCurremt(int item)
  • void addItemDecoration(RecyclerView.ItemDecoration decor)
  • void addItemDecoration(RecyclerView.ItemDecoration decor, int index)
  • void beginFakeDrag()
  • endFakeDrag()
  • getAdapter()
  • setOffscreenPageLimit(int limit)
  • setPageTransformer(ViewPager2.PageTransformer transformer)
  • registerOnPageChangeCallback(OnPageChangeCallback).
  • unregisterOnPageChangeCallback(ViewPager2.OnPageChangeCallback callback)
  • 4.ViewPager2的Demo

    ViewPager2 with Views

    viewPager2 = findViewById(R.id.viewpager2);
    viewPager2.setAdapter(new ViewPagerAdapter());
    public class ViewPagerAdapter extends RecyclerView.Adapter<ViewPagerAdapter.CardViewHolder> {
        @NonNull
        @Override
        public ViewPagerAdapter.CardViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            return new CardViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.view_item, parent, false));
        @Override
        public void onBindViewHolder(@NonNull ViewPagerAdapter.CardViewHolder holder, int position) {
            holder.textView.setText(mDatas.get(position));
        @Override
        public int getItemCount() {
            return mDatas.size();
        public static class CardViewHolder extends RecyclerView.ViewHolder {
            public TextView textView;
            public CardViewHolder(@NonNull View itemView) {
                super(itemView);
                textView = itemView.findViewById(R.id.tv_content);
    

    是不是很简单?adapter和使用RecyclerView是一样的,这个大家都很熟悉了吧?

    ViewPager2 with Fragments

    viewPager.setAdapter(new ViewPagerFragmentStateAdapter(),colors);
    public class ViewPagerFragmentStateAdapter extends FragmentStateAdapter {
        @NonNull
        @Override
        public Fragment createFragment(int position) {
            return PageFragment.newInstance(colors, position);
        @Override
        public int getItemCount() {
            return colors.size();
    

    ViewPager2和Fragment结合使用,需要使用FragmentStateAdapter。FragmentStateAdapter继承RecyclerView.Adapter,有兴趣的可以去看看源码。

    ViewPager2 with TabLayout

    mViewPager2.setAdapter(adapter);
    new TabLayoutMediator(mTabLayout, mViewPager2, (tab, position) -> tab.setText(titles.get(position))).attach();
     // 滑动监听
    mViewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
                @Override
                public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                    super.onPageScrolled(position, positionOffset, positionOffsetPixels);
                @Override
                public void onPageSelected(int position) {
                    super.onPageSelected(position);
                @Override
                public void onPageScrollStateChanged(int state) {
                    super.onPageScrollStateChanged(state);
    

    androidx中,TabLayout没有setupWithViewPager(ViewPager2 viewPager2)方法,而是用TabLayoutMediator将TabLayout和ViewPager2结合。

  • 几个api的使用示例和效果
  • void setOffscreenPageLimit(boolean enable)
  • void setUserInputEnabled(boolean enable)
  • void beginFakeDrag()
  • void notifyDataSetChanged();
  • Demo: ViewMutableActivity.java

    1565751313150_414x900.gif
    public class ViewMutableActivity extends AppCompatActivity implements View.OnClickListener {
        private void initViews() {
            landscape = getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
            adapter = new MuTableViewPagerAdapter(this, model);
            mViewPager2.setAdapter(adapter);
        private void setListener() {
            CheckBox checkBox = findViewById(R.id.disable_user_input_checkbox);
            checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
                if (isChecked) {
                    mViewPager2.setUserInputEnabled(false);
                } else {
                    mViewPager2.setUserInputEnabled(true);
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.buttonUpdate:
                    model.update(mViewPager2.getCurrentItem(), "update content");
                    adapter.notifyItemChanged(mViewPager2.getCurrentItem());
                    break;
                case R.id.buttonAddBefore:
                    int oldPosition = mViewPager2.getCurrentItem();
                    String content = model.getData(oldPosition);
                    model.add(oldPosition, "is new data");
                    adapter.notifyDataSetChanged();
                    if (model.contains(content)) {
                        int newPositin = model.getPosition(content);
                        mViewPager2.setCurrentItem(newPositin,false);
                    break;
                case R.id.buttonAddAfter:
                    int oldPosition1 = mViewPager2.getCurrentItem();
                    String content1 = model.getData(oldPosition1);
                    model.add(oldPosition1 + 1, "is new data");
                    adapter.notifyDataSetChanged();
                    if (model.contains(content1)) {
                        int newPositin = model.getPosition(content1);
                        mViewPager2.setCurrentItem(newPositin,false);
                    break;
                case R.id.buttonRemove:
                    if(!TextUtils.isEmpty(editText.getText().toString())){
                        int oldPosition2 = Integer.parseInt(editText.getText().toString());
                        if(oldPosition2 < model.getSize()){
                            model.removeData(oldPosition2);
                            adapter.notifyDataSetChanged();
                    break;
                case R.id.tv_vertical:
                    mViewPager2.setOrientation(ViewPager2.ORIENTATION_VERTICAL);
                    break;
                case R.id.tv_horizontal:
                    mViewPager2.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);
                    break;
                case R.id.tv_scroll:
                    mViewPager2.setUserInputEnabled(true);
                    break;
                case R.id.tv_unscroll:
                    mViewPager2.setUserInputEnabled(false);
                    break;
        private final float getValue(MotionEvent event) {
            return this.landscape ? event.getY() : event.getX();
        private boolean handleOnTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    lastValue = getValue(event);
                    mViewPager2.beginFakeDrag();
                    break;
                case MotionEvent.ACTION_MOVE:
                    float value = getValue(event);
                    float delta = value - lastValue;
                    mViewPager2.fakeDragBy(delta);
                    lastValue = value;
                    break;
                case MotionEvent.ACTION_CANCEL:
                case MotionEvent.ACTION_UP:
                    mViewPager2.endFakeDrag();
                    break;
            return true;
    

    三、ViewPager2到底好在哪里

    1、使用更加方便

    通过ViewPager2的介绍可以看出,ViewPager2实现滑动方向的切换,禁止滑动这些都有API,开发者可以很方便的根据需求进行修改。

    而ViewPager则需要根据不同的情况,重写方法。
    比如禁止滑动:

     public class ScrollViewPager extends ViewPager {
       @Override
       public boolean onInterceptTouchEvent(MotionEvent event) {
          if (isScroll) {
               return super.onInterceptTouchEvent(event);
           } else {
               return false;
        @Override
       public boolean onTouchEvent(MotionEvent event) {
           if (isScroll) {
               return super.onTouchEvent(event);
           } else {
               return true;
    

    2、性能上提升

  • ViewPager2实现了懒加载和View复用。
  • ViewPager2

    The given value must either be larger than 0, or {@code #OFFSCREEN_PAGE_LIMIT_DEFAULT(-1)}. public static final int OFFSCREEN_PAGE_LIMIT_DEFAULT = -1; public void setOffscreenPageLimit(@OffscreenPageLimit int limit) { if (limit < 1 && limit != OFFSCREEN_PAGE_LIMIT_DEFAULT) { throw new IllegalArgumentException( "Offscreen page limit must be OFFSCREEN_PAGE_LIMIT_DEFAULT or a number > 0"); mOffscreenPageLimit = limit; // Trigger layout so prefetch happens through getExtraLayoutSize() mRecyclerView.requestLayout();

    ViewPager

    public static final int DEFAULT_OFFSCREEN_PAGES = 1;
       public void setOffscreenPageLimit(int limit) {
           if (limit < DEFAULT_OFFSCREEN_PAGES) {
               Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
                       + DEFAULT_OFFSCREEN_PAGES);
               limit = DEFAULT_OFFSCREEN_PAGES;
           if (limit != mOffscreenPageLimit) {
               mOffscreenPageLimit = limit;
               populate();
    

    从源码中可以看出,ViewPager2的limit必须大于0或者是-1,而ViewPager的limit最小是1。VIewPager2可以不预加载,通过Fragment的生命周期可以验证。

    ViewPager2支持局部刷新

    notifyDataSetChanged();
    notifyItemChanged(int position)
    

    ViewPager 只能全局刷新

    notifyDataSetChanged();
    

    四、使用过程中的坑

  • 官方ViewPager2 with TabLayout示例代码闪退,几个意思?
  •      Caused by: java.lang.ClassCastException: Bootstrap method returned null
            at com.google.android.material.tabs.TabLayout$TabView.addOnLayoutChangeListener(TabLayout.java:2592) 
            at com.google.android.material.tabs.TabLayout$TabView.update(TabLayout.java:2508) 
            at com.google.android.material.tabs.TabLayout$TabView.setTab(TabLayout.java:2437) 
            at com.google.android.material.tabs.TabLayout.createTabView(TabLayout.java:1501) 
            at com.google.android.material.tabs.TabLayout.newTab(TabLayout.java:855) 
            at com.google.android.material.tabs.TabLayoutMediator.populateTabsFromPagerAdapter(TabLayoutMediator.java:142) 
            at com.google.android.material.tabs.TabLayoutMediator.attach(TabLayoutMediator.java:118) 
            at com.example.myviewpager2.CardViewTabLayoutActivity.onCreate(CardViewTabLayoutActivity.kt:37) 
            at android.app.Activity.performCreate(Activity.java:7441) 
            at android.app.Activity.performCreate(Activity.java:7431) 
            at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1286) 
            at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3343) 
            at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3548) 
            at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:86) 
            at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108) 
            at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68) 
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2155) 
            at android.os.Handler.dispatchMessage(Handler.java:109) 
            at android.os.Looper.loop(Looper.java:207) 
            at android.app.ActivityThread.main(ActivityThread.java:7539) 
            at java.lang.reflect.Method.invoke(Native Method) 
            at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:524) 
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:958)
    
     'com.google.android.material:material:1.1.0-alpha05' 替代
     'com.google.android.material:material:1.1.0-alpha08'