简体   繁体   中英

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

This question is for the new ViewPager2 class.

There is a similar question for the old ViewPager , but the solution requires extending ViewPager . However, ViewPager2 is final so cannot be extended.

In my situation, I have a ViewPager2 that contains three different fragments. One of these fragments is much taller than the other two, which means when you swipe to one of the shorter fragments, there is a lot of empty space. So how do I effectively wrap the ViewPager2 to the height of the current fragment?

The solution is to add the following in each fragment that is part of the ViewPager2 :

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

binding.root is from ViewBinding/DataBinding but it's is the main container of your layout, ie ConstraintLayout , LinearLayout , etc.

I'm not at all happy with this workaround, but it does solve the rendering problems I had when trying to use ViewPager2 with fragments of different heights. It will obviously slow down rendering and consume more memory.

myStupidViewPager2.offscreenPageLimit = fragments.size

I have found an answer to this question where i access the child through it's adapter as when you access the child at any given position rather than Zer0 index the view is null.

    class ViewPager2PageChangeCallback() : ViewPager2.OnPageChangeCallback() {
        override fun onPageSelected(position: Int) {
            val view = (pager.adapter as pagerAdapter).getViewAtPosition(position)
            view?.let {
                updatePagerHeightForChild(view, pager)
            }
        }
    }

    private fun updatePagerHeightForChild(view: View, pager: ViewPager2) {
        view.post {
            val wMeasureSpec =
                View.MeasureSpec.makeMeasureSpec(view.width, View.MeasureSpec.EXACTLY)
            val hMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
            view.measure(wMeasureSpec, hMeasureSpec)

            if (pager.layoutParams.height != view.measuredHeight) {
                pager.layoutParams = (pager.layoutParams)
                    .also { lp ->
                        lp.height = view.measuredHeight
                    }
            }
        }
    }

   pager.offscreenPageLimit = 1            
   pagerpager.registerOnPageChangeCallback(ViewPager2PageChangeCallback())

     override fun onDestroy() {
        super.onDestroy()
        pager.unregisterOnPageChangeCallback(ViewPager2PageChangeCallback())
    }

 

    class OrderDetailsPager(
    private val arrayList: ArrayList<Fragment>,
    fragmentManger: FragmentManager,
    lifecycle: Lifecycle
    ) : FragmentStateAdapter(fragmentManger, lifecycle) {
    override fun createFragment(position: Int): Fragment {
        return arrayList[position]
    }

    override fun getItemCount(): Int {
        return arrayList.size
    }

    fun getViewAtPosition(position: Int): View? {
        return arrayList[position].view
    }
}

I had the same problem, ViewPager2 was in ScrollView, which is under TabLayout. After navigating through the tabs, the height of the ScrollView became equal to the height of the maximum fragment height. The problem was solved by transferring the ScrollView to the fragment.

This worked for me after spending a lot of time, My viewpager2 is inside a Nesterscrollview

binding.viewpager.offscreenPageLimit = 2
binding.viewpager.isUserInputEnabled = false
binding.viewpager.registerOnPageChangeCallback(object :ViewPager2.OnPageChangeCallback(){
     override fun onPageSelected(position: Int) {
           super.onPageSelected(position)
           val myFragment = childFragmentManager.findFragmentByTag("f$position")
           myFragment?.view?.let { updatePagerHeightForChild(it,binding.viewpager) }
     }
})

private fun updatePagerHeightForChild(view: View, pager: ViewPager2) {
        view.post {
            val wMeasureSpec =
                View.MeasureSpec.makeMeasureSpec(view.width, View.MeasureSpec.EXACTLY)
            val hMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
            view.measure(wMeasureSpec, hMeasureSpec)

            if (pager.layoutParams.height != view.measuredHeight) {
                pager.layoutParams = (pager.layoutParams)
                    .also { lp ->
                        lp.height = view.measuredHeight
                    }
            }
        }
    }

