欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Android ViewPager2中的緩存和復用機制

 更新時間:2021年11月02日 09:32:21   作者:姜斌  
最近接觸到豎向整頁滑動的需求,發(fā)現(xiàn)了viewpager2,viewpager2支持fragment,保留了viewpager的特性,下面這篇文章主要給大家介紹了關(guān)于ViewPager2中的緩存和復用機制的相關(guān)資料,需要的朋友可以參考下

1. 前言

眾所周知ViewPager2是ViewPager的替代版本。它解決了ViewPager的一些痛點,包括支持right-to-left布局,支持垂直方向滑動,支持可修改的Fragment集合等。ViewPager2內(nèi)部是使用RecyclerView來實現(xiàn)的。

所以它繼承了RecyclerView的優(yōu)勢,包含但不限于以下:

  1. 支持橫向和垂直方向布局
  2. 支持嵌套滑動
  3. 支持ItemPrefetch(預加載)功能
  4. 支持三級緩存

ViewPager2相對于RecyclerView,它又擴展出了以下功能

  1. 支持屏蔽用戶觸摸功能setUserInputEnabled
  2. 支持模擬拖拽功能fakeDragBy
  3. 支持離屏顯示功能setOffscreenPageLimit
  4. 支持顯示Fragment的適配器FragmentStateAdapter

如果熟悉RecyclerView,那么上手ViewPager2將會非常簡單。可以簡單把ViewPager2想象成每個ItemView都是全屏的RecyclerView。本文將重點講解ViewPager2的離屏顯示功能和基于FragmentStateAdapter的緩存機制。

2. 回顧RecyclerView緩存機制

本章節(jié),簡單回顧下RecyclerView緩存機制。RecyclerView有三級緩存,簡單起見,這里只介紹mViewCaches和mRecyclerPool兩種緩存池。更多關(guān)于RecyclerView的緩存原理,請移步公眾號相關(guān)文章。

  1. mViewCaches:該緩存離UI更近,效率更高,它的特點是只要position能對應(yīng)上,就可以直接復用ViewHolder,無需重新綁定,該緩存池是用隊列實現(xiàn)的,先進先出,默認大小為2,如果RecyclerView開啟了預抓取功能,則緩存池大小為2+預抓取個數(shù),默認預抓取個數(shù)為1。所以默認開啟預抓取緩存池大小為3。
  2. mRecyclerPool:該緩存池離UI最遠,效率比mViewCaches低,回收到該緩存池的ViewHolder會將數(shù)據(jù)解綁,當復用該ViewHolder時,需要重新綁定數(shù)據(jù)。它的數(shù)據(jù)結(jié)構(gòu)是類似HashMap。key為itemType,value是數(shù)組,value存儲ViewHolder,數(shù)組默認大小為5,最多每種itemType的ViewHolder可以存儲5個。

3. offscreenPageLimit原理

//androidx.viewpager2:ViewPager2:1.0.0@aar
//ViewPager2.java
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;
    mRecyclerView.requestLayout();
  }

調(diào)用setOffscreenPageLimit方法就可以為ViewPager2設(shè)置離屏顯示的個數(shù),默認值為-1。如果設(shè)置不當,會拋異常。我們看到該方法,只是給mOffscreenPageLimit賦值。為什么就能實現(xiàn)離屏顯示功能呢?如下代碼

//androidx.viewpager2:ViewPager2:1.0.0@aar
//ViewPager2$LinearLayoutManagerImpl
@Override
protected void calculateExtraLayoutSpace(@NonNull RecyclerView.State state,
        @NonNull int[] extraLayoutSpace) {
    int pageLimit = getOffscreenPageLimit();
    if (pageLimit == OFFSCREEN_PAGE_LIMIT_DEFAULT) {
        super.calculateExtraLayoutSpace(state, extraLayoutSpace);
        return;
    }
    final int offscreenSpace = getPageSize() * pageLimit;
    extraLayoutSpace[0] = offscreenSpace;
    extraLayoutSpace[1] = offscreenSpace;
}

以水平滑動ViewPager2為例:getPageSize()表示ViewPager2的寬度,離屏的空間大小為getPageSize() * pageLimit。extraLayoutSpace[0]表示左邊的大小,extraLayoutSpace[1]表示右邊的大小。

假設(shè)設(shè)置offscreenPageLimit為1,簡單講,Android系統(tǒng)會默認把畫布寬度增加到3倍。左右兩邊各有一個離屏ViewPager2的寬度。

4. FragmentStateAdapter原理以及緩存機制

4.1 簡單使用

FragmentStateAdapter繼承自RecyclerView.Adapter。它有一個抽象方法,createFragment()。它能將Fragment與ViewPager2完美結(jié)合。

public abstract class FragmentStateAdapter extends
        RecyclerView.Adapter<FragmentViewHolder> implements StatefulAdapter {
    public abstract Fragment createFragment(int position);
}

使用FragmentStateAdapter非常簡單,Demo如下

