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:
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>