ViewPager2 with differing item heights and WRAP_CONTENT

Just do this for the desired Fragment in ViewPager2:

override fun onResume() {
     super.onResume()
     layoutTaskMenu.requestLayout()
}

Jetpack: binding.root.requestLayout() (thanks @syed-zeeshan for the specifics)


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.