views-widgets-samples icon indicating copy to clipboard operation
views-widgets-samples copied to clipboard

How to wrap height of Android ViewPager2 to height of current item?

Open sindicly opened this issue 4 years ago • 31 comments

The content of each piece of mine is long and short. How can I make viewpager2 fit the height of the subview?

sindicly avatar Sep 09 '20 03:09 sindicly

This is the layout:

<?xml version="1.0" encoding="utf-8"?>

<androidx.core.widget.NestedScrollView 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:id="@+id/nestedScrollView" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/grey_300">

<androidx.constraintlayout.widget.ConstraintLayout 
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    tools:ignore="HardcodedText">

    <com.google.android.material.tabs.TabLayout 
        android:id="@+id/mTablayout"
        android:layout_width="match_parent" 
        android:layout_height="50dp"
        android:layout_gravity="start"
        android:textSize="15sp"
        app:layout_constraintTop_toTopOf="parent" 
        app:tabBackground="@color/white"
        app:tabIndicatorColor="@color/light_blue_400" 
        app:tabIndicatorHeight="50dp"
        app:tabMode="auto" 
        app:tabSelectedTextColor="@color/white"
        app:tabTextColor="@color/black_alpha_176" />

    <View android:id="@+id/divider" 
        android:layout_width="match_parent"
        android:layout_height="1dp" 
        android:background="@color/grey_500"
        app:layout_constraintTop_toBottomOf="@+id/mTablayout" />

    <androidx.viewpager2.widget.ViewPager2 
        android:id="@+id/viewPager2"
        android:layout_width="match_parent" 
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintTop_toBottomOf="@+id/divider" />

</androidx.constraintlayout.widget.ConstraintLayout>

