Fragment as a Recyclerview item

Cart00nHero8
9 min readJan 1, 2021

Using Android Kotlin

Foreword:For the new requirements of the former company’s project, the past pages need to be displayed in the Recyclerview, but they are all Fragments. Recyclerview originally did not support this approach. What we can do?

Method1: FragmentContainer

Step1 Set up XML:

<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragmentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

Step2 Establish the corresponding Class of XML:

class FragmentContainerLayout @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {

lateinit var attachedActivity: AppCompatActivity
lateinit val containerId: Int
lateinit val fragment: Fragment

init {
inflate(context, R.layout.layout_fragment_container, this)
}

open fun initializeLayout() {
val oldFragment = attachedActivity.findFragment(containerId)
if (oldFragment != null) {
attachedActivity.removeFragment(oldFragment)
}
this.fragmentContainer.id = containerId
attachedActivity.replaceFragment(fragment,containerId)
}
}

In Recyclerview, each container must have its own unique id, otherwise it will continue to replace the same container, and remember its order, if the id changed will let app crashed. I use View.generateViewId() to generate the Id and use the original data source array to store

FragmentContainerData(
fragment = SignFragment(),
containerId = View.generateViewId()
)

Step3 Make item layout

<com.cartoonhero.source.stage.scenery.templates.FragmentContainerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

</com.cartoonhero.source.stage.scenery.templates.FragmentContainerLayout>

And use it in RecyclerView adapter

val itemView = LayoutInflater.from(parent.context).inflate(
R.layout.view_fragment_container, parent, false)

Method2: ViewPager2 with single page

In fact, the above method ViewPager2 is actually done, and it’s convenience to expand pages by using ViewPager2, but it is not recommended to do this because you will face the complex life cycle and recycling problems of nested Fragments

Same with Method1 Steps:

<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">

<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="parent"
android:orientation="horizontal" />
</androidx.constraintlayout.widget.ConstraintLayout>

But this time we want to put in the parameters related to ViewPager2

var attachedActivity: AppCompatActivity? = null
val vpFragments = mutableListOf<Fragment>()
val fragmentIds = mutableListOf<Long>() // record unique id
private val createdIds = hashSetOf<Long>() // Record order,Use the hashSet feature to determine whether it is duplicate

Generate ids

private fun generateFragmentIds() {
createdIds.clear()
fragmentIds.clear()
if (vpFragments.isNotEmpty() == true) {
for (fragment in vpFragments!!) {
val newId = View.generateViewId()
fragmentIds.add(newId.toLong())
}
}
}

ViewPager2 will remove Fragment with repeated id, so we won’t do it by ourselves to avoid Fragment already added crash

Then build FragmentStateAdapter

private inner class VP2FragmentAdapter(activity: AppCompatActivity): FragmentStateAdapter(activity) {
override fun getItemCount(): Int {
return vpFragments.size
}

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

override fun getItemId(position: Int): Long {
return fragmentIds[position]
}
override fun createFragment(position: Int): Fragment {
createdIds.add(fragmentIds[position])
return vpFragments[position]
}
}

Same with Method1 Step3

Result:

--

--