Android?ViewPager你可能不知道的刷新操作分享
前言
哎呀,這個(gè)我會(huì)。不就是 mViewPagerAdapter.notifyDataSetChanged(); 嘛,簡(jiǎn)單!
這個(gè)可能真不是那么簡(jiǎn)單,我們以常用的 ViewPager + Fragment 的使用為例。你調(diào)用 notifyDataSetChanged 刷新方法,會(huì)走到 getItemPosition 方法中查詢當(dāng)前Item是否需要刷新,而它的默認(rèn)實(shí)現(xiàn)是:
public int getItemPosition(@NonNull Object object) {
return POSITION_UNCHANGED;
}永遠(yuǎn)標(biāo)記不刷新,那么不管你是添加Pager,刪除Pager,改變Pager都是不生效的。
那有些同學(xué)就會(huì)說(shuō)了,每次刷新還要做差分?我直接一把梭,直接重新設(shè)置一個(gè) Adapter 不就萬(wàn)事大吉了?
反正每次接口數(shù)據(jù)回來(lái)都重新設(shè)置 Adapter ,還管什么性能不性能,效果實(shí)現(xiàn)了再說(shuō)!
mViewPager.setAdapter(null);
mViewPagerAdapter = new ViewPagerAdapter(getChildFragmentManager(),mFragmentList);
mViewPager.setAdapter(mViewPagerAdapter);
mViewPager.setOffscreenPageLimit(mFragmentList.size() - 1);但是就算如此也是有問(wèn)題的,當(dāng)我們一個(gè)頁(yè)面中根據(jù)不同的篩選條件,服務(wù)端返回不同數(shù)量的數(shù)組,我們就要展示不同數(shù)量的 ViewPager 如果這樣刷新 ViewPager 就可能出現(xiàn)顯示問(wèn)題。
怎么解決?幾種方案,接下來(lái)往下看:
一、清緩存重置Adapter的方案
如果除開(kāi)性能問(wèn)題,想直接每次直接替換一個(gè) Adapter 其實(shí)也是可行的,如果替換之后顯示的還是之前的頁(yè)面,或者顯示的索引不對(duì),大概率是 ViewPager 之前緩存的 Fragment 沒(méi)有清掉的。
所以我們需要自定義一個(gè) Adapter , 在里面定義清除緩存的方法,每次設(shè)置 Adapter 之前就調(diào)用清除緩存之后再設(shè)置 Adapter 。
直接上代碼:
/**
* 可以清除緩存的ViewPager
*/
public class ViewPagerClearAdapter extends FragmentPagerAdapter {
private List<Fragment> mFragments;
private FragmentTransaction mCurTransaction;
private FragmentManager mFragmentManger;
public ViewPagerClearAdapter(FragmentManager fragmentManager, List<Fragment> fragments) {
this(fragmentManager, fragments, 0);
}
public ViewPagerClearAdapter(FragmentManager fragmentManager, List<Fragment> fragments, int behavor) {
super(fragmentManager, behavor);
mFragments = fragments;
mFragmentManger = fragmentManager;
}
@Override
public Fragment getItem(int position) {
return mFragments.get(position);
}
@Override
public int getCount() {
return mFragments.size() == 0 ? 0 : mFragments.size();
}
/**
* 清除緩存fragment
*
* @param container ViewPager
*/
public void clear(ViewGroup container) {
if (this.mCurTransaction == null) {
this.mCurTransaction = mFragmentManger.beginTransaction();
}
for (int i = 0; i < mFragments.size(); i++) {
long itemId = this.getItemId(i);
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManger.findFragmentByTag(name);
if (fragment != null) {//根據(jù)對(duì)應(yīng)的ID,找到fragment,刪除
mCurTransaction.remove(fragment);
}
}
mCurTransaction.commitNowAllowingStateLoss();
}
/**
* 等同于FragmentPagerAdapter的makeFragmentName方法,
*/
private static String makeFragmentName(int viewId, long id) {
return "android:switcher:" + viewId + ":" + id;
}使用的時(shí)候,先清除再設(shè)置即可:
if (mViewPagerAdapter!=null){
mViewPagerAdapter.clear(mViewPager);
}
mViewPager.setAdapter(null);
mViewPagerAdapter = new ViewPagerClearAdapter(getChildFragmentManager(),mFragmentList);
mViewPager.setAdapter(mViewPagerAdapter);
if (mFragmentList.size() > 1) {
mViewPager.setOffscreenPageLimit(mFragmentList.size() - 1);
}這樣也算是間接的實(shí)現(xiàn)了刷新功能,但是有點(diǎn)傻,RecyclerView 感覺(jué)到暴怒,那么有沒(méi)有類(lèi)似 RecyclerView 那樣的智能刷新呢?
二、TabView+ViewPager的差分刷新
前言中我們說(shuō)到 ViewPager 的 notifyDataSetChanged 刷新方法,會(huì)走到 getItemPosition 方法,而內(nèi)部的默認(rèn)實(shí)現(xiàn)是不做刷新。
而重點(diǎn)的 getItemPosition 其實(shí)就是在 notifyDataSetChanged 執(zhí)行的時(shí)候拿到當(dāng)前的 Item 集合做的遍歷操作,讓每一個(gè) Item 都去自行判斷你有沒(méi)有變化。
那么難點(diǎn)就是如何判斷當(dāng)前的對(duì)象或索引位置有沒(méi)有變化呢?
2.1 使用arguments的方式
在 ViewPagerAdapter 中我們可以重寫(xiě)方法 instantiateItem 表示每次創(chuàng)建 Fragment 的時(shí)候執(zhí)行,創(chuàng)建一個(gè) Fragment 對(duì)象。
由于內(nèi)部默認(rèn)實(shí)現(xiàn)并沒(méi)有添加 Tag ,所以我們可以通過(guò)調(diào)用 super的方式拿到 fragment 對(duì)象,給他設(shè)置一個(gè)參數(shù),并記錄每一個(gè) Fragment 對(duì)應(yīng)的索引位置。
然后我們?cè)倥袛?Fragment 是否需要刷新的時(shí)候,拿到對(duì)應(yīng)的參數(shù),并獲取當(dāng)前 Fragment 的索引,判斷Fragment有沒(méi)有變化,索引有沒(méi)有變化。
當(dāng)都沒(méi)有變化,說(shuō)明此 Fragment 無(wú)需刷新,就返回 POSITION_UNCHANGED ,如果要刷新就返回 POSITION_NONE 。
如果返回 POSITION_NONE ,就會(huì)走到 destroyItem 的回調(diào),會(huì)銷(xiāo)毀 Framgent,如果有需要會(huì)重新創(chuàng)建新的 Fragment 。
完整的代碼實(shí)現(xiàn)如下:
class ViewPagerFragmentAdapter @JvmOverloads constructor(
private val fragmentManager: FragmentManager,
private val fragments: List<Fragment>,
private val pageTitles: List<String>? = null,
behavor: Int = 0
) : FragmentStatePagerAdapter(fragmentManager, behavor) {
private val fragmentMap = mutableMapOf<Int, Fragment>()
private val fragmentPositions = hashMapOf<Int, Int>()
init {
for ((index, fragment) in fragments.withIndex()) {
fragmentMap[index] = fragment
}
}
override fun getItem(position: Int): Fragment {
return fragments[position]
}
override fun getCount(): Int {
return if (fragments.isEmpty()) 0 else fragments.size
}
override fun getPageTitle(position: Int): CharSequence? {
return if (pageTitles == null) "" else pageTitles[position]
}
override fun instantiateItem(container: ViewGroup, position: Int): Any {
YYLogUtils.w("ViewPagerFragmentAdapter-instantiateItem")
val fragment = super.instantiateItem(container, position) as Fragment
val id = generateUniqueId()
var args = fragment.arguments
if (args == null) {
args = Bundle()
}
args.putInt("_uuid", id)
fragment.arguments = args
// 存儲(chǔ) Fragment 的位置信息
fragmentPositions[id] = position
return fragment
}
private fun generateUniqueId(): Int {
// 生成唯一的 ID
return UUID.randomUUID().hashCode()
}
override fun destroyItem(container: ViewGroup, position: Int, obj: Any) {
super.destroyItem(container, position, obj)
}
override fun getItemPosition(obj: Any): Int {
YYLogUtils.w("ViewPagerFragmentAdapter-getItemPosition")
val fragment = obj as Fragment
// 從 Fragment 中獲取唯一的 ID
val args = fragment.arguments
if (args != null && args.containsKey("_uuid")) {
val id = args.getInt("_uuid")
// 根據(jù) ID 獲取 Fragment 在 Adapter 中的位置
val position = fragmentPositions[id]
return if (position != null && position == fragments.indexOf(fragment)) {
// Fragment 未發(fā)生變化,返回 POSITION_UNCHANGED
POSITION_UNCHANGED
} else {
// Fragment 發(fā)生變化,返回 POSITION_NONE
POSITION_NONE
}
}
// 如果不是 Fragment,則返回默認(rèn)值
return super.getItemPosition(obj)
}
}使用起來(lái)很簡(jiǎn)單,我們這里使用默認(rèn)的TabView + ViewPager + 懶加載Fragment來(lái)看看效果:
val fragments = mutableListOf(LazyLoad1Fragment.obtainFragment(), LazyLoad2Fragment.obtainFragment(), LazyLoad3Fragment.obtainFragment());
val titles = mutableListOf("Demo1", "Demo2", "Demo3");
val adapter = ViewPagerFragmentAdapter(supportFragmentManager, fragments, titles)
override fun init() {
//默認(rèn)的添加數(shù)據(jù)適配器
mBinding.viewPager.adapter = adapter
mBinding.viewPager.offscreenPageLimit = fragments.size - 1
mBinding.tabLayout.setupWithViewPager(mBinding.viewPager)
}我們這里使用的是原始懶加載的方案,關(guān)于每一種懶加載Fragment的使用可以看我之前的文章:Fragment懶加載的幾種方式與性能對(duì)比。
我們?cè)贅?biāo)題欄加一個(gè)測(cè)試的按鈕,查看增刪改的功能是否能行?
mBinding.easyTitle.addRightText("刷新") {
//添加并刷新
// fragments.add(LazyLoad1Fragment.obtainFragment())
// titles.add("Demo4")
//更新指定位置并刷新
// fragments[2] = LazyLoad2Fragment.obtainFragment()
// titles[2] = "Refresh1"
//反轉(zhuǎn)換位置呢
// fragments.reverse()
// titles.reverse()
//刪除并刷新
fragments.removeAt(2)
titles.removeAt(2)
mBinding.viewPager.adapter?.notifyDataSetChanged()
mBinding.viewPager.offscreenPageLimit = fragments.size - 1
}添加的效果:

