如何重启ViewPager2中由Activity创建的Fragment?
问题原因
你调用notifyDataSetChanged()、notifyItemChanged()不生效的核心原因有两个:
- 默认
FragmentStateAdapter通过itemId判断页面是否为同一个对象,你没有重写id相关判断逻辑时,系统默认以position作为itemId,只要位置不变就会判定为同一个Fragment,不会触发重建。 - 你当前的Adapter直接持有外部预创建的Fragment列表,违反了
FragmentStateAdapter的设计原则:Fragment实例的创建、生命周期回收应该完全由Adapter和FragmentManager接管,外部自行缓存的Fragment实例很容易和实际挂载的实例不一致,导致刷新逻辑失效。
修复方案
第一步:重写ViewPagerAdapter
不要在构造参数中传入预创建的Fragment列表,改为维护页面标识和Fragment创建逻辑,同时重写id相关方法,新增刷新接口:
class ViewPagerAdapter( fragmentManager: FragmentManager, lifecycle: Lifecycle ) : FragmentStateAdapter(fragmentManager, lifecycle) { // 存储每个页面的唯一ID private val pageIds = mutableListOf<Long>() // 存储每个页面对应的Fragment创建逻辑,不持有现成Fragment实例 private val fragmentBuilders = mutableMapOf<Long, () -> Fragment>() /** * 添加页面 * @param pageId 页面唯一标识 * @param builder Fragment创建逻辑 */ fun addPage(pageId: Long, builder: () -> Fragment) { pageIds.add(pageId) fragmentBuilders[pageId] = builder } override fun getItemCount(): Int = pageIds.size override fun createFragment(position: Int): Fragment { val currentId = pageIds[position] return fragmentBuilders[currentId]?.invoke() ?: throw IllegalArgumentException("对应位置页面不存在") } override fun getItemId(position: Int): Long = pageIds[position] override fun containsItem(itemId: Long): Boolean = pageIds.contains(itemId) /** * 刷新指定位置的Fragment * @param position 要重启的Fragment位置 */ fun refreshFragment(position: Int) { val oldId = pageIds[position] // 生成新ID,让系统判定为全新页面,自动销毁旧实例重建 val newId = System.currentTimeMillis() pageIds[position] = newId fragmentBuilders[newId] = fragmentBuilders[oldId]!! fragmentBuilders.remove(oldId) notifyItemChanged(position) } /** * 刷新所有Fragment */ fun refreshAll() { val newPageIds = mutableListOf<Long>() val newBuilders = mutableMapOf<Long, () -> Fragment>() pageIds.forEachIndexed { index, oldId -> val newId = System.currentTimeMillis() + index newPageIds.add(newId) newBuilders[newId] = fragmentBuilders[oldId]!! } pageIds.clear() pageIds.addAll(newPageIds) fragmentBuilders.clear() fragmentBuilders.putAll(newBuilders) notifyDataSetChanged() } }
第二步:修改MainActivity中的初始化逻辑
移除你自己维护的fragments: MutableList<Fragment>列表,初始化Adapter时通过addPage传入Fragment创建逻辑:
private val tabsArray = arrayOf("Test1", "Test2") // ... fragmentPagerAdapter = ViewPagerAdapter( supportFragmentManager, lifecycle ) // 注册页面,传入创建逻辑即可,不要提前new Fragment实例 fragmentPagerAdapter.addPage(0L) { Test1Fragment() } fragmentPagerAdapter.addPage(1L) { Test2Fragment() } viewPager.adapter = fragmentPagerAdapter TabLayoutMediator(tabs, viewPager) { tab, position -> tab.text = tabsArray[position] }.attach() // ...
第三步:需要重启Fragment时直接调用对应方法
- 重启指定位置的Fragment(比如重启第一个Tab页):
fragmentPagerAdapter.refreshFragment(0) - 重启ViewPager2内所有Fragment:
fragmentPagerAdapter.refreshAll()
注意事项
- 不要在任何业务代码中自行缓存ViewPager2内的Fragment实例,需要获取当前挂载的Fragment时,通过
supportFragmentManager.findFragmentByTag("f${fragmentPagerAdapter.getItemId(position)}")获取即可,避免拿到已经被销毁的失效实例。 - 不要为了方便重写Adapter的
getItemId直接返回位置,否则永远无法触发对应位置的Fragment重建。
内容的提问来源于stack exchange,提问作者Murat Aktay
相关产品推荐
相关产品推荐

