成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

ViewPager2重大更新,支持offscreenPageLimit

番茄西紅柿 / 3745人閱讀

摘要:前言最近發(fā)布了版本,新增功能,該功能在上并不友好,現(xiàn)在官方將此功能延續(xù)下來,這回是騾子是馬呢趕緊拉出來溜溜閱讀指南內(nèi)容基于版本講解,由于正式版還未發(fā)布,如有功能變動有勞看官指出內(nèi)容重點介紹的特性和預(yù)加載機(jī)制,另外包括的狀態(tài)和的生命周

前言

最近ViewPager2發(fā)布了1.0.0-alpha04版本,新增offscreenPageLimit功能,該功能在ViewPager上并不友好,現(xiàn)在官方將此功能延續(xù)下來,這回是騾子是馬呢?趕緊拉出來溜溜;

閱讀指南:

內(nèi)容基于ViewPager21.0.0-alpha04版本講解,由于正式版還未發(fā)布,如有功能變動有勞看官指出

內(nèi)容重點:介紹ViewPager2的offscreenPageLimit特性和預(yù)加載機(jī)制,另外包括Adapter的狀態(tài)和Fragment的生命周期等內(nèi)容

ViewPager頑疾

頑疾是什么鬼,沒有這么嚴(yán)重吧。ViewPager有兩個毛?。?b>不能關(guān)閉預(yù)加載和更新Adapter不生效,所以開頭我為什么說offscreenPageLimitViewPager上十分不友好;本質(zhì)上是因為offscreenPageLimit不能設(shè)置成0(設(shè)置成0就是想象中的關(guān)閉預(yù)加載);

上面是ViewPager默認(rèn)情況下的加載示意圖,當(dāng)切換到當(dāng)前頁面時,會默認(rèn)預(yù)加載左右兩側(cè)的布局到ViewPager中,盡管兩側(cè)的View并不可見的,我們稱這種情況叫預(yù)加載;由于ViewPageroffscreenPageLimit設(shè)置了限制,頁面的預(yù)加載是不可避免;

ViewPager

private static final int DEFAULT_OFFSCREEN_PAGES = 1;

public void setOffscreenPageLimit(int limit) {
    if (limit < DEFAULT_OFFSCREEN_PAGES) {//不允許小于1
        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();
    }
}

ViewPager強(qiáng)制預(yù)加載的邏輯在Fragment配合ViewPager使用時依然存在

Fragment懶加載前因后果

先說PagerAdapter

PagerAdapter常用方法如下:

instantiateItem(ViewGroup container, int position)初始化ItemView,返回需要添加ItemView

destroyItem(iewGroup container, int position, Object object)銷毀ItemView,移除指定的ItemView

isViewFromObject(View view, Object object)View和Object是否對應(yīng)

setPrimaryItem(ViewGroup container, int position, Object object) 當(dāng)前頁面的主Item

getCount()獲取Item個數(shù)

先說setPrimaryItem(ViewGroup container, int position, Object object),該方法表示當(dāng)前頁面正在顯示主要Item,何為主要Item?如果預(yù)加載的ItemView已經(jīng)劃入屏幕,當(dāng)前的PrimaryItem依然不會改變,除非新的ItemView完全劃入屏幕,且滑動已經(jīng)停止才會判斷;

由于ViewPager不可避免的進(jìn)行布局預(yù)加載,造成PagerAdapter必須提前調(diào)用instantiateItem(ViewGroup container, int position)方法,instantiateItem()是創(chuàng)建ItemView的唯一入口方法,所以PagerAdapter的實現(xiàn)類FragmentPagerAdapterFragmentStatePagerAdapter必須抓住該方法進(jìn)行Fragment對象的創(chuàng)建;

碰巧的是,FragmentPagerAdapterFragmentStatePagerAdapter一股腦的在instantiateItem()中進(jìn)行創(chuàng)建且進(jìn)行addattach操作,并沒有在setPrimaryItem()方法中對Fragment進(jìn)行操作;

因此,預(yù)加載會導(dǎo)致不可見的Fragment一股腦的調(diào)用onCreate、onCreateView、onResume等方法,用戶只能通過Fragment.setUserVisibleHint()方法進(jìn)行識別;

大多數(shù)的懶加載都是對Fragment做手腳,結(jié)合生命周期方法和setUserVisibleHint狀態(tài),控制數(shù)據(jù)延遲加載,而布局只能提前進(jìn)入;