指定位置替換Fragment效果:

反轉(zhuǎn)集合,應(yīng)該是第一個(gè)和第三個(gè)Fragment需要重載:

刪除指定的數(shù)據(jù):

2.2 使用Tag的方式
而使用 Tag 的方式替換其實(shí)是類(lèi)似的道理,需要在創(chuàng)建 Fragment 的時(shí)候綁定 tag ,在查詢是否需要刷新的方法中需要拿到tag進(jìn)行判斷:
Fragment fragment = getItem(position);
FragmentTransaction ft = ((FragmentActivity) mContext).getSupportFragmentManager().beginTransaction();
ft.add(R.id.viewpager, fragment, "fragment" + position);
ft.attach(fragment);
ft.commit(); @Override
public int getItemPosition(@NonNull Object object) {
if (object instanceof Fragment) {
Fragment fragment = (Fragment) object;
Integer position = fragmentMap.get(fragment.getTag());
if (position != null && position == fragments.indexOf(fragment)) {
// Fragment 未發(fā)生變化,返回 POSITION_UNCHANGED
return POSITION_UNCHANGED;
} else {
// Fragment 發(fā)生變化,返回 POSITION_NONE
return POSITION_NONE;
}
}
// 如果不是 Fragment,則返回默認(rèn)值
return super.getItemPosition(object);
}這里就不做過(guò)多的介紹,如果是簡(jiǎn)單的操作也是是可行的。只是需要重寫(xiě)創(chuàng)建Fragment流程。
由于我自用的并不是 Tag 的方式,因?yàn)椴⒉幌胄薷膬?nèi)部的創(chuàng)建 Fragment 方式,畢竟內(nèi)部還涉及到 SavedState 與 BEHAVIOR 的一些處理。
如果你感興趣可以自行實(shí)現(xiàn)!
三、自定義Tab或第三方Tab
如果我們用到一些自定義Tab的樣式,或者使用一些第三方的TabLayout,那么我們?cè)撊绾巫觯?/p>
CustomTabView 還能綁定到 ViewPager 嗎?如果要做刷新又該如何操作?
例如我們使用自定義的Tab樣式:
override fun init() {
titles.forEach {
addTab(it)
}
mBinding.viewPager.adapter = adapter
mBinding.viewPager.offscreenPageLimit = fragments.size - 1
//自定義Tab不能這么設(shè)置了?
mBinding.tabLayout.setupWithViewPager(mBinding.viewPager)
}
private fun addTab(content: String) {
val tab: TabLayout.Tab = mBinding.tabLayout.newTab()
val view: View = layoutInflater.inflate(R.layout.tab_custom_layout, null)
tab.customView = view
val textView = view.findViewById<TextView>(R.id.tab_text)
textView.text = content
mBinding.tabLayout.addTab(tab)
}是可以運(yùn)行,但是不能用 setupWithViewPager 方式,如果用這種方式會(huì)默認(rèn)給設(shè)置原生默認(rèn)的 TabView 。而沒(méi)有自定義 View 效果。