class ViewPager2WithFragmentsActivity : AppCompatActivity() {
    private lateinit var mViewPager2: ViewPager2
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_recycler_view_view_pager2)
        mViewPager2 = findViewById(R.id.viewPager2)
        (mViewPager2.getChildAt(0) as RecyclerView).layoutManager?.apply {
//            isItemPrefetchEnabled = false
        }
        mViewPager2.orientation = ViewPager2.ORIENTATION_VERTICAL
        mViewPager2.adapter = MyAdapter(this)
//        mViewPager2.offscreenPageLimit = 1
    }

    inner class MyAdapter(fragmentActivity: FragmentActivity) :
        FragmentStateAdapter(fragmentActivity) {
        override fun getItemCount(): Int {
            return 100
        }

        override fun createFragment(position: Int): Fragment {
            return MyFragment("Item $position")
        }

    }

    class MyFragment(val text: String) : Fragment() {
        init {
            println("MyFragment $text")
        }
        override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            var view = layoutInflater.inflate(R.layout.view_item_view_pager_snap, container)
            view.findViewById<TextView>(R.id.text_view).text = text
            return view;
        }
    }
}

4.2 原理

首先FragmentStateAdapter對應(yīng)的ViewHolder定義如下,它只是返回一個簡單的帶有id的FrameLayout。由此可以看出,F(xiàn)ragmentStateAdapter并不復用Fragment,它僅僅是復用FrameLayout而已。

public final class FragmentViewHolder extends ViewHolder {
    private FragmentViewHolder(@NonNull FrameLayout container) {
        super(container);
    }

    @NonNull static FragmentViewHolder create(@NonNull ViewGroup parent) {
        FrameLayout container = new FrameLayout(parent.getContext());
        container.setLayoutParams(
                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.MATCH_PARENT));
        container.setId(ViewCompat.generateViewId());
        container.setSaveEnabled(false);
        return new FragmentViewHolder(container);
    }

    @NonNull FrameLayout getContainer() {
        return (FrameLayout) itemView;
    }
}

然后介紹FragmentStateAdapter中兩個非常重要的數(shù)據(jù)結(jié)構(gòu):

final LongSparseArray<Fragment> mFragments = new LongSparseArray<>();

private final LongSparseArray<Integer> mItemIdToViewHolder = new LongSparseArray<>();

mFragments:是position與Fragment的映射表。隨著position的增長,F(xiàn)ragment是會不斷的新建出來的。 Fragment可以被緩存起來,當它被回收后無法重復使用。

Fragment什么時候會被回收掉呢?

mItemIdToViewHolder:是position與ViewHolder的Id的映射表。由于ViewHolder是RecyclerView緩存機制的載體。所以隨著position的增長,ViewHolder并不會像Fragment那樣不斷的新建出來,而是會充分利用RecyclerView的復用機制。所以如下圖,position 4處打上了一個大大的問號,具體的值是不確定的,它由緩存的大小以及離屏個數(shù)共同決定的。

接下來我們講解onViewRecycled()。當ViewHolder從mViewCaches緩存中移出到mRecyclerPool緩存中時會調(diào)用該方法

@Override
public final void onViewRecycled(@NonNull FragmentViewHolder holder) {
    final int viewHolderId = holder.getContainer().getId();
    final Long boundItemId = itemForViewHolder(viewHolderId); // item currently bound to the VH
    if (boundItemId != null) {
        removeFragment(boundItemId);
        mItemIdToViewHolder.remove(boundItemId);
    }
}

該方法的作用是,當ViewHolder回收到RecyclerPool中時,將ViewHolder相關(guān)的信息從上面兩張表中移除。

舉例 當ViewHolder1發(fā)生回收時,position 0對應(yīng)的信息從兩張表中刪除

最后講解onBindViewHolder方法

@Override
public final void onBindViewHolder(final @NonNull FragmentViewHolder holder, int position) {
    final long itemId = holder.getItemId();
    final int viewHolderId = holder.getContainer().getId();
    final Long boundItemId = itemForViewHolder(viewHolderId); // item currently bound to the VH
    if (boundItemId != null && boundItemId != itemId) {
        removeFragment(boundItemId);
        mItemIdToViewHolder.remove(boundItemId);
    }

    mItemIdToViewHolder.put(itemId, viewHolderId); // this might overwrite an existing entry
    ensureFragment(position);

    /** Special case when {@link RecyclerView} decides to keep the {@link container}
     * attached to the window, but not to the view hierarchy (i.e. parent is null) */
    final FrameLayout container = holder.getContainer();
    if (ViewCompat.isAttachedToWindow(container)) {
        if (container.getParent() != null) {
            throw new IllegalStateException("Design assumption violated.");
        }
        container.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
            @Override
            public void onLayoutChange(View v, int left, int top, int right, int bottom,
                    int oldLeft, int oldTop, int oldRight, int oldBottom) {
                if (container.getParent() != null) {
                    container.removeOnLayoutChangeListener(this);
                    placeFragmentInViewHolder(holder);
                }
            }
        });
    }

    gcFragments();
}

該方法可以分成3個部分:

  1. 檢查該復用的ViewHolder在兩張表中是否還有殘留的數(shù)據(jù),如果有,將它從兩張表中移除掉。
  2. 新建Fragment,并將ViewHolder與Fragment和position的信息注冊到兩張表中
  3. 在合適的時機把Fragment展示在ViewPager2上。