ViewPager2基本使用

build.gradle引入

implementation androidx.viewpager2:viewpager2:1.0.0-alpha04

布局文件添加

"@+id/view_pager"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="1" />

設(shè)置ViewHolder+Adapter

ViewPager2 viewPager = findViewById(R.id.view_pager2);
viewPager.setAdapter(new RecyclerView.Adapter() {
        @Override
        public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_card_layout, parent, false);
            ViewHolder viewHolder = new ViewHolder(itemView);
            return viewHolder;
        }
        @Override
        public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
            holder.labelCenter.setText(String.valueOf(position));
        }
        @Override
        public int getItemCount() {
            return SIZE;
        }
    }));

static class ViewHolder extends RecyclerView.ViewHolder{
    private final TextView labelCenter;
    public ViewHolder(@NonNull View itemView) {
        super(itemView);
        labelCenter = itemView.findViewById(R.id.label_center);
    }
}

設(shè)置Fragment+Adapter

viewPager.setAdapter(new FragmentStateAdapter(this) {
        @NonNull
        @Override
        public Fragment getItem(int position) {
            return new VSFragment();
        }

        @Override
        public int getItemCount() {
            return SIZE;
        }
    });

ViewPager2的使用非常簡單,甚至比ViewPager還要簡單,只要熟悉RecyclerView的童鞋肯定會寫ViewPager2;

ViewPager2常用方法如下:

setAdapter() 設(shè)置適配器

setOrientation() 設(shè)置布局方向

setCurrentItem() 設(shè)置當(dāng)前Item下標(biāo)

beginFakeDrag() 開始模擬拖拽

fakeDragBy() 模擬拖拽中

endFakeDrag() 模擬拖拽結(jié)束

setUserInputEnabled() 設(shè)置是否允許用戶輸入/觸摸

setOffscreenPageLimit()設(shè)置屏幕外加載頁面數(shù)量

registerOnPageChangeCallback() 注冊頁面改變回調(diào)

setPageTransformer()?設(shè)置頁面滑動時的變換效果

很多好看好玩的效果,請讀者自行運行官方的DEMO(github.com/googlesampl…);

重要申明

在上文說ViewPager預(yù)加載時,我就在想offscreenPageLimit能不能稱之為預(yù)加載,如果在ViewPager上可以,那么在ViewPager2上可能就要混淆了,因為ViewPager2擁有RecyclerView的一整套緩存策略,包括RecyclerView的預(yù)加載;為了避免混淆,在下面的文章中我把offscreenPageLimit定義為離屏加載,預(yù)加載只代表RecyclerView的預(yù)加載;

ViewPager2離屏加載

1.0.0-alpha04版本中,ViewPager2提供了離屏加載功能,該功能和ViewPager的預(yù)加載存的的意義似乎是一樣的;

ViewPager2

public static final int OFFSCREEN_PAGE_LIMIT_DEFAULT = 0;

public void setOffscreenPageLimit(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();
}

從代碼可以看出,ViewPager2的離屏加載最小可以為0,僅僅從這一步開始,我大膽的猜測ViewPager2支持所謂的懶加載,帶著好奇,看一眼OffscreenPageLimit實現(xiàn)原理;

ViewPager2.LinearLayoutManagerImpl

@Override
protected void calculateExtraLayoutSpace(@NonNull RecyclerView.State state,
        @NonNull int[] extraLayoutSpace) {
    int pageLimit = getOffscreenPageLimit();
    if (pageLimit == OFFSCREEN_PAGE_LIMIT_DEFAULT) {//如果等于默認(rèn)值(0),調(diào)用基類的方法
        // Only do custom prefetching of offscreen pages if requested
        super.calculateExtraLayoutSpace(state, extraLayoutSpace);
        return;
    }
    //返回offscreenSpace
    final int offscreenSpace = getPageSize() * pageLimit;
    extraLayoutSpace[0] = offscreenSpace;
    extraLayoutSpace[1] = offscreenSpace;
}

OffscreenPageLimit本質(zhì)上是重寫LinearLayoutManagercalculateExtraLayoutSpace方法,該方法是最新的recyclerView包加入的功能;

