Reputation: 18931
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?
Upvotes: 15
Views: 13194
Reputation: 11
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.
Upvotes: 1
Reputation: 88
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 (i.e 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()
}
}
Upvotes: 1
Reputation: 173
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.
Upvotes: 0
Reputation: 575
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
}
}
}
}
Upvotes: 1
Reputation: 1
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>
Upvotes: 0
Reputation: 217
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, i.e. ConstraintLayout
, LinearLayout
, etc.
Upvotes: 15
Reputation: 421
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
}
}
Upvotes: 2
Reputation: 31
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.
Upvotes: 3
Reputation: 7564
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
Upvotes: 8
Reputation: 1
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));
Upvotes: 0