所以我們一般都是手動(dòng)的監(jiān)聽(tīng)實(shí)現(xiàn)效果:
//自定義Tab不能這么設(shè)置了?
// mBinding.tabLayout.setupWithViewPager(mBinding.viewPager)
// 需要手動(dòng)的寫(xiě)監(jiān)聽(tīng)綁定
mBinding.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrolled(i: Int, v: Float, i1: Int) {}
override fun onPageSelected(i: Int) {
mBinding.tabLayout.setScrollPosition(i, 0f, true)
}
override fun onPageScrollStateChanged(i: Int) {}
})
mBinding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
val position = tab.position
mBinding.viewPager.setCurrentItem(position, true)
}
override fun onTabUnselected(tab: TabLayout.Tab) {}
override fun onTabReselected(tab: TabLayout.Tab) {}
})效果:

那么增刪改的操作又有什么區(qū)別呢?
mBinding.easyTitle.addRightText("Refresh") {
//添加并刷新
fragments.add(LazyLoad1Fragment.obtainFragment())
titles.add("Demo4")
addTab("Demo4")
//刪除并刷新
fragments.removeAt(2)
titles.removeAt(2)
mBinding.tabLayout.removeTabAt(2)
mBinding.viewPager.adapter?.notifyDataSetChanged()
mBinding.viewPager.offscreenPageLimit = fragments.size - 1
}由于沒(méi)有 setupWithViewPager 的方式綁定,所以當(dāng)ViewPager變化之后我們需要手動(dòng)的自己處理Tab相關(guān)的賦值與刪除等操作:
否則會(huì)出現(xiàn),ViewPager刷新了,但TabView不會(huì)刷新的問(wèn)題:

自行處理Tab之后的效果:

不管是第三方的TabLayout,還是自定義的TabView,相比原生默認(rèn)的 TabView 使用操作還是要復(fù)雜上一點(diǎn)。
四、ViewPager2的區(qū)別
而TabView + ViewPager2 + 懶加載Fragment 就更簡(jiǎn)單啦,都是基于RV實(shí)現(xiàn)的,我們可以直接調(diào)用RV的刷新方法。
override fun init() {
mBinding.viewPager2.bindFragment(
supportFragmentManager,
this.lifecycle,
fragments,
)
TabLayoutMediator(mBinding.tabLayout, mBinding.viewPager2) { tab, position ->
//回調(diào)
tab.text = titles[position]
}.attach()
}內(nèi)部數(shù)據(jù)適配器的Adapter:
/**
* 給ViewPager2綁定Fragment
*/
fun ViewPager2.bindFragment(
fm: FragmentManager,
lifecycle: Lifecycle,
fragments: List<Fragment>
): ViewPager2 {
offscreenPageLimit = fragments.size - 1
adapter = object : FragmentStateAdapter(fm, lifecycle) {
override fun getItemCount(): Int = fragments.size
override fun createFragment(position: Int): Fragment = fragments[position]
}
return this
}后面我們給它加上一些操作方法:
mBinding.easyTitle.addRightText("Refresh2") {
//添加并刷新
// titles.add("Demo4")
// fragments.add(Lazy2Fragment1.obtainFragment())
// mBinding.viewPager2.adapter?.notifyItemInserted(fragments.size-1)
//更新指定位置并刷新
// fragments[1] = Lazy2Fragment1.obtainFragment()
// titles[1] = "Refresh2"
// mBinding.viewPager2.adapter?.notifyItemChanged(1)
//刪除并刷新
fragments.removeAt(2)
mBinding.viewPager2.adapter?.notifyItemRemoved(2)
mBinding.viewPager2.adapter?.notifyItemRangeChanged(2, 1)
}可以看到我們是直接使用RV的Apdater來(lái)操作的,也就不需要魔改一些 Adapter 之類(lèi)的代碼。
可以看到一些效果如下:


真是簡(jiǎn)單又方便!
話是這么說(shuō),但是感覺(jué) ViewPager2 的風(fēng)評(píng)并不是很好的樣子,很多伙伴反饋有一些小問(wèn)題??偸遣仍诳由?,不知道大家都是怎么選擇的呢?
總結(jié)
本文中我們可以回顧一下ViewPager的用法,F(xiàn)ragment的懶加載用法,重要的是可變 ViewPager 的情況下如何操作。
那么在實(shí)際開(kāi)發(fā)的過(guò)程中,我們其實(shí)可以區(qū)分場(chǎng)景,如果是靜態(tài)的ViewPager,數(shù)量不可變的,可以直接用簡(jiǎn)單的數(shù)據(jù)適配器來(lái)實(shí)現(xiàn),而如果是可變的ViewPager,大家可以區(qū)分三種情況來(lái)使用,都是可行的。
我個(gè)人來(lái)說(shuō)之前都是用清除緩存的方式,后來(lái)用的是修改 Fragment 的 argument 的方式做的。
如果大家有類(lèi)似的使用場(chǎng)景,其實(shí)按需選擇即可,也不知道大家平時(shí)都是怎么做的,如果有更好的方案可以交流一下哦。
到此這篇關(guān)于Android ViewPager你可能不知道的刷新操作分享的文章就介紹到這了,更多相關(guān)Android ViewPager刷新內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解android項(xiàng)目由Gradle 2.2 切換到 3.0的坑
本篇文章主要介紹了詳解android項(xiàng)目由Gradle 2.2 切換到 3.0的坑,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-02-02
創(chuàng)建Android庫(kù)的方法及Android .aar文件用法小結(jié)
本文給大家介紹了創(chuàng)建Android庫(kù)的方法及Android中 .aar文件生成方法與用法詳解,涉及到創(chuàng)建庫(kù)模塊操作步驟及開(kāi)發(fā)注意事項(xiàng),需要的朋友參考下吧2017-12-12
Android 簡(jiǎn)單封裝獲取驗(yàn)證碼倒計(jì)時(shí)功能
倒計(jì)時(shí)效果相信大家都不陌生,我們可以使用很多種方法去實(shí)現(xiàn)此效果,這里自己采用 CountDownTimer 定時(shí)器簡(jiǎn)單封裝下此效果,方便我們隨時(shí)調(diào)用。下面小編給大家分享android驗(yàn)證碼倒計(jì)時(shí)封裝方法,感興趣的朋友一起看看吧2018-01-01
Android鬧鐘機(jī)制實(shí)現(xiàn)定時(shí)任務(wù)功能
這篇文章主要為大家詳細(xì)介紹了Android鬧鐘機(jī)制實(shí)現(xiàn)定時(shí)任務(wù)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01
Android實(shí)現(xiàn)兩個(gè)數(shù)相加功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)兩個(gè)數(shù)相加功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-03-03
Android實(shí)現(xiàn)將View保存成Bitmap的方法
這篇文章主要介紹了Android實(shí)現(xiàn)將View保存成Bitmap的方法,涉及Android畫(huà)布Canvas、位圖bitmap及View的相關(guān)使用技巧,需要的朋友可以參考下2016-06-06
Mac 下 Android Studio 不打印日志的解決辦法
這篇文章主要介紹了Mac 下 Android Studio 不打印日志的解決辦法的相關(guān)資料,希望通過(guò)本文能幫助到大家,需要的朋友可以參考下2017-10-10
Android實(shí)現(xiàn)兩圓點(diǎn)之間來(lái)回移動(dòng)加載進(jìn)度
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)兩圓點(diǎn)之間來(lái)回移動(dòng)加載進(jìn)度,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-06-06