calculateExtraLayoutSpace方法定義了布局額外的空間,何為布局額外的空間?默認(rèn)空間等于RecyclerView的寬高空間,定義這個意在可以放大可布局的空間,該方法參數(shù)extraLayoutSpace是一個長度為2的int數(shù)組,第一條數(shù)據(jù)接受左邊/上邊的額外空間,第二條數(shù)據(jù)接受右邊/下邊的額外空間,故上訴代碼是表明左右/上下各擴(kuò)大offscreenSpace;

綜上代碼,OffscreenPageLimit其實就是放大了LinearLayoutManager的布局空間,我們下面看運行效果;

布局對比

為了對比兩者加載布局的效果,我準(zhǔn)備了LinearLayout同時展示ViewPager和ViewPager2,設(shè)置相同的Item布局和數(shù)據(jù)源,然后用Android布局分析工具抓取兩者的布局結(jié)構(gòu),代碼比較簡單,就不貼出來了;

默認(rèn)offscreenPageLimit

從運行結(jié)果來看,ViewPager會默認(rèn)會預(yù)布局兩側(cè)各一個布局,ViewPager2默認(rèn)不進(jìn)行預(yù)布局,主要由各自的默認(rèn)offscreenPageLimit參數(shù)決定,ViewPager默認(rèn)為1且不允許小于1,ViewPager2默認(rèn)為0

設(shè)置offscreenPageLimit=2

分析運行結(jié)果,在設(shè)置相同的offscreenPageLimit時,兩者都會預(yù)布局左右(上下)兩者的offscreenPageLimit個ItemView;

從對比結(jié)果上來看,ViewPager2offscreenPageLimitViewPager運行結(jié)果一樣,但是ViewPager2最小offscreenPageLimit可以設(shè)置為0;

ViewPager2預(yù)加載和緩存

ViewPager2預(yù)加載RecyclerView的預(yù)加載,代碼在RecyclerViewGapWorker中,這個知識可能有些同學(xué)不是很了解,推薦先看這篇博客medium.com/google-deve…;

ViewPager2上默認(rèn)開啟預(yù)加載,表現(xiàn)形式是在拖動控件或者Fling時,可能會預(yù)加載一條數(shù)據(jù);下面是預(yù)加載的示意圖:

如何關(guān)閉預(yù)加載?

((RecyclerView)viewPager.getChildAt(0)).getLayoutManager().setItemPrefetchEnabled(false);

預(yù)加載的開關(guān)在LayoutManager上,只需要獲取LayoutManager并調(diào)用setItemPrefetchEnabled()即可控制開關(guān);

ViewPager2默認(rèn)會緩存2條ItemView,而且在最新的RecyclerView中可以自定義緩存Item的個數(shù);

RecyclerView

public void setItemViewCacheSize(int size) {
    mRecycler.setViewCacheSize(size);
}

小結(jié): 預(yù)加載緩存View層面沒有本質(zhì)的區(qū)別,都是已經(jīng)準(zhǔn)備了布局,但是沒有加載到parent上; 預(yù)加載離屏加載View層面有本質(zhì)的區(qū)別,離屏加載的View已經(jīng)添加到parent上;

提前加載對Adapter影響

所謂的提前加載,是指當(dāng)前position不可見但加載了布局,包括上面說的預(yù)加載離屏加載,下面先介紹一下Adapter:

ViewPager2Adapter本質(zhì)上是RecyclerView.Adapter,下面列舉常用方法:

onCreateViewHolder(ViewGroup parent, int viewType)創(chuàng)建ViewHolder

onBindViewHolder(VH holder, int position)綁定ViewHolder

onViewRecycled(VH holder)當(dāng)View被回收

onViewAttachedToWindow(VH holder)當(dāng)前View加載到窗口

onViewDetachedFromWindow(VH holder)當(dāng)前View從窗口移除

getItemCount()//獲取Item個數(shù)

下面主要針對ItemView的創(chuàng)建來說,暫不討論回收的情況;

onBindViewHolder 預(yù)加載和離屏加載都會調(diào)用

onViewAttachedToWindow 離屏加載ItemView會調(diào)用,可見ItemView會調(diào)用

onViewDetachedFromWindow 從可見到不可見的ItemView(除離屏中)必定調(diào)用

小結(jié): 預(yù)加載緩存Adapter層面沒有區(qū)別,都會調(diào)用onBindViewHolder方法; 預(yù)加載離屏加載Adapter層面有本質(zhì)的區(qū)別,離屏加載的View會調(diào)用onViewAttachedToWindow

