思來想去仍是決定把ViewPager2寫了,畢竟針對ViewPager已經寫了3篇了,也不差這最後一哆嗦了。沒看過以前3篇文章的,能夠在這裏自取:java
你的ViewPager八成用錯了。android
錯誤的ViewPager用法(續),會產生內存泄漏?內存溢出?git
FragmentStatePagerAdapter在ViewPager中優化了什麼github
結束今天的這一篇文章,也算是無心間成了一個小的系列了。api
首先來講ViewPager2已經穩定了,你們能夠愉快的用起來了:緩存
dependencies {
implementation "androidx.viewpager2:viewpager2:1.0.0"
}
複製代碼
瞭解基本的api用法確定仍是官方API、基礎使用、遷移ViewPager至ViewPager2。ide
畢竟有些同窗不喜歡看文縐縐的官方文檔,那這裏我就直接貼一下基本的用法,除了佈局之外,就只有一個Adapter稍稍和ViewPager不一樣:函數
private inner class ScreenSlidePagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
override fun getItemCount(): Int = NUM_PAGES
// new本身的Fragment
override fun createFragment(position: Int): Fragment = ScreenSlidePageFragment()
}
複製代碼
很容易遷移,getItemCount()
就是ViewPager裏的getCount()
,createFragment()
是ViewPager中的getItem()
。佈局
基於這倆個方法,官方給予了額外的解釋:post
能夠看到,官方明確提到:createFragment()
須要提供new的實例,而不是複用的實例。這也算是官方層面對你的ViewPager八成用錯了。的間接的佐證。
能夠發現ViewPager2裏的Adapter的方法命名合理的多,createFragment()
,很明顯咱們應該在這個方法return咱們須要建立的Fragment。
不知道你們有沒有注意到構造函數的不一樣,ViewPager2的構造函數接受FragmentActivity或者Fragment,而再也不是FragmentManager。這也算是官方層面上告訴你們什麼狀況下用什麼FragmentManager:
public FragmentStateAdapter(@NonNull FragmentActivity fragmentActivity) {
this(fragmentActivity.getSupportFragmentManager(), fragmentActivity.getLifecycle());
}
public FragmentStateAdapter(@NonNull Fragment fragment) {
this(fragment.getChildFragmentManager(), fragment.getLifecycle());
}
複製代碼
固然,這也不是說就必定Fragment中就用FragmentStateAdapter(@NonNull Fragment fragment)
,Activity下就必定用FragmentStateAdapter(@NonNull FragmentActivity fragmentActivity)
。
官網有這麼一句話:
大部分狀況下,這麼使用是更好的選擇。
所以怎麼使用並不絕對,若是你們充分理解ViewPager的設計和FragmentManager的設計,其實能夠根據本身的需求選擇使用哪一個構造函數。
更多代碼,能夠參考Google的demo
你們應該也都知道,ViewPager2是基於RecycleView實現的,所以勢必可使用DiffUtils。不過現實很骨感,使用DiffUtil還要額外重寫2個方法:
對ViewPager理解比較深入的同窗,看到getItemId()
應該會很熟悉,畢竟是ViewPager時代裏動態更新Fragment的接口api。
並非說
getItemId()
只會在DiffUtil中生效,getItemId()
和ViewPager中的效果相似。只要同一個position下getItemId()
的return的Long不一樣,就會觸發從新createFragment()
。雖然能夠完成更新Fragment的效果,可是會帶來體驗的瑕疵:會「閃一下」。
簡單貼一下Google的用法:
private val items = (1..9).map { longToItem(nextValue++) }.toMutableList()
object : FragmentStateAdapter(this) {
override fun createFragment(position: Int): PageFragment {
val itemId = items.itemId(position)
val itemText = items.getItemById(itemId)
return PageFragment.create(itemText)
}
override fun getItemCount(): Int = items.size
// 主要在於這倆個方法
override fun getItemId(position: Int): Long = items.itemId(position)
override fun containsItem(itemId: Long): Boolean = items.contains(itemId)
}
複製代碼
固然demo中,也提到了DiffUtil的用法:
/** using [DiffUtil] */
val idsOld = items.createIdSnapshot()
performChanges()
val idsNew = items.createIdSnapshot()
DiffUtil.calculateDiff(object : DiffUtil.Callback() {
override fun getOldListSize(): Int = idsOld.size
override fun getNewListSize(): Int = idsNew.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
idsOld[oldItemPosition] == idsNew[newItemPosition]
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
areItemsTheSame(oldItemPosition, newItemPosition)
}, true).dispatchUpdatesTo(viewPager.adapter!!)
複製代碼
看到這裏可能有小夥伴會問:notifyDataSetChanged()
、和DiffUtils的區別是什麼?
這個問題我沒辦法回答,由於答案就是notifyDataSetChanged()
和DiffUtil的區別...DiffUtil的出現就是爲了解決數據diff的問題,畢竟notifyDataSetChanged()
是一股腦更新所有。
固然因爲ViewPager2的特殊性,是否真正去new Fragment,還要基於getItemId()
的實現。不過notifyDataSetChanged()
會實打實的必定調用onCreateViewHolder()
;而DiffUtil則是由我們本身的實現控制。
這就是兩者的區別。
這部分是一個「渾身難受」的點,因爲ViewPager2的獨特性,適配TabLayout須要費點腦筋。若是不能本身適配,可使用Google的提供的適配方案:
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
tab.text = "你須要顯示的Title"
}.attach()
複製代碼
TabLayoutMediator這個類,須要
com.google.android.material:material:1.1.0
及以上。
首先,我們看看FragmentStateAdapter:
public abstract class FragmentStateAdapter extends RecyclerView.Adapter<FragmentViewHolder> implements StatefulAdapter 複製代碼
很直接的繼承RecyclerView.Adapter
,以此ViewPager2的機制是不會脫離RecycleView的。所以接下來,我們看一看onCreateViewHolder()
、onBindViewHolder()
。
onCreateViewHolder()沒啥好說,就是生成一個父佈局,這裏直接貼代碼:
public final FragmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return FragmentViewHolder.create(parent);
}
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;
}
}
複製代碼
重點內容在onBindViewHolder()
中:
final LongSparseArray<Fragment> mFragments = new LongSparseArray<>();
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);
// 判斷在onBindViewHolder()的時候,是否須要removeFragment()
if (boundItemId != null && boundItemId != itemId) {
removeFragment(boundItemId);
mItemIdToViewHolder.remove(boundItemId);
}
mItemIdToViewHolder.put(itemId, viewHolderId);
// 判斷是否回調createFragment()
ensureFragment(position);
// 省略部分代碼
// 特殊狀況下remove到引用
gcFragments();
}
private void ensureFragment(int position) {
// 基於getItemId()的return,判斷mFragemtns中是否有緩存
long itemId = getItemId(position);
if (!mFragments.containsKey(itemId)) {
Fragment newFragment = createFragment(position);
newFragment.setInitialSavedState(mSavedStates.get(itemId));
mFragments.put(itemId, newFragment);
}
}
複製代碼
onBindViewHolder()裏邊的流程仍是比較直接的,和ViewPager很想。總結一句話:借用RecycleView的bind時機,基因而否有緩存,決定是否須要從新new。
這裏和ViewPager不一樣的是移除緩存的策略,也就是上面我們看到的removeFragments()
:
private void removeFragment(long itemId) {
Fragment fragment = mFragments.get(itemId);
// 省略判空
// remove掉View
if (fragment.getView() != null) {
ViewParent viewParent = fragment.getView().getParent();
if (viewParent != null) {
((FrameLayout) viewParent).removeAllViews();
}
}
// remove掉state
if (!containsItem(itemId)) {
mSavedStates.remove(itemId);
}
// 若是沒有被add,直接remove這個Fragemnt
if (!fragment.isAdded()) {
mFragments.remove(itemId);
return;
}
// 若是已經add了,而且containsItem(itemId)仍是true,那麼就存一下state而後remove
if (fragment.isAdded() && containsItem(itemId)) {
mSavedStates.put(itemId, mFragmentManager.saveFragmentInstanceState(fragment));
}
mFragmentManager.beginTransaction().remove(fragment).commitNow();
mFragments.remove(itemId);
}
複製代碼
remove方法一共有三處會被調用,一個是在onBindViewHolder()
中
onBindViewHolder()
調用時,當前bind的ViewHolder的id不是當前ViewHolder的itemId時,纔會調用。(也就是隻會出現從新notify的時候)shouldDelayFragmentTransactions()
都爲false時纔會調用。(也就是savestate的時候)所以,ViewPager2的Fragment移除策略是徹底基於RecycleView的(固然加載策略也是基於RecycleView,畢竟一塊兒的開始是在onBindViewHolder()
方法中)。
ViewPager2總體來講並無什麼特殊的地方,畢竟民間基於RecycleView實現的ViewPager也是數不勝數。
到此也算是給本身的ViewPager系列文章畫下句號了。
接下來差很少會基於官方的資料,結合咱們自身的項目好好聊聊Jetpack。