android / views-widgets-samples

Multiple samples showing the best practices in views-widgets on Android.
Apache License 2.0
5.03k stars 3.01k forks source link

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

Open sindicly opened 3 years ago

sindicly commented 3 years ago

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

sindicly commented 3 years ago

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 commented 3 years ago

Why can't viewpager2 be inherited without openness at all

CodeK1988 commented 3 years ago

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

hadia commented 3 years ago

any luck for solving the issue

yinxiucheng commented 3 years ago

@hadia could you paste the code of solving.

yinxiucheng commented 3 years ago

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

JiaYuZ commented 3 years ago

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

ChinGyi2019 commented 3 years ago

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

hereisderek commented 3 years ago

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
    }
}
whatiamdoing commented 3 years ago

thanks dude)

MohammadRezaei92 commented 3 years ago

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
    }
}
Ikrimah1998 commented 3 years ago

how do i implement ViewPager2ViewHeightAnimator ?

Ikrimah1998 commented 3 years ago

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?

MohammadRezaei92 commented 3 years ago

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.

jasonRap79 commented 3 years ago

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 commented 3 years ago

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 .

Ikrimah1998 commented 3 years ago

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

MohammadRezaei92 commented 3 years ago

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?

Ikrimah1998 commented 3 years ago

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

hereisderek commented 3 years ago

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

letsky commented 3 years ago

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
Ikrimah1998 commented 3 years ago

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.?

adherencegoo commented 2 years ago

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()
        }
    })
    }
  2. 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() }
Ikrimah1998 commented 2 years ago

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 .

sdzshn3 commented 2 years ago

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()
    }
MarkWang33 commented 2 years ago

Thanks guys for help this problem.

angelmarrugo commented 2 years ago

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

MohammadRezaei92 commented 2 years ago

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.

angelmarrugo commented 2 years ago

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

DennisVanAcker1 commented 2 years ago

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)

ranjithdurai commented 1 year ago

thank you all, I have been trying last 3 days but I can't solve this problem. now I get a result , love you all😘😘❤️