How to wrap the height of a ViewPager to the height of its current Fragment?
You can customize the ViewPager
to resize the ViewPager
to it's current page size on page swipe.
You can use the below code.
public class WrapContentViewPager extends ViewPager {
private int mCurrentPagePosition = 0;
public WrapContentViewPager(Context context) {
super(context);
}
public WrapContentViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
try {
boolean wrapHeight = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST;
if (wrapHeight) {
View child = getChildAt(mCurrentPagePosition);
if (child != null) {
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = child.getMeasuredHeight();
heightMeasureSpec = MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY);
}
}
} catch (Exception e) {
e.printStackTrace();
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public void reMeasureCurrentPage(int position) {
mCurrentPagePosition = position;
requestLayout();
}
}
Declare it in xml:
<your.package.name.WrapContentViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</your.package.name.WrapContentViewPager>
After that call reMeasureCurrentpage
function on page swipe.
final WrapContentViewPager wrapContentViewPager = (WrapContentViewPager) findViewById(R.id.view_pager);
wrapContentViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
wrapContentViewPager.reMeasureCurrentPage(wrapContentViewPager.getCurrentItem());
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
ViewPager2 with differing item heights and WRAP_CONTENT
The solution is to register a PageChangeCallback
and adjust the LayoutParams
of the ViewPager2
after asking the child to re-measure itself.
pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
val view = // ... get the view
view.post {
val wMeasureSpec = MeasureSpec.makeMeasureSpec(view.width, MeasureSpec.EXACTLY)
val hMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
view.measure(wMeasureSpec, hMeasureSpec)
if (pager.layoutParams.height != view.measuredHeight) {
// ParentViewGroup is, for example, LinearLayout
// ... or whatever the parent of the ViewPager2 is
pager.layoutParams = (pager.layoutParams as ParentViewGroup.LayoutParams)
.also { lp -> lp.height = view.measuredHeight }
}
}
}
})
Alternatively, if your view's height can change at some point due to e.g. asynchronous data load, then use a global layout listener instead:
pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
private val listener = ViewTreeObserver.OnGlobalLayoutListener {
val view = // ... get the view
updatePagerHeightForChild(view)
}
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
val view = // ... get the view
// ... IMPORTANT: remove the global layout listener from other views
otherViews.forEach { it.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener) }
view.viewTreeObserver.addOnGlobalLayoutListener(layoutListener)
}
private fun updatePagerHeightForChild(view: View) {
view.post {
val wMeasureSpec = MeasureSpec.makeMeasureSpec(view.width, MeasureSpec.EXACTLY)
val hMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
view.measure(wMeasureSpec, hMeasureSpec)
if (pager.layoutParams.height != view.measuredHeight) {
// ParentViewGroup is, for example, LinearLayout
// ... or whatever the parent of the ViewPager2 is
pager.layoutParams = (pager.layoutParams as ParentViewGroup.LayoutParams)
.also { lp -> lp.height = view.measuredHeight }
}
}
}
}
See discussion here:
https://issuetracker.google.com/u/0/issues/143095219
How set dynamic height of fragment contains RecycleView in ViewPager android?
Finally, I Create custom class from ViewPager like this :
public class WrappedHeightTabbedCardViewPager extends NonSwipeableViewPager {
private int currentPagePosition = 0;
public WrappedHeightTabbedCardViewPager(@NonNull Context context) {
super(context);
}
public WrappedHeightTabbedCardViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
View child = getChildAt(currentPagePosition);
if (child != null) {
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = child.getMeasuredHeight();
heightMeasureSpec = MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY);
}
if (heightMeasureSpec != 0) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
public void reMeasureCurrentPage(int position) {
currentPagePosition = position;
requestLayout();
}
}
Dynamic height viewpager with recyclerview and tablayout
Your main problem is nesting a RecyclerView inside a ScrollView. This can easily cause performance issues since the RecyclerView doesn't know which part of itself is currently on screen, so it loads all items at once. Add a print log inonBindViewHolder
to confirm this. Also it means the RecyclerView height (and thus the viewpager height) is now arbitrary and not limited to the size of the screen, which will make it impossible to centre your text.
To achieve the layout you want, I would replace NestedScrollView with a CoordinatorLayout. I've done this successfully in my app for what I think is the exact same situation. Then you would no longer need a custom ViewPager.
Here is a tested working example using your layout:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|enterAlways">
<RelativeLayout
android:id="@+id/header"
android:layout_width="0dp"
android:layout_height="250dp"
android:background="#F8F8F8"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/header">
</com.google.android.material.tabs.TabLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager.widget.ViewPager
android:id="@+id/items"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_anchor="@id/app_layout"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Note that you must remove this line from RecyclerView:android:nestedScrollingEnabled="false"
Related Topics
Exception:Aapt2 Error: Check Logs for Details
Using Listview:How to Add a Header View
Get Listview Item Position on Button Click
Cmusphinx Pocketsphinx - Recognize All (Or Large Amount) of Words
How to Record Microphone to More Compressed Format During Webrtc Call on Android
How to Programmatically Clear Application Data
Android Recording Incoming and Outgoing Calls
How to Write Style to Error Text of Edittext in Android
How to Get the Current Location in Google Maps Android API V2
Kotlin: Whats Does "Return@" Mean
How to Set Dropdown Arrow in Spinner
How to Add an Image in Email Body
Why Does Alertdialog.Builder(Context Context) Only Accepts Activity as a Parameter
How to Send JSON Object to Server Using Volley in Android
Why a New Viewmodel Is Created in Each Compose Navigation Route
Android Share Intent for a Bitmap - Is It Possible Not to Save It Prior Sharing