ViewPager2對Fragment支持

目前,ViewPager2Fragment的支持只能使用FragmentStateAdapter,使用起來也是非常簡單:

默認(rèn)情況下,ViewPager2是開啟預(yù)加載關(guān)閉離屏加載的,這種情況下,切換頁面對Fragment生命周如何?

問題一:關(guān)閉預(yù)加載對Fragment的影響: 經(jīng)過驗證,是否開啟預(yù)加載,對Fragment的生命周期沒有影響,結(jié)果和默認(rèn)上圖是一樣的;

問題二:開啟離屏加載對Fragment的影響: 設(shè)置offscreenPageLimit=1時:

打印結(jié)果解讀:

備注:log日志下標(biāo)是從2開始的,標(biāo)注的頁碼是從1開始,請自行矯正;

默認(rèn)情況下,ViewPager2會緩存兩條數(shù)據(jù),所以滑動到第4頁,第1頁的Fragment才開始移除,這可以理解;

設(shè)置offscreenPageLimit=1時,ViewPager2在第1頁會加載兩條數(shù)據(jù),這可以理解,會把下一頁View提前加載進(jìn)來;以后每滑一頁,會加載下一頁數(shù)組,直到第5頁,會移除第1頁的Fragment;第6頁會移除第2頁的Fragment

如何理解offscreenPageLimitFragment的影響,假設(shè)offscreenPageLimit=1,這樣ViewPager2最多可以承托3個ItemView,再加上2個緩存的ItemView,就是5個,由于offscreenPageLimit會在ViewPager2兩邊放置一個,所以向前最多承載4個,向后最多能承載1個(預(yù)加載對Fragment沒有影響,所以不計算),這樣很自然就是第5個時候,回收第1個;

FragmentStateAdapter源碼簡單解讀

onCreateViewHolder()方法

public final FragmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    return FragmentViewHolder.create(parent);
}
static FragmentViewHolder create(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);
}

onCreateViewHolder()創(chuàng)建一個寬高都MATCH_PARENTFrameLayout,注意這里并不像PagerAdapterFragmentrootView;

onBindViewHolder()

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
    //保證目標(biāo)Fragment不為空,意思是可以提前創(chuàng)建
    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();
    //如果ItemView已經(jīng)在添加到Window中,且parent不等于null,會觸發(fā)綁定viewHoder操作;
    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);
                    //將Fragment和ViewHolder綁定
                    placeFragmentInViewHolder(holder);
                }
            }
        });
    }
    //回收垃圾Fragments
    gcFragments();
}

onBindViewHolder()首先會獲取當(dāng)前position對應(yīng)的Fragment,這意味著預(yù)加載的Fragment對象會提前創(chuàng)建;

如果當(dāng)前的holder.itemView已經(jīng)添加到屏幕且已經(jīng)布局且parent不等于空,就會將Fragment綁定到ViewHodler;

每次調(diào)用都會gc一次,主要的避免用戶修改數(shù)據(jù)源造成垃圾對象;

onViewAttachedToWindow()

public final void onViewAttachedToWindow(@NonNull final FragmentViewHolder holder) {
    placeFragmentInViewHolder(holder);
    gcFragments();
}

onViewAttachedToWindow()方法調(diào)用onViewAttachedToWindowFragmenthodler綁定;

onViewRecycled()

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);
    }
}

當(dāng)onViewRecycled()時才會觸發(fā)Fragment移除;

核心添加操作:

 //將Fragment.rootView添加到FrameLayout;
 scheduleViewAttach(fragment, container);//將rootI
 mFragmentManager.beginTransaction().add(fragment, "f" + holder.getItemId()).commitNow();
 
 //主要是監(jiān)聽onFragmentViewCreated方法,獲取rootView然后添加到container
 private void scheduleViewAttach(final Fragment fragment, final FrameLayout container) {
    // After a config change, Fragments that were in FragmentManager will be recreated. Since
    // ViewHolder container ids are dynamically generated, we opted to manually handle
    // attaching Fragment views to containers. For consistency, we use the same mechanism for
    // all Fragment views.
    mFragmentManager.registerFragmentLifecycleCallbacks(
            new FragmentManager.FragmentLifecycleCallbacks() {
                @Override
                public void onFragmentViewCreated(@NonNull FragmentManager fm,
                        @NonNull Fragment f, @NonNull View v,
                        @Nullable Bundle savedInstanceState) {
                    if (f == fragment) {
                        fm.unregisterFragmentLifecycleCallbacks(this);
                        addViewToContainer(v, container);
                    }
                }
            }, false);
}

