ViewPager2的使用方式

4字数 681阅读 1937

一、ViewPager2介绍

1 简介

 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. 准备工作

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


    1565751395827_414x900.gif
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


    1565751175277_414x900.gif
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


    1565751357088_414x900.gif
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'

推荐阅读更多精彩内容