Как добавить/удалить вкладки в ViewPager2 во время выполнения? Использование только одного фрагмента

В приложении, в котором я тестирую эту концепцию, приложение начинается с одной вкладки, и пользователь может щелкнуть кнопку внутри фрагмента и добавить новую вкладку с именем элемента в предварительно определенный список названий фильмов. Представьте, что в списке 100 заголовков, как я могу повторно использовать один и тот же фрагмент для каждой созданной вкладки, чтобы мне не нужно было создавать 100 фрагментов (по одному для каждого заголовка)?

Эта ссылка показывает, как добавлять/удалять вкладки, используя несколько фрагментов и образец приложения.

class DynamicViewPagerAdapter(
    fragmentActivity: FragmentActivity,
    private val titleId: Int
) : FragmentStateAdapter(fragmentActivity) {

override fun createFragment(position: Int): Fragment {
    return DynamicFragment.getInstance(titleId)
}

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

override fun getItemId(position: Int): Long {
    
    return position.toLong()
}

override fun containsItem(itemId: Long): Boolean {
    return titles.contains(titles[itemId.toInt()])
}

fun addTab(title: String) {
    titles.add(title)
    notifyDataSetChanged()
}

fun addTab(index: Int, title: String) {
    titles.add(index, title)
    notifyDataSetChanged()
}

fun removeTab(name: String) {
    titles.remove(name)
    notifyDataSetChanged()
}

fun removeTab(index: Int) {
    titles.removeAt(index)
    notifyDataSetChanged()
}

}


person extremeoats    schedule 14.06.2021    source источник


Ответы (2)


Вы можете добиться этого, используя один фрагмент, которому вы передаете идентификатор элемента. Затем фрагменту потребуется извлечь элемент из списка и отобразить содержимое этого элемента. Вот пример адаптера пейджера.

private inner class SamplePagerAdapter(activity: AppCompatActivity, private val itemId: Int): FragmentStateAdapter(activity) {
            override fun getItemCount(): Int {
                return 100
            }
    
            override fun createFragment(position: Int): Fragment {
                return ItemFragment.newInstance(itemId)
            }
        }
person Val Okafor    schedule 16.06.2021
comment
Похоже, это помогло бы мне выбрать, что отображать внутри фрагментов, соответствующих их вкладкам. Моя основная проблема заключается в добавлении вкладок с использованием одного и того же фрагмента, поэтому, вероятно, переопределение «getItemId ()» и «containsItem ()». Я обновлю свой пост с помощью моего текущего адаптера, и вот адаптер, который использует несколько фрагментов (обратите внимание на класс enum): github.com/abhishekBansal/ViewPager2Demo/blob/master/app/src/ - person extremeoats; 18.06.2021
comment
Обновление: в итоге я использовал глобальную переменную вместо параметра «itemId», потому что я застрял, и это всего лишь тестовое приложение. В моем реальном приложении я заполняю recyclerview данными из репозитория SQLite. - person extremeoats; 19.06.2021

Я понял это, очень важной частью было сохранение порядковых номеров. В процессе удаления вкладок адаптер делает несколько вызовов getItemId() и containsItem(). В этом процессе адаптер использует позицию вкладки и порядковый номер элемента на вкладке. В примере я обнаружил, что используются разные фрагменты и предопределенный их количество, они используют enum для получения порядковых номеров, а я использовал MutableMap (заголовки и ключи; порядковые номера в качестве значений). Вот ссылка на готовое тестовое приложение.

Я использовал эти глобальные переменные (только в качестве теста):

// pre-defined list of titles
val testMovieTitles = listOf(
    "Hulk", "Chucky", "Cruella", "Nobody", "Scar Face",
    "Avatar", "Joker", "Profile", "Saw", "Ladies")

val titles = mutableListOf("All Movies")
val titlesOrdinals: MutableMap<String, Int> = mutableMapOf("All Movies" to 0)

const val MY_LOG = "MY_LOG"

Активность:

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import com.example.testmyviewpager2.*
import com.example.testmyviewpager2.databinding.ActivityDynamicViewPagerBinding
import com.google.android.material.tabs.TabLayoutMediator

class DynamicViewPagerActivity : AppCompatActivity() {

private var binding: ActivityDynamicViewPagerBinding? = null
var titleIncrementer = 0 // to use the next tile until it doesn't match one of the tabs

val activityViewPagerAdapter: DynamicViewPagerAdapter by lazy {
    DynamicViewPagerAdapter(this)}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = ActivityDynamicViewPagerBinding.inflate(layoutInflater)
    setContentView(binding!!.root)

