ViewPager2 не может динамически добавлять фрагмент удаления

Удаление/добавление фрагментов в индексе приводит к неожиданному поведению в Viewpager2. Это было невозможно с ViewPager, но ожидалось, что оно будет работать с Viewpager2. Это приводит к дублированию фрагментов и рассинхронизации TabLayout. Вот демонстрационный проект, который воспроизводит эту проблему. Существует кнопка-переключатель, которая удаляет фрагмент и повторно прикрепляет его к определенному индексу. В этом случае прикрепленный фрагмент должен быть зеленым, но он синий и почему-то есть 2 синих фрагмента.

вот как выглядит мой адаптер

class ViewPager2Adapter(activity: FragmentActivity) : FragmentStateAdapter(activity) {
    val fragmentList: MutableList<FragmentName> = mutableListOf()

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

    override fun createFragment(position: Int): Fragment {
        return when (fragmentList[position]) {
            FragmentName.WHITE -> WhiteFragment()
            FragmentName.RED -> RedFragment()
            FragmentName.GREEN -> GreenFragment()
            FragmentName.BLUE -> BlueFragment()
        }
    }

    fun add(fragment: FragmentName) {
        fragmentList.add(fragment)
        notifyDataSetChanged()
    }

    fun add(index: Int, fragment: FragmentName) {
        fragmentList.add(index, fragment)
        notifyDataSetChanged()
    }

    fun remove(index: Int) {
        fragmentList.removeAt(index)
        notifyDataSetChanged()
    }

    fun remove(name: FragmentName) {
        fragmentList.remove(name)
        notifyDataSetChanged()
    }

    enum class FragmentName {
        WHITE,
        RED,
        GREEN,
        BLUE
    }
}

Я также сообщил об ошибке в Google.


person Abhishek Bansal    schedule 24.04.2020    source источник


Ответы (2)


Оказывается, вам нужно переопределить эти два метода, если вы работаете с mutable коллекциями в ViewPager2.

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

    override fun containsItem(itemId: Long): Boolean {
        val fragment = FragmentName.values()[itemId.toInt()]
        return fragmentList.contains(fragment)
    }

Добавление этих двух в мой текущий адаптер устраняет проблему.

person Abhishek Bansal    schedule 27.04.2020
comment
спаситель! пытался исправить это последние 2 часа - person ScruffyFox; 28.06.2020
comment
Были ли у вас проблемы с фрагментами, которые не прикреплялись при изменении ориентации/конфигурации с этим изменением? - person ScruffyFox; 28.06.2020
comment
@ScruffyFox Я не помню, чтобы тестировал сценарий изменения конфигурации, но это не должно быть проблемой, потому что адаптер воссоздается, и все фрагменты воссоздаются вместе с ним. Обязательно всегда создавайте новые фрагменты в createFragment. - person Abhishek Bansal; 28.06.2020
comment
Когда я удаляю фрагмент из ViewPager, связанная ViewModel имеет значение null (тот же жизненный цикл, что и фрагмент), и он не обновляется должным образом. Как вы справляетесь с этим сценарием? - person Funnycuni; 03.08.2020
comment
Может быть, вместо этого связать ViewModel с родительской активностью? Также: developer.android.com/topic/libraries/architecture/ - person pallgeuer; 04.10.2020
comment
Как это делается в Java? - person user2066728; 01.02.2021
comment
@ user2066728 то же самое. Просто преобразуйте его в соответствующий синтаксис Java. - person Abhishek Bansal; 01.02.2021
comment
@ user2066728 и для тех, кому нужно - я нашел хороший пример Java здесь. - person Taha; 24.03.2021

Это странно с моей стороны

getItemId()
containsItem()

просто даст нежелательное поведение при использовании 3 видов фрагментов.

В конце концов, все, что мне нужно, это простой класс FragmentStateAdapter, подобный этому

class AppFragmentAdapter(private val fragmentList: MutableList<Pair<String, Fragment>>, fragment: Fragment) : FragmentStateAdapter(fragment) {

//    private var pageIds = fragmentList.map { fragmentList.hashCode().toLong() }

    override fun getItemCount(): Int = fragmentList.size

    override fun createFragment(position: Int): Fragment {
        return fragmentList[position].second
    }

//    override fun getItemId(position: Int): Long = pageIds[position] // Make sure notifyDataSetChanged() works

//    override fun containsItem(itemId: Long): Boolean = pageIds.contains(itemId)

    fun getFragmentName(position: Int) = fragmentList[position].first

    fun addFragment(fragment: Pair<String, Fragment>) {
        fragmentList.add(fragment)
        notifyDataSetChanged()
    }

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

}

Жаль, что я ищу немедленный ответ о том, как сделать ViewPager2 динамическим, не пытаясь сначала использовать самый простой подход, который я мог придумать. Многие отвечают здесь на SO, указывая, что getItemId() и containsItem() необходимо переопределить при добавлении или удалении фрагментов на ViewPager2, что доставляет головную боль почти 2 дня. Почувствовал себя преданным.

person Mihae Kheel    schedule 03.04.2021