ViewPager2的使用方式

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

推荐阅读更多精彩内容