    setUpTabs()
    addTabFabOnClick()
}

private fun setUpTabs() {
    binding!!.dynamicViewPager.offscreenPageLimit = 4
    binding!!.dynamicViewPager.adapter = activityViewPagerAdapter

    // Set the title of the tabs
    TabLayoutMediator(binding!!.dynamicTabLayout, binding!!.dynamicViewPager) { tab, position ->
        tab.text = titles[position]
    }.attach()
}

private fun addTabFabOnClick() {
    binding!!.addTabFab.setOnClickListener {
        val nextTitlePosition = titles.size - 1
        val nextOrdinalId = titlesOrdinals.size - 1
        var nextTitle = testMovieTitles[nextTitlePosition]

        // if a title has been added before, don't add it
        // new tabs cannot have the same name as old tabs
        while(titles.contains(nextTitle)) {
            titleIncrementer++
            nextTitle = testMovieTitles[nextTitlePosition + titleIncrementer]
        }
        if (titleIncrementer > 0) { Log.d("${MY_LOG}Activity", "incrementer: $titleIncrementer") }

        if(!titles.contains(nextTitle)) {
            activityViewPagerAdapter.addTab(nextOrdinalId+1, nextTitle)
        } else {
            Log.d("${MY_LOG}Activity", "\t\t titles contains next title \t\t $titles $nextTitle")}
    }
}
}

Повторно используемый фрагмент:

import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.example.testmyviewpager2.*
import kotlinx.android.synthetic.main.fragment_dynamic.*

class DynamicFragment : Fragment() {

private var fragmentViewPagerAdapter: DynamicViewPagerAdapter? = null
private var titleToDisplay = "All Movies"

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    // Inflate the layout for this fragment
    return inflater.inflate(R.layout.fragment_dynamic, container, false)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    // get the adapter instance from the main activity
    fragmentViewPagerAdapter = (activity as? DynamicViewPagerActivity)!!.activityViewPagerAdapter
    removeButtonOnClick()
    dynamic_fragment_text.text = titleToDisplay
    Log.d("${MY_LOG}fragCreated", "name: ${titleToDisplay}")
    super.onViewCreated(view, savedInstanceState)
}

override fun onDestroy() {
    Log.d("${MY_LOG}destroyed", "\t\t\t $titles")
    Log.d("${MY_LOG}destroyed", "\t\t\t $titlesOrdinals")
    super.onDestroy()
}

private fun removeButtonOnClick() {
    removeButton.setOnClickListener {

        val numOfTabs = titles.size
        if (numOfTabs > 1 && titleToDisplay != "All Movies") {
            fragmentViewPagerAdapter!!.removeTab(titleToDisplay)
        }
    }
}

fun setTitleText(title: String) {
    titleToDisplay = title
}

companion object{
    //The Fragment retrieves the Item from the List and display the content of that item.
    fun getInstance(titleId: Int): DynamicFragment {
        val thisDynamicFragment = DynamicFragment()
        val titleToDisplay = titles[titleId]
        thisDynamicFragment.setTitleText(titleToDisplay)
        return thisDynamicFragment
    }
}
}

Адаптер ViewPager2:

import android.util.Log
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.example.testmyviewpager2.*

class DynamicViewPagerAdapter(fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) {

private val theActivity = DynamicViewPagerActivity()

override fun createFragment(position: Int): Fragment {
    // Used this to change the text inside each fragment
    return DynamicFragment.getInstance(titles.size-1)
}

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

override fun getItemId(position: Int): Long {
    return titlesOrdinals[titles[position]]!!.toLong()
}

// called when a tab is removed
override fun containsItem(itemId: Long): Boolean {
    var thisTitle = "No Title"
    titlesOrdinals.forEach{ (k, v) ->
        if(v == itemId.toInt()) {
            thisTitle = k
        }
    }
    return titles.contains(thisTitle)
}

fun addTab(ordinal: Int, title: String) {
    titles.add(title)

    // don't rewrite an ordinal
    if(!titlesOrdinals.containsKey(title)) {
        titlesOrdinals[title] = ordinal
    }
    notifyDataSetChanged()
    Log.d("${MY_LOG}created", "\t\t\t $titles")
    Log.d("${MY_LOG}created", "\t\t\t $titlesOrdinals")
}

fun removeTab(name: String) {
    titles.remove(name)
    notifyDataSetChanged()
    Log.d("${MY_LOG}removeTab", "----------------")
}

fun removeTab(index: Int) {
    titles.removeAt(index)
    notifyDataSetChanged()
}
}
person extremeoats    schedule 24.06.2021