</androidx.core.widget.NestedScrollView> `

The following is the method I used before, but it doesn't work

`mViewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { @Override public void onPageSelected(int position) { super.onPageSelected(position); /* View childView = mViewPager.getChildAt(position); View rootView = mViewPager.getRootView();*/

            Fragment fragment = mAdapter.createFragment(position);
            View childView = fragment.getView();
            if (childView == null) return;

            int wMeasureSpec = View.MeasureSpec.makeMeasureSpec(childView.getWidth(), View.MeasureSpec.EXACTLY);
            int hMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
            childView.measure(wMeasureSpec, hMeasureSpec);
            if (mViewPager.getLayoutParams().height != childView.getMeasuredHeight()) {
                ViewGroup.LayoutParams lp = mViewPager.getLayoutParams();
                lp.height = childView.getMeasuredHeight();
            }
        }
    });`

sindicly avatar Sep 09 '20 03:09 sindicly

Why can't viewpager2 be inherited without openness at all

sindicly avatar Sep 09 '20 04:09 sindicly

dataBinding.viewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { override fun onPageScrolled( position: Int, positionOffset: Float, positionOffsetPixels: Int ) { super.onPageScrolled(position, positionOffset, positionOffsetPixels) if (position > 0 && positionOffset == 0.0f && positionOffsetPixels == 0) { dataBinding.viewPager2.layoutParams.height = dataBinding.viewPager2.getChildAt(0).height } } }). try it

CodeK1988 avatar Oct 28 '20 10:10 CodeK1988

any luck for solving the issue

hadia avatar Jan 02 '21 21:01 hadia

@hadia could you paste the code of solving.

yinxiucheng avatar Jan 12 '21 02:01 yinxiucheng

@sindicly 兄弟,你这个问题解决了没?我也遇到了,用了你上面说的方法,网上也都说的这个方法。但是存在问题。

yinxiucheng avatar Jan 12 '21 02:01 yinxiucheng

I've implemented the viewPager2.registerOnPageChangeCallback and added listener in viewpager2's fragment, after data loaded completed and UI render completed, ask the surveyViewPager to reset height again

It solve the problem

JiaYuZ avatar Jan 21 '21 23:01 JiaYuZ

I've implemented the viewPager2.registerOnPageChangeCallback and added listener in viewpager2's fragment, after data loaded completed and UI render completed, ask the surveyViewPager to reset height again

It solve the problem if u mind, show me some light plexx ...

ChinGyi2019 avatar Feb 02 '21 04:02 ChinGyi2019

this is for wrapping the height of each view



import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2


class ViewPager2ViewHeightAnimator {

    var viewPager2: ViewPager2? = null; set(value) {
        if (field != value) {
            field?.unregisterOnPageChangeCallback(onPageChangeCallback)
            field = value
            value?.registerOnPageChangeCallback(onPageChangeCallback)
        }
    }

    private val layoutManager: LinearLayoutManager? get() = (viewPager2?.getChildAt(0) as? RecyclerView)?.layoutManager as? LinearLayoutManager


    private val onPageChangeCallback = object : ViewPager2.OnPageChangeCallback(){
        override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
            super.onPageScrolled(position, positionOffset, positionOffsetPixels)
            recalculate(position, positionOffset)
        }
    }

    fun recalculate(position: Int, positionOffset: Float = 0f) = layoutManager?.apply {
        val leftView = findViewByPosition(position) ?: return@apply
        val rightView = findViewByPosition(position + 1)
        viewPager2?.apply {
            val leftHeight = getMeasuredViewHeightFor(leftView)
            layoutParams = layoutParams.apply {
                height = if (rightView != null) {
                    val rightHeight = getMeasuredViewHeightFor(rightView)
                    leftHeight + ((rightHeight - leftHeight) * positionOffset).toInt()
                } else {
                    leftHeight
                }
            }
            invalidate()
        }
    }

    private fun getMeasuredViewHeightFor(view: View) : Int {
        val wMeasureSpec = View.MeasureSpec.makeMeasureSpec(view.width, View.MeasureSpec.EXACTLY)
        val hMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
        view.measure(wMeasureSpec, hMeasureSpec)
        return view.measuredHeight
    }
}

hereisderek avatar Mar 04 '21 01:03 hereisderek

thanks dude)

whatiamdoing avatar Jun 11 '21 12:06 whatiamdoing

this is for wrapping the height of each view

import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2


class ViewPager2ViewHeightAnimator {

    var viewPager2: ViewPager2? = null; set(value) {
        if (field != value) {
            field?.unregisterOnPageChangeCallback(onPageChangeCallback)
            field = value
            value?.registerOnPageChangeCallback(onPageChangeCallback)
        }
    }

    private val layoutManager: LinearLayoutManager? get() = (viewPager2?.getChildAt(0) as? RecyclerView)?.layoutManager as? LinearLayoutManager


    private val onPageChangeCallback = object : ViewPager2.OnPageChangeCallback(){
        override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
            super.onPageScrolled(position, positionOffset, positionOffsetPixels)
            recalculate(position, positionOffset)
        }
    }

    fun recalculate(position: Int, positionOffset: Float = 0f) = layoutManager?.apply {
        val leftView = findViewByPosition(position) ?: return@apply
        val rightView = findViewByPosition(position + 1)
        viewPager2?.apply {
            val leftHeight = getMeasuredViewHeightFor(leftView)
            layoutParams = layoutParams.apply {
                height = if (rightView != null) {
                    val rightHeight = getMeasuredViewHeightFor(rightView)
                    leftHeight + ((rightHeight - leftHeight) * positionOffset).toInt()
                } else {
                    leftHeight
                }
            }
            invalidate()
        }
    }

    private fun getMeasuredViewHeightFor(view: View) : Int {
        val wMeasureSpec = View.MeasureSpec.makeMeasureSpec(view.width, View.MeasureSpec.EXACTLY)
        val hMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
        view.measure(wMeasureSpec, hMeasureSpec)
        return view.measuredHeight
    }
}

I do some improvements on it to change the height of viewpager if view height changes on runtime:

class ViewPager2ViewHeightAnimator {

    var viewPager2: ViewPager2? = null; set(value) {
        if (field != value) {
            field?.unregisterOnPageChangeCallback(onPageChangeCallback)
            field = value
            value?.registerOnPageChangeCallback(onPageChangeCallback)
        }
    }

    private val layoutManager: LinearLayoutManager? get() = (viewPager2?.getChildAt(0) as? RecyclerView)?.layoutManager as? LinearLayoutManager

    private val onPageChangeCallback = object : ViewPager2.OnPageChangeCallback() {
        override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
            super.onPageScrolled(position, positionOffset, positionOffsetPixels)
            recalculate(position, positionOffset)
        }
    }

    fun recalculate(position: Int, positionOffset: Float = 0f) = layoutManager?.apply {
        val leftView = findViewByPosition(position) ?: return@apply
        val rightView = findViewByPosition(position + 1)
        val setMeasure = {
            viewPager2?.apply {
                val leftHeight = getMeasuredViewHeightFor(leftView)
                layoutParams = layoutParams.apply {
                    height = if (rightView != null) {
                        val rightHeight = getMeasuredViewHeightFor(rightView)
                        leftHeight + ((rightHeight - leftHeight) * positionOffset).toInt()
                    } else {
                        leftHeight
                    }
                }
                invalidate()
            }
        }
        val onLayoutChanged =
            ViewTreeObserver.OnGlobalLayoutListener {
                setMeasure.invoke()
            }
        leftView.viewTreeObserver.addOnGlobalLayoutListener(onLayoutChanged)
        rightView?.viewTreeObserver?.addOnGlobalLayoutListener(onLayoutChanged)
        setMeasure.invoke()
    }

    private fun getMeasuredViewHeightFor(view: View): Int {
        val wMeasureSpec = View.MeasureSpec.makeMeasureSpec(view.width, View.MeasureSpec.EXACTLY)
        val hMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
        view.measure(wMeasureSpec, hMeasureSpec)
        return view.measuredHeight
    }
}

MohammadRezaei92 avatar Jun 19 '21 07:06 MohammadRezaei92

how do i implement ViewPager2ViewHeightAnimator ?

Ikrimah1998 avatar Jun 21 '21 05:06 Ikrimah1998

this is for wrapping the height of each view

import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2


class ViewPager2ViewHeightAnimator {

    var viewPager2: ViewPager2? = null; set(value) {
        if (field != value) {
            field?.unregisterOnPageChangeCallback(onPageChangeCallback)
            field = value
            value?.registerOnPageChangeCallback(onPageChangeCallback)
        }
    }

    private val layoutManager: LinearLayoutManager? get() = (viewPager2?.getChildAt(0) as? RecyclerView)?.layoutManager as? LinearLayoutManager


    private val onPageChangeCallback = object : ViewPager2.OnPageChangeCallback(){
        override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
            super.onPageScrolled(position, positionOffset, positionOffsetPixels)
            recalculate(position, positionOffset)
        }
    }

    fun recalculate(position: Int, positionOffset: Float = 0f) = layoutManager?.apply {
        val leftView = findViewByPosition(position) ?: return@apply
        val rightView = findViewByPosition(position + 1)
        viewPager2?.apply {
            val leftHeight = getMeasuredViewHeightFor(leftView)
            layoutParams = layoutParams.apply {
                height = if (rightView != null) {
                    val rightHeight = getMeasuredViewHeightFor(rightView)
                    leftHeight + ((rightHeight - leftHeight) * positionOffset).toInt()
                } else {
                    leftHeight
                }
            }
            invalidate()
        }
    }

    private fun getMeasuredViewHeightFor(view: View) : Int {
        val wMeasureSpec = View.MeasureSpec.makeMeasureSpec(view.width, View.MeasureSpec.EXACTLY)
        val hMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
        view.measure(wMeasureSpec, hMeasureSpec)
        return view.measuredHeight
    }
}

I do some improvements on it to change the height of viewpager if view height changes on runtime:

class ViewPager2ViewHeightAnimator {

    var viewPager2: ViewPager2? = null; set(value) {
        if (field != value) {
            field?.unregisterOnPageChangeCallback(onPageChangeCallback)
            field = value
            value?.registerOnPageChangeCallback(onPageChangeCallback)
        }
    }

    private val layoutManager: LinearLayoutManager? get() = (viewPager2?.getChildAt(0) as? RecyclerView)?.layoutManager as? LinearLayoutManager

    private val onPageChangeCallback = object : ViewPager2.OnPageChangeCallback() {
        override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
            super.onPageScrolled(position, positionOffset, positionOffsetPixels)
            recalculate(position, positionOffset)
        }
    }

    fun recalculate(position: Int, positionOffset: Float = 0f) = layoutManager?.apply {
        val leftView = findViewByPosition(position) ?: return@apply
        val rightView = findViewByPosition(position + 1)
        val setMeasure = {
            viewPager2?.apply {
                val leftHeight = getMeasuredViewHeightFor(leftView)
                layoutParams = layoutParams.apply {
                    height = if (rightView != null) {
                        val rightHeight = getMeasuredViewHeightFor(rightView)
                        leftHeight + ((rightHeight - leftHeight) * positionOffset).toInt()
                    } else {
                        leftHeight
                    }
                }
                invalidate()
            }
        }
        val onLayoutChanged =
            ViewTreeObserver.OnGlobalLayoutListener {
                setMeasure.invoke()
            }
        leftView.viewTreeObserver.addOnGlobalLayoutListener(onLayoutChanged)
        rightView?.viewTreeObserver?.addOnGlobalLayoutListener(onLayoutChanged)
        setMeasure.invoke()
    }

    private fun getMeasuredViewHeightFor(view: View): Int {
        val wMeasureSpec = View.MeasureSpec.makeMeasureSpec(view.width, View.MeasureSpec.EXACTLY)
        val hMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
        view.measure(wMeasureSpec, hMeasureSpec)
        return view.measuredHeight
    }
}

How do i Implement this?

Ikrimah1998 avatar Jun 21 '21 05:06 Ikrimah1998

how do i implement ViewPager2ViewHeightAnimator ?

1 Copy this class to your project. 2 Get an instance of it. 3 Pass your viewpager to viewpager2 variable.

MohammadRezaei92 avatar Jun 23 '21 19:06 MohammadRezaei92

Hey what all that I give you guys? To be honest I kind of did it on accident apparently somebody in my house is doing something online and I was trying to figure it out but I really don't know what I'm doing thanks guys

On Fri, Jun 11, 2021, 7:55 AM whatiamdoing @.***> wrote:

thanks dude)

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/android/views-widgets-samples/issues/184#issuecomment-859561668, or unsubscribe https://github.com/notifications/unsubscribe-auth/ATYLZK6TL2GNJ255NBWP4ODTSIBU7ANCNFSM4RA4QZGQ .

jasonRap79 avatar Jun 23 '21 20:06 jasonRap79

Thank you, man I totally lost it there for a sec. Cool thanks

On Wed, Jun 23, 2021, 3:08 PM Jason Rapa @.***> wrote:

Hey what all that I give you guys? To be honest I kind of did it on accident apparently somebody in my house is doing something online and I was trying to figure it out but I really don't know what I'm doing thanks guys

On Fri, Jun 11, 2021, 7:55 AM whatiamdoing @.***> wrote:

thanks dude)

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/android/views-widgets-samples/issues/184#issuecomment-859561668, or unsubscribe https://github.com/notifications/unsubscribe-auth/ATYLZK6TL2GNJ255NBWP4ODTSIBU7ANCNFSM4RA4QZGQ .

jasonRap79 avatar Jun 23 '21 22:06 jasonRap79

how do i implement ViewPager2ViewHeightAnimator ?

1 Copy this class to your project. 2 Get an instance of it. 3 Pass your viewpager to viewpager2 variable.

Get an instance? how

Ikrimah1998 avatar Jun 24 '21 05:06 Ikrimah1998

how do i implement ViewPager2ViewHeightAnimator ?

1 Copy this class to your project. 2 Get an instance of it. 3 Pass your viewpager to viewpager2 variable.

Get an instance? how

Do you know programming at all?

MohammadRezaei92 avatar Jun 24 '21 13:06 MohammadRezaei92

how do i implement ViewPager2ViewHeightAnimator ?

1 Copy this class to your project. 2 Get an instance of it. 3 Pass your viewpager to viewpager2 variable.

Get an instance? how

Do you know programming at all?

Noo, please can i see the code

Ikrimah1998 avatar Jun 24 '21 14:06 Ikrimah1998

not sure where y'all get your instances, I get mine from the god

On Fri, Jun 25, 2021 at 2:58 AM Ikrimah1998 @.***> wrote:

how do i implement ViewPager2ViewHeightAnimator ?

1 Copy this class to your project. 2 Get an instance of it. 3 Pass your viewpager to viewpager2 variable.

Get an instance? how

Do you know programming at all?

Noo, please can i see the code

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/android/views-widgets-samples/issues/184#issuecomment-867704963, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAGNA44MOQIHZ32262H2NCTTUNBZVANCNFSM4RA4QZGQ .

--

yours Derek

hereisderek avatar Jun 24 '21 22:06 hereisderek

how do i implement ViewPager2ViewHeightAnimator ?

1 Copy this class to your project. 2 Get an instance of it. 3 Pass your viewpager to viewpager2 variable.

Get an instance? how

Do you know programming at all?

Noo, please can i see the code

use findViewById() get your ViewPager2 instance

val viewPager2 = findViewById(R.id.viewpager2)
val viewPager2ViewHeightAnimator = ViewPager2ViewHeightAnimator()
viewPager2ViewHeightAnimator.viewPager2 = viewPager2

letsky avatar Jul 07 '21 05:07 letsky

how do i implement ViewPager2ViewHeightAnimator ?

1 Copy this class to your project. 2 Get an instance of it. 3 Pass your viewpager to viewpager2 variable.

Get an instance? how

Do you know programming at all?

Noo, please can i see the code

use findViewById() get your ViewPager2 instance

val viewPager2 = findViewById(R.id.viewpager2)
val viewPager2ViewHeightAnimator = ViewPager2ViewHeightAnimator()
viewPager2ViewHeightAnimator.viewPager2 = viewPager2

how to i Pass your viewpager to viewpager2 variable.?

Ikrimah1998 avatar Jul 07 '21 14:07 Ikrimah1998

this is for wrapping the height of each view

import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2


class ViewPager2ViewHeightAnimator {

    var viewPager2: ViewPager2? = null; set(value) {
        if (field != value) {
            field?.unregisterOnPageChangeCallback(onPageChangeCallback)
            field = value
            value?.registerOnPageChangeCallback(onPageChangeCallback)
        }
    }

    private val layoutManager: LinearLayoutManager? get() = (viewPager2?.getChildAt(0) as? RecyclerView)?.layoutManager as? LinearLayoutManager


    private val onPageChangeCallback = object : ViewPager2.OnPageChangeCallback(){
        override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
            super.onPageScrolled(position, positionOffset, positionOffsetPixels)
            recalculate(position, positionOffset)
        }
    }

    fun recalculate(position: Int, positionOffset: Float = 0f) = layoutManager?.apply {
        val leftView = findViewByPosition(position) ?: return@apply
        val rightView = findViewByPosition(position + 1)
        viewPager2?.apply {
            val leftHeight = getMeasuredViewHeightFor(leftView)
            layoutParams = layoutParams.apply {
                height = if (rightView != null) {
                    val rightHeight = getMeasuredViewHeightFor(rightView)
                    leftHeight + ((rightHeight - leftHeight) * positionOffset).toInt()
                } else {
                    leftHeight
                }
            }
            invalidate()
        }
    }

    private fun getMeasuredViewHeightFor(view: View) : Int {
        val wMeasureSpec = View.MeasureSpec.makeMeasureSpec(view.width, View.MeasureSpec.EXACTLY)
        val hMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
        view.measure(wMeasureSpec, hMeasureSpec)
        return view.measuredHeight
    }
}

I do some improvements on it to change the height of viewpager if view height changes on runtime:

class ViewPager2ViewHeightAnimator {

    var viewPager2: ViewPager2? = null; set(value) {
        if (field != value) {
            field?.unregisterOnPageChangeCallback(onPageChangeCallback)
            field = value
            value?.registerOnPageChangeCallback(onPageChangeCallback)
        }
    }

    private val layoutManager: LinearLayoutManager? get() = (viewPager2?.getChildAt(0) as? RecyclerView)?.layoutManager as? LinearLayoutManager

    private val onPageChangeCallback = object : ViewPager2.OnPageChangeCallback() {
        override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
            super.onPageScrolled(position, positionOffset, positionOffsetPixels)
            recalculate(position, positionOffset)
        }
    }

    fun recalculate(position: Int, positionOffset: Float = 0f) = layoutManager?.apply {
        val leftView = findViewByPosition(position) ?: return@apply
        val rightView = findViewByPosition(position + 1)
        val setMeasure = {
            viewPager2?.apply {
                val leftHeight = getMeasuredViewHeightFor(leftView)
                layoutParams = layoutParams.apply {
                    height = if (rightView != null) {
                        val rightHeight = getMeasuredViewHeightFor(rightView)
                        leftHeight + ((rightHeight - leftHeight) * positionOffset).toInt()
                    } else {
                        leftHeight
                    }
                }
                invalidate()
            }
        }
        val onLayoutChanged =
            ViewTreeObserver.OnGlobalLayoutListener {
                setMeasure.invoke()
            }
        leftView.viewTreeObserver.addOnGlobalLayoutListener(onLayoutChanged)
        rightView?.viewTreeObserver?.addOnGlobalLayoutListener(onLayoutChanged)
        setMeasure.invoke()
    }

    private fun getMeasuredViewHeightFor(view: View): Int {
        val wMeasureSpec = View.MeasureSpec.makeMeasureSpec(view.width, View.MeasureSpec.EXACTLY)
        val hMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
        view.measure(wMeasureSpec, hMeasureSpec)
        return view.measuredHeight
    }
}

I encountered a bug when applying this solution: the first fragment in viewPager2 is always match_parent when just entering the page

And, I solved it by making OnGlobalLayoutListener disposable

  1. Add an extension for convenience
fun ViewTreeObserver.addDisposableOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener) {
    addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            removeOnGlobalLayoutListener(this)
            listener.onGlobalLayout()
        }
    })
}
  1. Replace the following code
val onLayoutChanged =
    ViewTreeObserver.OnGlobalLayoutListener {
        setMeasure.invoke()
    }
leftView.viewTreeObserver.addOnGlobalLayoutListener(onLayoutChanged)
rightView?.viewTreeObserver?.addOnGlobalLayoutListener(onLayoutChanged)

with

leftView.viewTreeObserver.addDisposableOnGlobalLayoutListener { setMeasure.invoke() }
rightView?.viewTreeObserver?.addDisposableOnGlobalLayoutListener { setMeasure.invoke() }

adherencegoo avatar Jul 16 '21 13:07 adherencegoo

how i pas in my view pager, please help me

On Fri, Jul 16, 2021 at 9:16 PM Owen Chen @.***> wrote:

this is for wrapping the height of each view

import android.view.Viewimport androidx.recyclerview.widget.LinearLayoutManagerimport androidx.recyclerview.widget.RecyclerViewimport androidx.viewpager2.widget.ViewPager2

class ViewPager2ViewHeightAnimator {

var viewPager2: ViewPager2? = null; set(value) {
    if (field != value) {
        field?.unregisterOnPageChangeCallback(onPageChangeCallback)
        field = value
        value?.registerOnPageChangeCallback(onPageChangeCallback)
    }
}

private val layoutManager: LinearLayoutManager? get() = (viewPager2?.getChildAt(0) as? RecyclerView)?.layoutManager as? LinearLayoutManager


private val onPageChangeCallback = object : ViewPager2.OnPageChangeCallback(){
    override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
        super.onPageScrolled(position, positionOffset, positionOffsetPixels)
        recalculate(position, positionOffset)
    }
}

fun recalculate(position: Int, positionOffset: Float = 0f) = layoutManager?.apply {
    val leftView = findViewByPosition(position) ?: ***@***.***
    val rightView = findViewByPosition(position + 1)
    viewPager2?.apply {
        val leftHeight = getMeasuredViewHeightFor(leftView)
        layoutParams = layoutParams.apply {
            height = if (rightView != null) {
                val rightHeight = getMeasuredViewHeightFor(rightView)
                leftHeight + ((rightHeight - leftHeight) * positionOffset).toInt()
            } else {
                leftHeight
            }
        }
        invalidate()
    }
}

private fun getMeasuredViewHeightFor(view: View) : Int {
    val wMeasureSpec = View.MeasureSpec.makeMeasureSpec(view.width, View.MeasureSpec.EXACTLY)
    val hMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
    view.measure(wMeasureSpec, hMeasureSpec)
    return view.measuredHeight
}

}

I do some improvements on it to change the height of viewpager if view height changes on runtime:

class ViewPager2ViewHeightAnimator {

var viewPager2: ViewPager2? = null; set(value) {
    if (field != value) {
        field?.unregisterOnPageChangeCallback(onPageChangeCallback)
        field = value
        value?.registerOnPageChangeCallback(onPageChangeCallback)
    }
}

private val layoutManager: LinearLayoutManager? get() = (viewPager2?.getChildAt(0) as? RecyclerView)?.layoutManager as? LinearLayoutManager

private val onPageChangeCallback = object : ViewPager2.OnPageChangeCallback() {
    override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
        super.onPageScrolled(position, positionOffset, positionOffsetPixels)
        recalculate(position, positionOffset)
    }
}

fun recalculate(position: Int, positionOffset: Float = 0f) = layoutManager?.apply {
    val leftView = findViewByPosition(position) ?: ***@***.***
    val rightView = findViewByPosition(position + 1)
    val setMeasure = {
        viewPager2?.apply {
            val leftHeight = getMeasuredViewHeightFor(leftView)
            layoutParams = layoutParams.apply {
                height = if (rightView != null) {
                    val rightHeight = getMeasuredViewHeightFor(rightView)
                    leftHeight + ((rightHeight - leftHeight) * positionOffset).toInt()
                } else {
                    leftHeight
                }
            }
            invalidate()
        }
    }
    val onLayoutChanged =
        ViewTreeObserver.OnGlobalLayoutListener {
            setMeasure.invoke()
        }
    leftView.viewTreeObserver.addOnGlobalLayoutListener(onLayoutChanged)
    rightView?.viewTreeObserver?.addOnGlobalLayoutListener(onLayoutChanged)
    setMeasure.invoke()
}

private fun getMeasuredViewHeightFor(view: View): Int {
    val wMeasureSpec = View.MeasureSpec.makeMeasureSpec(view.width, View.MeasureSpec.EXACTLY)
    val hMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
    view.measure(wMeasureSpec, hMeasureSpec)
    return view.measuredHeight
}

}

I encountered a bug when applying this solution: the first fragment in viewPager2 is always match_parent when just entering the page

And, I solved it by making OnGlobalLayoutListener disposable

  1. Add an extension for convenience

fun ViewTreeObserver.addDisposableOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener) { addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { override fun onGlobalLayout() { removeOnGlobalLayoutListener(this) listener.onGlobalLayout() } }) }

  1. Replace the following code

val onLayoutChanged = ViewTreeObserver.OnGlobalLayoutListener { setMeasure.invoke() } leftView.viewTreeObserver.addOnGlobalLayoutListener(onLayoutChanged) rightView?.viewTreeObserver?.addOnGlobalLayoutListener(onLayoutChanged)

with

leftView.viewTreeObserver.addDisposableOnGlobalLayoutListener { setMeasure.invoke() } rightView?.viewTreeObserver?.addDisposableOnGlobalLayoutListener { setMeasure.invoke() }

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/android/views-widgets-samples/issues/184#issuecomment-881442535, or unsubscribe https://github.com/notifications/unsubscribe-auth/APTH7ZO3D5Y6ZS7LDV6ZFD3TYAWL5ANCNFSM4RA4QZGQ .

Ikrimah1998 avatar Jul 17 '21 13:07 Ikrimah1998

Try this. This is working very well Put this in the fragment which is being used in viewPager

    override fun onResume() {
        super.onResume()
        binding.root.requestLayout()
    }

sdzshn3 avatar Aug 11 '21 13:08 sdzshn3

Thanks guys for help this problem.

MarkWang33 avatar Oct 18 '21 06:10 MarkWang33

this is for wrapping the height of each view

import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2


class ViewPager2ViewHeightAnimator {

    var viewPager2: ViewPager2? = null; set(value) {
        if (field != value) {
            field?.unregisterOnPageChangeCallback(onPageChangeCallback)
            field = value
            value?.registerOnPageChangeCallback(onPageChangeCallback)
        }
    }

    private val layoutManager: LinearLayoutManager? get() = (viewPager2?.getChildAt(0) as? RecyclerView)?.layoutManager as? LinearLayoutManager


    private val onPageChangeCallback = object : ViewPager2.OnPageChangeCallback(){
        override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
            super.onPageScrolled(position, positionOffset, positionOffsetPixels)
            recalculate(position, positionOffset)
        }
    }

    fun recalculate(position: Int, positionOffset: Float = 0f) = layoutManager?.apply {
        val leftView = findViewByPosition(position) ?: return@apply
        val rightView = findViewByPosition(position + 1)
        viewPager2?.apply {
            val leftHeight = getMeasuredViewHeightFor(leftView)
            layoutParams = layoutParams.apply {
                height = if (rightView != null) {
                    val rightHeight = getMeasuredViewHeightFor(rightView)
                    leftHeight + ((rightHeight - leftHeight) * positionOffset).toInt()
                } else {
                    leftHeight
                }
            }
            invalidate()
        }
    }

    private fun getMeasuredViewHeightFor(view: View) : Int {
        val wMeasureSpec = View.MeasureSpec.makeMeasureSpec(view.width, View.MeasureSpec.EXACTLY)
        val hMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
        view.measure(wMeasureSpec, hMeasureSpec)
        return view.measuredHeight
    }
}

I do some improvements on it to change the height of viewpager if view height changes on runtime:

class ViewPager2ViewHeightAnimator {

    var viewPager2: ViewPager2? = null; set(value) {
        if (field != value) {
            field?.unregisterOnPageChangeCallback(onPageChangeCallback)
            field = value
            value?.registerOnPageChangeCallback(onPageChangeCallback)
        }
    }

    private val layoutManager: LinearLayoutManager? get() = (viewPager2?.getChildAt(0) as? RecyclerView)?.layoutManager as? LinearLayoutManager

    private val onPageChangeCallback = object : ViewPager2.OnPageChangeCallback() {
        override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
            super.onPageScrolled(position, positionOffset, positionOffsetPixels)
            recalculate(position, positionOffset)
        }
    }

    fun recalculate(position: Int, positionOffset: Float = 0f) = layoutManager?.apply {
        val leftView = findViewByPosition(position) ?: return@apply
        val rightView = findViewByPosition(position + 1)
        val setMeasure = {
            viewPager2?.apply {
                val leftHeight = getMeasuredViewHeightFor(leftView)
                layoutParams = layoutParams.apply {
                    height = if (rightView != null) {
                        val rightHeight = getMeasuredViewHeightFor(rightView)
                        leftHeight + ((rightHeight - leftHeight) * positionOffset).toInt()
                    } else {
                        leftHeight
                    }
                }
                invalidate()
            }
        }
        val onLayoutChanged =
            ViewTreeObserver.OnGlobalLayoutListener {
                setMeasure.invoke()
            }
        leftView.viewTreeObserver.addOnGlobalLayoutListener(onLayoutChanged)
        rightView?.viewTreeObserver?.addOnGlobalLayoutListener(onLayoutChanged)
        setMeasure.invoke()
    }

    private fun getMeasuredViewHeightFor(view: View): Int {
        val wMeasureSpec = View.MeasureSpec.makeMeasureSpec(view.width, View.MeasureSpec.EXACTLY)
        val hMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
        view.measure(wMeasureSpec, hMeasureSpec)
        return view.measuredHeight
    }
}

I encountered a bug when applying this solution: the first fragment in viewPager2 is always match_parent when just entering the page

And, I solved it by making OnGlobalLayoutListener disposable

  1. Add an extension for convenience
fun ViewTreeObserver.addDisposableOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener) {
    addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            removeOnGlobalLayoutListener(this)
            listener.onGlobalLayout()
        }
    })
}
  1. Replace the following code
val onLayoutChanged =
    ViewTreeObserver.OnGlobalLayoutListener {
        setMeasure.invoke()
    }
leftView.viewTreeObserver.addOnGlobalLayoutListener(onLayoutChanged)
rightView?.viewTreeObserver?.addOnGlobalLayoutListener(onLayoutChanged)

with

leftView.viewTreeObserver.addDisposableOnGlobalLayoutListener { setMeasure.invoke() }
rightView?.viewTreeObserver?.addDisposableOnGlobalLayoutListener { setMeasure.invoke() }

Hello, what you have posted has helped me but I have a performance problem and it is that the animation of the tablayout fails, any idea how to solve this? I have basically my view built with a viewpager 2 and a tablaout but when I add this listener the animation gets slow and not clean

angelmarrugo avatar Dec 06 '21 13:12 angelmarrugo

this is for wrapping the height of each view

import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2


class ViewPager2ViewHeightAnimator {

    var viewPager2: ViewPager2? = null; set(value) {
        if (field != value) {
            field?.unregisterOnPageChangeCallback(onPageChangeCallback)
            field = value
            value?.registerOnPageChangeCallback(onPageChangeCallback)
        }
    }

    private val layoutManager: LinearLayoutManager? get() = (viewPager2?.getChildAt(0) as? RecyclerView)?.layoutManager as? LinearLayoutManager


    private val onPageChangeCallback = object : ViewPager2.OnPageChangeCallback(){
        override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
            super.onPageScrolled(position, positionOffset, positionOffsetPixels)
            recalculate(position, positionOffset)
        }
    }

    fun recalculate(position: Int, positionOffset: Float = 0f) = layoutManager?.apply {
        val leftView = findViewByPosition(position) ?: return@apply
        val rightView = findViewByPosition(position + 1)
        viewPager2?.apply {
            val leftHeight = getMeasuredViewHeightFor(leftView)
            layoutParams = layoutParams.apply {
                height = if (rightView != null) {
                    val rightHeight = getMeasuredViewHeightFor(rightView)
                    leftHeight + ((rightHeight - leftHeight) * positionOffset).toInt()
                } else {
                    leftHeight
                }
            }
            invalidate()
        }
    }

    private fun getMeasuredViewHeightFor(view: View) : Int {
        val wMeasureSpec = View.MeasureSpec.makeMeasureSpec(view.width, View.MeasureSpec.EXACTLY)
        val hMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
        view.measure(wMeasureSpec, hMeasureSpec)
        return view.measuredHeight
    }
}

I do some improvements on it to change the height of viewpager if view height changes on runtime:

class ViewPager2ViewHeightAnimator {

    var viewPager2: ViewPager2? = null; set(value) {
        if (field != value) {
            field?.unregisterOnPageChangeCallback(onPageChangeCallback)
            field = value
            value?.registerOnPageChangeCallback(onPageChangeCallback)
        }
    }

    private val layoutManager: LinearLayoutManager? get() = (viewPager2?.getChildAt(0) as? RecyclerView)?.layoutManager as? LinearLayoutManager

    private val onPageChangeCallback = object : ViewPager2.OnPageChangeCallback() {
        override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
            super.onPageScrolled(position, positionOffset, positionOffsetPixels)
            recalculate(position, positionOffset)
        }
    }

    fun recalculate(position: Int, positionOffset: Float = 0f) = layoutManager?.apply {
        val leftView = findViewByPosition(position) ?: return@apply
        val rightView = findViewByPosition(position + 1)
        val setMeasure = {
            viewPager2?.apply {
                val leftHeight = getMeasuredViewHeightFor(leftView)
                layoutParams = layoutParams.apply {
                    height = if (rightView != null) {
                        val rightHeight = getMeasuredViewHeightFor(rightView)
                        leftHeight + ((rightHeight - leftHeight) * positionOffset).toInt()
                    } else {
                        leftHeight
                    }
                }
                invalidate()
            }
        }
        val onLayoutChanged =
            ViewTreeObserver.OnGlobalLayoutListener {
                setMeasure.invoke()
            }
        leftView.viewTreeObserver.addOnGlobalLayoutListener(onLayoutChanged)
        rightView?.viewTreeObserver?.addOnGlobalLayoutListener(onLayoutChanged)
        setMeasure.invoke()
    }

    private fun getMeasuredViewHeightFor(view: View): Int {
        val wMeasureSpec = View.MeasureSpec.makeMeasureSpec(view.width, View.MeasureSpec.EXACTLY)
        val hMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
        view.measure(wMeasureSpec, hMeasureSpec)
        return view.measuredHeight
    }
}

I encountered a bug when applying this solution: the first fragment in viewPager2 is always match_parent when just entering the page And, I solved it by making OnGlobalLayoutListener disposable

  1. Add an extension for convenience
fun ViewTreeObserver.addDisposableOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener) {
    addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            removeOnGlobalLayoutListener(this)
            listener.onGlobalLayout()
        }
    })
}
  1. Replace the following code
val onLayoutChanged =
    ViewTreeObserver.OnGlobalLayoutListener {
        setMeasure.invoke()
    }
leftView.viewTreeObserver.addOnGlobalLayoutListener(onLayoutChanged)
rightView?.viewTreeObserver?.addOnGlobalLayoutListener(onLayoutChanged)

with

leftView.viewTreeObserver.addDisposableOnGlobalLayoutListener { setMeasure.invoke() }
rightView?.viewTreeObserver?.addDisposableOnGlobalLayoutListener { setMeasure.invoke() }

Hello, what you have posted has helped me but I have a performance problem and it is that the animation of the tablayout fails, any idea how to solve this? I have basically my view built with a viewpager 2 and a tablaout but when I add this listener the animation gets slow and not clean

This method has been deprecated, I suggest using: view.requireView() on onResume method of viewpager2 fragments.

MohammadRezaei92 avatar Dec 11 '21 11:12 MohammadRezaei92

class ViewPager2ViewHeightAnimator ...

It works but I am presenting performance problems when tabbing or changing pages, each time I change the scroll of the first fragment becomes slower

angelmarrugo avatar Jan 06 '22 22:01 angelmarrugo

this is for wrapping the height of each view

import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2


class ViewPager2ViewHeightAnimator {

    var viewPager2: ViewPager2? = null; set(value) {
        if (field != value) {
            field?.unregisterOnPageChangeCallback(onPageChangeCallback)
            field = value
            value?.registerOnPageChangeCallback(onPageChangeCallback)
        }
    }

    private val layoutManager: LinearLayoutManager? get() = (viewPager2?.getChildAt(0) as? RecyclerView)?.layoutManager as? LinearLayoutManager


    private val onPageChangeCallback = object : ViewPager2.OnPageChangeCallback(){
        override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
            super.onPageScrolled(position, positionOffset, positionOffsetPixels)
            recalculate(position, positionOffset)
        }
    }

    fun recalculate(position: Int, positionOffset: Float = 0f) = layoutManager?.apply {
        val leftView = findViewByPosition(position) ?: return@apply
        val rightView = findViewByPosition(position + 1)
        viewPager2?.apply {
            val leftHeight = getMeasuredViewHeightFor(leftView)
            layoutParams = layoutParams.apply {
                height = if (rightView != null) {
                    val rightHeight = getMeasuredViewHeightFor(rightView)
                    leftHeight + ((rightHeight - leftHeight) * positionOffset).toInt()
                } else {
                    leftHeight
                }
            }
            invalidate()
        }
    }

    private fun getMeasuredViewHeightFor(view: View) : Int {
        val wMeasureSpec = View.MeasureSpec.makeMeasureSpec(view.width, View.MeasureSpec.EXACTLY)
        val hMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
        view.measure(wMeasureSpec, hMeasureSpec)
        return view.measuredHeight
    }
}

I do some improvements on it to change the height of viewpager if view height changes on runtime:

class ViewPager2ViewHeightAnimator {

    var viewPager2: ViewPager2? = null; set(value) {
        if (field != value) {
            field?.unregisterOnPageChangeCallback(onPageChangeCallback)
            field = value
            value?.registerOnPageChangeCallback(onPageChangeCallback)
        }
    }

    private val layoutManager: LinearLayoutManager? get() = (viewPager2?.getChildAt(0) as? RecyclerView)?.layoutManager as? LinearLayoutManager

    private val onPageChangeCallback = object : ViewPager2.OnPageChangeCallback() {
        override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
            super.onPageScrolled(position, positionOffset, positionOffsetPixels)
            recalculate(position, positionOffset)
        }
    }

    fun recalculate(position: Int, positionOffset: Float = 0f) = layoutManager?.apply {
        val leftView = findViewByPosition(position) ?: return@apply
        val rightView = findViewByPosition(position + 1)
        val setMeasure = {
            viewPager2?.apply {
                val leftHeight = getMeasuredViewHeightFor(leftView)
                layoutParams = layoutParams.apply {
                    height = if (rightView != null) {
                        val rightHeight = getMeasuredViewHeightFor(rightView)
                        leftHeight + ((rightHeight - leftHeight) * positionOffset).toInt()
                    } else {
                        leftHeight
                    }
                }
                invalidate()
            }
        }
        val onLayoutChanged =
            ViewTreeObserver.OnGlobalLayoutListener {
                setMeasure.invoke()
            }
        leftView.viewTreeObserver.addOnGlobalLayoutListener(onLayoutChanged)
        rightView?.viewTreeObserver?.addOnGlobalLayoutListener(onLayoutChanged)
        setMeasure.invoke()
    }

    private fun getMeasuredViewHeightFor(view: View): Int {
        val wMeasureSpec = View.MeasureSpec.makeMeasureSpec(view.width, View.MeasureSpec.EXACTLY)
        val hMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
        view.measure(wMeasureSpec, hMeasureSpec)
        return view.measuredHeight
    }
}

the improvements that you made are dangerous to use in fragments, as their view can still be referenced by registering them as globalLayoutListeners. (fyi)

DennisVanAcker1 avatar May 23 '22 02:05 DennisVanAcker1