Based on Lucas Nobile I found out another way of implementing. Instead of putting the following code on the OnResume method of each affected fragment:

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

Just add this override to the adapter of the viewPager2:

override fun onBindViewHolder(
   holder: FragmentViewHolder,
   position: Int,
   payloads: MutableList<Any>
){
   holder.itemView.requestLayout()
   super.onBindViewHolder(holder, position, payloads)
}

Obs: Code made in kotlin.

For me set viewpager2's recyclerview layout params resolve this problem.

Try

<androidx.viewpager2.widget.ViewPager2
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

and set recyclerview layout params

RecyclerView recyclerView = (RecyclerView) viewPager.getChildAt(0);
recyclerView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));

https://gist.github.com/safaorhan/1a541af729c7657426138d18b87d5bd4

this work for me;

Thanks this work for me ,when we want to use viewpager as Book ' pages. There are different kinds of page height. Sometimes , we need to scroll action when page is too long :3 At that time, just wrap your viewpager's item layout with NestedScroll View..

Thanks to @safaorhan

/** * Disables the child attach listener so that inflated children with wrap_content heights can pass. * * This is very fragile and depends on the implementation details of [ViewPager2]. * * @see ViewPager2.enforceChildFillListener (the removed listener) */

// call this method with your viewpager...

private fun ViewPager2.hackMatchParentCheckInViewPager() {
    (getChildAt(0) as RecyclerView).clearOnChildAttachStateChangeListeners()
}

//View Pager

<androidx.viewpager2.widget.ViewPager2
        android:id="@+id/page_view_pager"
        android:layout_width="match_parent"
        android:layout_height="0dp">

//Your Child Viewpager item

<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:layout_width="match_parent"
    android:layout_height="wrap_content">

    <LinearLayout
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
      ....
    </LinearLayout>
    </androidx.core.widget.NestedScrollView>

I was able to do so by overriding onPageSelected callback and remeasuring the current view's height.

It would look something like this:

mViewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
            @Override
            public void onPageSelected(int position) {
                mTabLayout.selectTab(mTabLayout.getTabAt(position));

 View view = mViewPagerAdapter.getViewAtPosition(position); // this is a method i have in the adapter that returns the fragment's view, by calling fragment.getview()
                if (view != null) {
                    view.post(() -> {
                        int wMeasureSpec = View.MeasureSpec.makeMeasureSpec(view.getWidth(), View.MeasureSpec.EXACTLY);
                        int hMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
                        view.measure(wMeasureSpec, hMeasureSpec);
                        viewPager.getLayoutParams().height = view.getMeasuredHeight();
                        mViewPagerAdapter.notifyDataSetChanged();
                    });
                }
            }
        });
        mViewPager.setOffscreenPageLimit(mTabLayout.getTabCount());

The layout of TabLayout and Viewpager2 looks the following (inside a NestedScrollView)

<LinearLayout
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:orientation="vertical">

                        <com.google.android.material.tabs.TabLayout
                            android:id="@+id/data_collection_tab_layout"
                            android:layout_width="match_parent"
                            android:layout_marginBottom="12dp"
                            android:layout_height="wrap_content" />

                        <androidx.viewpager2.widget.ViewPager2
                            android:id="@+id/data_collection_viewpager2"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"/>

                    </LinearLayout>

Also, please make sure all the Fragment's layout height it's wrap content.

I have an issue with dynamic pages height which could change in the runtime. After page change, some of the content on the highest page is cut off.

I was able to fix that by re-measuring pager content (ie page) and pager itself.

         viewPager2.setPageTransformer { page, _ ->
                val wMeasureSpec =
                    View.MeasureSpec.makeMeasureSpec(page.width, View.MeasureSpec.EXACTLY)
                val hMeasureSpec =
                    View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
                page.measure(wMeasureSpec, hMeasureSpec)
                measure(wMeasureSpec, hMeasureSpec)
                post {
                    adapter?.notifyDataSetChanged()
                }
            }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM