ViewPager2的使用方式

一刃麸、ViewPager2介紹

1 簡(jiǎn)介

 ViewPager2是Google 在 androidx 組件包里增加的一個(gè)組件帚桩,目前已經(jīng)到了1.0.0-beta02版本。

谷歌為什么要出這個(gè)組件呢锌奴?官方是這么說的:

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

2 具體改動(dòng):

New features:

  • 支持豎向滾動(dòng)

  • 完整支持notifyDataSetChanged

  • 能夠關(guān)閉用戶輸入 (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. 準(zhǔn)備工作

android.useAndroidX=true
android.enableJetifier=true

android.useAndroidX=true 表示當(dāng)前項(xiàng)目啟用 AndroidX

android.enableJetifier=true 表示將依賴包也遷移到AndroidX 伊者。如果取值為 false ,表示不遷移依賴包到AndroidX,但在使用依賴包中的內(nèi)容時(shí)可能會(huì)出現(xiàn)問題胁赢,當(dāng)然了企蹭,如果你的項(xiàng)目中沒有使用任何三方依賴,那么智末,此項(xiàng)可以設(shè)置為 false

  • 依賴庫(kù)
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);
        }
    }
}

是不是很簡(jiǎn)單谅摄?adapter和使用RecyclerView是一樣的,這個(gè)大家都很熟悉了吧系馆?

  • 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結(jié)合使用送漠,需要使用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();

 // 滑動(dòng)監(jiān)聽
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結(jié)合下隧。

  • 幾個(gè)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實(shí)現(xiàn)滑動(dòng)方向的切換句惯,禁止滑動(dòng)這些都有API土辩,開發(fā)者可以很方便的根據(jù)需求進(jìn)行修改。

而ViewPager則需要根據(jù)不同的情況抢野,重寫方法拷淘。
比如禁止滑動(dòng):

 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實(shí)現(xiàn)了懶加載和View復(fù)用指孤。

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可以不預(yù)加載结洼,通過Fragment的生命周期可以驗(yàn)證黎做。

  • 刷新

ViewPager2支持局部刷新

notifyDataSetChanged();
notifyItemChanged(int position)
...

ViewPager 只能全局刷新

notifyDataSetChanged();

四、使用過程中的坑

  • 官方ViewPager2 with TabLayout示例代碼閃退松忍,幾個(gè)意思蒸殿?
     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'
  • 如果在已創(chuàng)建的項(xiàng)目使用androidX,先看看需要注意的點(diǎn)
  • 這里推薦一個(gè)android.support向androidX遷移的文章
    http://www.reibang.com/p/41de8689615d
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鸣峭,一起剝皮案震驚了整個(gè)濱河市宏所,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌叽掘,老刑警劉巖楣铁,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異更扁,居然都是意外死亡盖腕,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門浓镜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來溃列,“玉大人,你說我怎么就攤上這事膛薛√” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵哄啄,是天一觀的道長(zhǎng)雅任。 經(jīng)常有香客問我,道長(zhǎng)咨跌,這世上最難降的妖魔是什么沪么? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮锌半,結(jié)果婚禮上禽车,老公的妹妹穿的比我還像新娘。我一直安慰自己刊殉,他們只是感情好殉摔,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著记焊,像睡著了一般逸月。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上遍膜,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天彻采,我揣著相機(jī)與錄音腐缤,去河邊找鬼。 笑死肛响,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的惜索。 我是一名探鬼主播特笋,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼巾兆!你這毒婦竟也來了猎物?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤角塑,失蹤者是張志新(化名)和其女友劉穎蔫磨,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體圃伶,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡堤如,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了窒朋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搀罢。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖侥猩,靈堂內(nèi)的尸體忽然破棺而出榔至,到底是詐尸還是另有隱情,我是刑警寧澤欺劳,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布唧取,位于F島的核電站,受9級(jí)特大地震影響划提,放射性物質(zhì)發(fā)生泄漏枫弟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一腔剂、第九天 我趴在偏房一處隱蔽的房頂上張望媒区。 院中可真熱鬧,春花似錦掸犬、人聲如沸袜漩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)宙攻。三九已至,卻和暖如春介褥,著一層夾襖步出監(jiān)牢的瞬間座掘,已是汗流浹背递惋。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留溢陪,地道東北人萍虽。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像形真,于是被迫代替她去往敵國(guó)和親杉编。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容