【BUG收集】ViewPager2 + Fragment 进行动态删除时,总会删除最后一个的问题

1,407 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第9天 点击查看活动详情

【日期】: 2022/04/23

【问题】:ViewPager2 动态删除Fragment时,不论删除的是哪一个下标,总会删除最后一个

【如何发现】: 出现了问题,我们先去看源码:FragmentStateAdapter。java


/**
 * 默认实现适用于不添加、移动或删除项的集合。
 * <p>
 * TODO(b/122670460): 重写这个方法的时候,要同时重写containsItem(long)
 * <p>
 * @param position Adapter position
 * @return stable item id {@link RecyclerView.Adapter#hasStableIds()}
 */
@Override
public long getItemId(int position) {
    return position;
}

/**
 * 默认实现适用于不添加、移动或删除项的集合。
 * <p>
 * TODO(b/122670460): 重写这个方法的时候,要同时重写getItemId(int)
 */
public boolean containsItem(long itemId) {
    return itemId >= 0 && itemId < getItemCount();
}

我们看到有这么两个方法,方法介绍这两个方法的默认实现只适用于不添加、移动或删除项的集合,但是我们需要进行添加或删除操作,那我们就需要重写这两个方法。

【如何修复】: 我们需要新建一个集成FragmentStateAdapter的类,并实现上面提到的两个方法: 先上完整代码:

class ViewPagerAdapter(
    fm: FragmentManager,
    lifecycle: Lifecycle,
    private val fragments: MutableList<Fragment>
) : FragmentStateAdapter(fm, lifecycle) {

    private val fragmentHashCodes = arrayListOf<Long>()

    init {
        for (fragment in fragments) {
            fragmentHashCodes.add(fragment.hashCode().toLong())
        }
    }

    override fun getItemCount(): Int {
        return fragments.size
    }

    override fun createFragment(position: Int): Fragment {
        return fragments[position]
    }

    fun removeFragment(position: Int){
        fragments.removeAt(position)
        fragmentHashCodes.removeAt(position)
        notifyDataSetChanged()
    }

    fun addFragment(fragment: Fragment){
        fragments.add(fragment)
        fragmentHashCodes.add(fragment.hashCode().toLong())
        notifyDataSetChanged()
    }

    override fun getItemId(position: Int): Long {
        return fragments[position].hashCode().toLong()
    }

    override fun containsItem(itemId: Long): Boolean {
        return fragmentHashCodes.contains(itemId)
    }
}

我们定义两个数据源fragments: MutableList<Fragment>fragmentHashCodes: ArrayList<Long>

fragments: 用来存储上一个界面传递来的Fragment集合
mFragmentHashCodes: 用来存储每个Fragment对应的hashCode值,要注意,进行增删的时候,要和fragments保持一致

在Adapter创建的时候,我们初始化一个Long类型的空集合,并把fragments对应的hashCode值添加到fragmentHashCodes中。

private val fragmentHashCodes = arrayListOf<Long>()

init {
    for (fragment in fragments) {
        fragmentHashCodes.add(fragment.hashCode().toLong())
    }
}

接下来我们需要添加一个添加方法:

fun addFragment(fragment: Fragment){
    fragments.add(fragment)
    fragmentHashCodes.add(fragment.hashCode().toLong())
    notifyDataSetChanged()
}

添加Fragment的同时,也要把这个Fragment对应的hashCode添加到fragmentHashCodes中。

下面我们在添加一个删除方法,和添加方法同理:

fun removeFragment(position: Int){
    fragments.removeAt(position)
    fragmentHashCodes.removeAt(position)
    notifyDataSetChanged()
}

然后我们在重写我们上面提到的两个重要的方法,也是我们解决这个问题的核心点:

override fun getItemId(position: Int): Long {
    return fragments[position].hashCode().toLong()
}

override fun containsItem(itemId: Long): Boolean {
    return fragmentHashCodes.contains(itemId)
}

最后我们在界面上调用:

val adapter = ViewPagerAdapter(supportFragmentManager, lifecycle, fragments)
binding.viewPager.adapter = adapter
binding.viewPager.isUserInputEnabled = false
binding.viewPager.offscreenPageLimit = fragments.size

binding.ivAdd.click {
    adapter.addFragment(...)
    binding.viewPager.setCurrentItem(fragments.size - 1, false)
    binding.viewPager.offscreenPageLimit = fragments.size
}

问题解决!!!

【总结】

遇到问题不可怕,当我们一时找不到解决方案的时候,我们可以先去看看官方源码,这些问题就迎刃而解了。

最后

感谢你看到最后, 最后再说两点~
①如果你持有不同的看法,欢迎你在文章下方进行留言、评论。
②如果对你有帮助,或者你认可的话,欢迎给个小点赞,支持一下~
我是沐小枫,一个热爱学习、热爱编程的山东人

(文章内容仅供学习参考, 如有侵权,非常抱歉,请立即联系作者删除。)