更詳細(xì)的FragmentStateAdapter源碼解讀盡請期待;

but!!!

Fragment中監(jiān)聽不到setUserVisibleHint

在設(shè)置offscreenPageLimit>0時,Fragment中是監(jiān)聽不到setUserVisibleHint調(diào)用的,我查了源碼沒有調(diào)用,而且該方法被標(biāo)記過時,所以,適用于ViewPager那一套懶加載Fragment在這里恐怕是不行了;

話又說回來,既然想玩懶加載,為啥還要設(shè)置offscreenPageLimit>0呢,offscreenPageLimit=0就自帶懶加載效果;

Adapter小結(jié):

目前ViewPager2Fragment支持只能用FragmentStateAdapterFragmentStateAdapter在遇到預(yù)加載時,只會創(chuàng)建Fragment對象,不會把Fragment真正的加入到布局中,所以自帶懶加載效果;

FragmentStateAdapter不會一直保留Fragment實例,回收的ItemView也會移除Fragment,所以得做好Fragment`重建后恢復(fù)數(shù)據(jù)的準(zhǔn)備;

FragmentStateAdapter在遇到offscreenPageLimit>0時,處理離屏Fragment和可見Fragment沒有什么區(qū)別,所以無法通過setUserVisibleHint判斷顯示與否,這一點知得注意;

ViewPager懶加載請注意

新版的Fragment中(Version 1.1.0-alpha07),該方法setUserVisibleHint已經(jīng)過時,由FragmentTransactionsetMaxLifecycle替代,新版本的FragmentPagerAdapter可以設(shè)置直接調(diào)用生命周期,這代表ViewPager+Fragment懶加載有更好的解決方案,請注意

最后 ViewPager2更多優(yōu)點

由于本章篇幅有點,沒有對ViewPager2進(jìn)行的全面介紹,不代表ViewPager就僅此而已,就當(dāng)前版本來看,ViewPager2的優(yōu)點或者特有的功能如下:

支持RecyclerView級別的復(fù)用

支持預(yù)加載和離屏加載(本章介紹)

支持動態(tài)更新Adapter(ViewPager大坑之一)

支持模擬拖拽

支持豎直方向滑動

支持頁面滑動狀態(tài)監(jiān)聽和頁面變換(延續(xù)了ViewPager的功能)

只想到這么多了

總結(jié)

這一次ViewPager2更新,官方貌似要發(fā)力替換ViewPager了,無論是它高效的復(fù)用還是自帶懶加載,亦或是更新有效的Adapter,都要比ViewPager強(qiáng)大,如果看官老爺們想嘗試升級,在下十分贊賞,但從當(dāng)前版本來看,請謹(jǐn)慎使用Fragment+offscreenPageLimit>0組合的情況。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/7041.html

相關(guān)文章

  • ViewPager2避坑系列】瞬間暴增數(shù)個Fragment

    摘要:前言最近我在關(guān)注的使用,期間一直基于官方的調(diào)試,今天遇到一個奇葩的問題,捉摸了半天最終找到原因,原來是中布局的問題,事后感覺有必要分享一下這個過程,一來可以鞏固測量的知識,二來希望大家能避開這個坑閱讀指南代碼基于,看官老爺最好能下載前言 最近我在關(guān)注ViewPager2的使用,期間一直基于官方的Demo調(diào)試android-viewpager2,今天遇到一個奇葩的問題,捉摸了半天最終找到原因,...

    番茄西紅柿 評論0 收藏0
  • Bootstrap 4重大更新,亮點解讀

    摘要:重大更新亮點解讀月日對來說是個特別的日子不僅是項目四周年紀(jì)念日,也是經(jīng)過了一年密集開發(fā)之后發(fā)布內(nèi)測版的日子。是一次重大更新,幾乎涉及每行代碼。 Bootstrap 4重大更新、亮點解讀 8月19日對Bootstrap來說是個特別的日子——不僅是項目四周年紀(jì)念日,也是經(jīng)過了一年密集開發(fā)之后發(fā)布Bootstrap 4內(nèi)測版的日子。Bootstrap 4是一次重大更新,幾乎涉及每行代碼。 新...

    golden_hamster 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<