大概的脈絡(luò)就是這樣,為了避免文章冗余,其它的細支且也蠻重要的方法就沒有列出來

5. 案例講解回收機制

5.1 默認情況

默認情況:offscreenPageLimit = -1,開啟預抓取功能

因為開啟了預抓取,所以mViewCaches大小為3。

  1. 剛開始進入ViewPager2,沒有觸發(fā)Touch事件,不會觸發(fā)預抓取,所以只有Fragment1
  2. 滑動到Fragment2,會觸發(fā)Fragment3預抓取,由于offscreenPageLimit = -1,所以只有Fragment2會展示在ViewPager2上,1和3進入mViewCaches緩存中
  3. 滑動到Fragment3。1、2、4進入mViewCaches緩存中
  4. 滑動到Fragment4。2、3、5進入mViewCaches緩存中,由于緩存數(shù)量為3,所以1被擠出到mRecyclerPool緩存中,同時把Fragment1從mFragments中移除掉
  5. 滑動到Fragment5。Fragment6會復用Fragment1對應(yīng)的ViewHolder。3、4、6進入mViewCaches緩存中,2被擠出到mRecyclerPool緩存中

5.2 offscreenPageLimit=1

offscreenPageLimit=1,所以ViewPager2一下子能展示3屏Fragment,左右各顯示一屏

  1. Fragment1左邊沒有數(shù)據(jù),所以屏幕只有1和2
  2. 滑動到fragment2,1、2、3顯示在屏幕上(1和3肉眼不可見,下同),同時預抓取4放入mViewCaches
  3. 滑動到fragment3,2、3、4顯示在屏幕上,1和5放入mViewCaches
  4. 滑動到fragment4,3、4、5顯示在屏幕上,1、2、6放入mViewCaches
  5. 滑動到fragment5,4、5、6顯示在屏幕上,2、3、7放入mViewCaches,1被回收到mRecyclerPool緩存中。Fragment1同時從mFragments中刪除掉

總結(jié)

到此這篇關(guān)于Android ViewPager2中緩存和復用機制的文章就介紹到這了,更多相關(guān)ViewPager2緩存和復用機制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Flutter狀態(tài)管理Provider示例解析

    Flutter狀態(tài)管理Provider示例解析

    這篇文章主要為大家介紹了Flutter狀態(tài)管理Provider示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-12-12
  • Android Studio修改Log信息顏色的實現(xiàn)

    Android Studio修改Log信息顏色的實現(xiàn)

    這篇文章主要介紹了Android Studio修改Log信息顏色的實現(xiàn),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-04-04
  • 詳解Android中Handler的實現(xiàn)原理

    詳解Android中Handler的實現(xiàn)原理

    這篇文章主要為大家詳細介紹了Android中Handler的實現(xiàn)原理,本文深入分析 Android 的消息處理機制,了解 Handler 的工作原理,感興趣的小伙伴們可以參考一下
    2016-04-04
  • Android MonoRepo多倉和單倉的差別理論

    Android MonoRepo多倉和單倉的差別理論

    這篇文章主要為大家介紹了Android MonoRepo多倉和單倉的差別理論,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-06-06
  • Android中Notification的用法匯總

    Android中Notification的用法匯總

    這篇文章主要介紹了Android中Notification的用法匯總的相關(guān)資料,需要的朋友可以參考下
    2016-01-01
  • Android加載View中Background詳解

    Android加載View中Background詳解

    本文講解的是Android什么時候進行View中Background的加載,十分的詳盡,十分全面細致,附上所有代碼,這里推薦給大家,希望大家能夠喜歡。
    2015-03-03
  • Android為按鈕控件綁定事件的五種實現(xiàn)方式

    Android為按鈕控件綁定事件的五種實現(xiàn)方式

    本篇文章主要是介紹了Android為按鈕控件綁定事件的五種實現(xiàn)方式,具有一定的參考價值,感興趣的小伙伴們可以參考一下。
    2016-11-11
  • JetpackCompose Scaffold組件使用教程

    JetpackCompose Scaffold組件使用教程

    在今年的Google/IO大會上,亮相了一個全新的 Android 原生 UI 開發(fā)框架-Jetpack Compose, 與蘋果的SwiftIUI一樣,Jetpack Compose是一個聲明式的UI框架
    2023-01-01
  • Android實現(xiàn)漸變色的圓弧虛線效果

    Android實現(xiàn)漸變色的圓弧虛線效果

    最近在學習Android的paint類的時候,學習了PathEffect路徑效果和Shader渲染效果。所以就做了下面的一個效果,其中自定義的view組主要是用DashPathEffect、SweepGradient的API形成的效果。感興趣的朋友們可以參考借鑒,下面來一起看看吧。
    2016-10-10
  • Android內(nèi)存泄漏的輕松解決方法

    Android內(nèi)存泄漏的輕松解決方法

    這篇文章主要給大家介紹了關(guān)于Android內(nèi)存泄漏的輕松解決方法,文中通過示例代碼介紹的非常詳細,對各位Android具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧
    2019-04-04

最新評論