ViewPager2 with differing item heights and WRAP_CONTENT ViewPager2 with differing item heights and WRAP_CONTENT android android

ViewPager2 with differing item heights and WRAP_CONTENT


The solution is to register a PageChangeCallback and adjust the LayoutParams of the ViewPager2 after asking the child to re-measure itself.

pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {    override fun onPageSelected(position: Int) {        super.onPageSelected(position)        val view = // ... get the view        view.post {            val wMeasureSpec = MeasureSpec.makeMeasureSpec(view.width, MeasureSpec.EXACTLY)            val hMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)            view.measure(wMeasureSpec, hMeasureSpec)            if (pager.layoutParams.height != view.measuredHeight) {                // ParentViewGroup is, for example, LinearLayout                // ... or whatever the parent of the ViewPager2 is                pager.layoutParams = (pager.layoutParams as ParentViewGroup.LayoutParams)                    .also { lp -> lp.height = view.measuredHeight }            }        }    }})

Alternatively, if your view's height can change at some point due to e.g. asynchronous data load, then use a global layout listener instead:

pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {    private val listener = ViewTreeObserver.OnGlobalLayoutListener {        val view = // ... get the view        updatePagerHeightForChild(view)    }    override fun onPageSelected(position: Int) {        super.onPageSelected(position)        val view = // ... get the view        // ... IMPORTANT: remove the global layout listener from other views        otherViews.forEach { it.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener) }        view.viewTreeObserver.addOnGlobalLayoutListener(layoutListener)    }    private fun updatePagerHeightForChild(view: View) {        view.post {            val wMeasureSpec = MeasureSpec.makeMeasureSpec(view.width, MeasureSpec.EXACTLY)            val hMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)            view.measure(wMeasureSpec, hMeasureSpec)            if (pager.layoutParams.height != view.measuredHeight) {                // ParentViewGroup is, for example, LinearLayout                // ... or whatever the parent of the ViewPager2 is                pager.layoutParams = (pager.layoutParams as ParentViewGroup.LayoutParams)                    .also { lp -> lp.height = view.measuredHeight }            }        }    }}

See discussion here:

https://issuetracker.google.com/u/0/issues/143095219


In my case, adding adapter.notifyDataSetChanged() in onPageSelected helped.


Stumbled across this case myself however with fragments. Instead of resizing the view as the accepted answer I decided to wrap the view in a ConstraintLayout. This requires you to specify a size of your ViewPager2 and not use wrap_content.

So Instead of changing size of our viewpager it will have to be minimum size of the largest view it handles.

A bit new to Android so don't know if this is a good solution or not, but it does the job for me.

In other words:

<androidx.constraintlayout.widget.ConstraintLayout  xmlns:android="http://schemas.android.com/apk/res/android"  xmlns:app="http://schemas.android.com/apk/res-auto"  xmlns:tools="http://schemas.android.com/tools"  android:layout_width="match_parent"  android:layout_height="match_parent"  >    <!-- Adding transparency above your view due to wrap_content -->    <androidx.constraintlayout.widget.ConstraintLayout      android:layout_width="match_parent"      android:layout_height="wrap_content"      app:layout_constraintBottom_toBottomOf="parent"      app:layout_constraintStart_toStartOf="parent"      app:layout_constraintEnd_toEndOf="parent"      >      <!-- Your view here -->    </androidx.constraintlayout.widget.ConstraintLayout></androidx.constraintlayout.widget.ConstraintLayout>