Dynamic Height Viewpager

Dynamic height viewpager

@abhishek's ans does what is required but the code below also adds animation during height change

public class WrappingViewPager extends ViewPager {

private Boolean mAnimStarted = false;

public WrappingViewPager(Context context) {
super(context);
}

public WrappingViewPager(Context context, AttributeSet attrs){
super(context, attrs);
}

@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);

if(!mAnimStarted && null != getAdapter()) {
int height = 0;
View child = ((FragmentPagerAdapter) getAdapter()).getItem(getCurrentItem()).getView();
if (child != null) {
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
height = child.getMeasuredHeight();
if (VersionUtils.isJellyBean() && height < getMinimumHeight()) {
height = getMinimumHeight();
}
}

// Not the best place to put this animation, but it works pretty good.
int newHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
if (getLayoutParams().height != 0 && heightMeasureSpec != newHeight) {
final int targetHeight = height;
final int currentHeight = getLayoutParams().height;
final int heightChange = targetHeight - currentHeight;

Animation a = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if (interpolatedTime >= 1) {
getLayoutParams().height = targetHeight;
} else {
int stepHeight = (int) (heightChange * interpolatedTime);
getLayoutParams().height = currentHeight + stepHeight;
}
requestLayout();
}

@Override
public boolean willChangeBounds() {
return true;
}
};

a.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
mAnimStarted = true;
}

@Override
public void onAnimationEnd(Animation animation) {
mAnimStarted = false;
}

@Override
public void onAnimationRepeat(Animation animation) {
}
});

a.setDuration(1000);
startAnimation(a);
mAnimStarted = true;
} else {
heightMeasureSpec = newHeight;
}
}

super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}

Android ViewPager with dynamic height

I found a good solution here If anyone else was wondering how to make ViewPager change It's height dynamically.

Also you can find quick answer here If you don't have time to look at Git.

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.

CoordinatorLayout with recyclerview below

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"

Viewpager with dynamic height not working (always use the first fragment's height)

Hi Blaze found some thing use full, following solution is working for me.


Its include a custom scroll view and custom view pager. The purpose custom view pager is its calculate the height dynamically according to its children's height, its hieght is set to wrap content initially and the purpose of scroll view is handling the touch event , if the users scrolls the screen vertically it will not pass the touch even to scroll and thus user is able to scroll the page.

Custom scroll view

public class CustomScrollView extends ScrollView {

private GestureDetector mGestureDetector;

public CustomScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
mGestureDetector = new GestureDetector(context, new YScrollDetector());
setFadingEdgeLength(0);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return super.onInterceptTouchEvent(ev)
&& mGestureDetector.onTouchEvent(ev);
}

// Return false if we're scrolling in the x direction
class YScrollDetector extends SimpleOnGestureListener {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
return (Math.abs(distanceY) > Math.abs(distanceX));
}
}
}

Custom view pager

public class CustomPager extends ViewPager{

public CustomPager (Context context) {
super(context);
}

public CustomPager (Context context, AttributeSet attrs) {
super(context, attrs);
}

@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);

boolean wrapHeight = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST;

final View tab = getChildAt(0);
int width = getMeasuredWidth();
int tabHeight = tab.getMeasuredHeight();

if (wrapHeight) {
// Keep the current measured width.
widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
}

int fragmentHeight = measureFragment(((Fragment) getAdapter().instantiateItem(this, getCurrentItem())).getView());
heightMeasureSpec = MeasureSpec.makeMeasureSpec(tabHeight + fragmentHeight + (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, getResources().getDisplayMetrics()), MeasureSpec.AT_MOST);

super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

public int measureFragment(View view) {
if (view == null)
return 0;

view.measure(0, 0);
return view.getMeasuredHeight();
}
}

Layout file

<com.example.demo2.activity.widget.CustomScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >

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

<TextView
style="@style/text"
android:text="Text 1" />

<View style="@style/text_divider" />

<TextView
style="@style/text"
android:text="Text 2" />

<com.example.demo2.activity.widget.CustomPager
android:id="@+id/myViewPager"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

<View style="@style/text_divider" />

<TextView
style="@style/text"
android:text="Text 4" />

<View style="@style/text_divider" />

<TextView
style="@style/text"
android:text="Text 5" />

<View style="@style/text_divider" />

<TextView
style="@style/text"
android:text="Text 6" />

<View style="@style/text_divider" />

<TextView
style="@style/text"
android:text="Text 7" />

<View style="@style/text_divider" />

<TextView
style="@style/text"
android:text="Text 8" />

<View style="@style/text_divider" />

<TextView
style="@style/text"
android:text="Text 9" />

<View style="@style/text_divider" />

<TextView
style="@style/text"
android:text="Text 10" />
</LinearLayout>

Activity

public class MainActivity extends FragmentActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

MyPagerAdapter pageAdapter = new MyPagerAdapter(getSupportFragmentManager());
ViewPager pager = (ViewPager)findViewById(R.id.myViewPager);
pager.setAdapter(pageAdapter);

}

}

Fragment adapter

fragments with different heights are added to the viewpager, FragmentBlue,Green etc are just fragments used to add to the viewpager you can use your own fragments, one thing to remember is their outer layout should be same in my case I used liner layout for all

public class MyPagerAdapter extends FragmentPagerAdapter {

private List<Fragment> fragments;

public MyPagerAdapter(FragmentManager fm) {
super(fm);
this.fragments = new ArrayList<Fragment>();
fragments.add(new FragmentBlue());
fragments.add(new FragmentGreen());
fragments.add(new FragmentPink());
fragments.add(new FragmentRed());
}

@Override
public Fragment getItem(int position) {
return fragments.get(position);
}

@Override
public int getCount() {
return fragments.size();
}
}

Android : ViewPager with a dynamic height within a ScrollView

This might seem like really stupid and I am not sure why this works exactly! If anyone knows why, please do answer.

The problem with the ViewPager collapsing was that it simply contained a WebView in each Fragment. I toiled hard trying to find solutions and writing more custom ViewPagers and ScrollViews. Just when I was about to give up I wrapped the WebView in a LinearLayout and the Fragment at position 0 did not collapse. It works smoothly now.

So if anyone is getting blank pages while trying this or similar solutions please try to wrap it in a LinearLayout or something.

Hope this helps someone!

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();
}

}

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



Related Topics



Leave a reply



Submit