Dynamic height viewpager
Solution 1
@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);
}
}
Solution 2
Made a few tweaks in your code and it is working fine now.
1] onMeasure
function wasn't proper. Use below logic
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mCurrentView == null) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
int height = 0;
mCurrentView.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = mCurrentView.getMeasuredHeight();
if (h > height) height = h;
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
2] ViewPager
needs to be re-measured each time a page is changed. Good place to do this is setPrimaryItem
function of PagerAdapter
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
super.setPrimaryItem(container, position, object);
if (position != mCurrentPosition) {
Fragment fragment = (Fragment) object;
CustomPager pager = (CustomPager) container;
if (fragment != null && fragment.getView() != null) {
mCurrentPosition = position;
pager.measureCurrentView(fragment.getView());
}
}
}
Here is the link to GitHub project with these tweaks: https://github.com/vabhishek/WrapContentViewPagerDemo
Solution 3
Just in case someone else find this post like me. Worked version without bug of initially zero height:
public class DynamicHeightViewPager extends ViewPager {
private View mCurrentView;
public DynamicHeightViewPager(Context context) {
super(context);
}
public DynamicHeightViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mCurrentView != null) {
mCurrentView.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int height = Math.max(0, mCurrentView.getMeasuredHeight());
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
public void measureCurrentView(View currentView) {
mCurrentView = currentView;
requestLayout();
}
}
And used it in custom FragmentPagerAdapter, like this
public abstract class AutoheightFragmentPagerAdapter extends FragmentPagerAdapter {
private int mCurrentPosition = -1;
public AutoheightFragmentPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
super.setPrimaryItem(container, position, object);
if (position != mCurrentPosition && container instanceof DynamicHeightViewPager) {
Fragment fragment = (Fragment) object;
DynamicHeightViewPager pager = (DynamicHeightViewPager) container;
if (fragment != null && fragment.getView() != null) {
mCurrentPosition = position;
pager.measureCurrentView(fragment.getView());
}
}
}
}
Solution 4
Adding to @vihaan's solution, if you have a PagerTitleStrip or PagetTabStrip, you can add this
// Account for pagerTitleStrip or pagerTabStrip
View tabStrip = getChildAt(0);
if (tabStrip instanceof PagerTitleStrip) {
tabStrip.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.UNSPECIFIED));
height += tabStrip.getMeasuredHeight();
}
just before starting the animation (before the comment
// Not the best place to put this animation, but it works pretty good.
so that the height of the strip is taken into account.
Vihaan Verma
Updated on July 21, 2022Comments
-
Vihaan Verma almost 2 years
I'm trying to create a custom viewpager inside custom scroll viewthat dynamically wraps the current child's height.
package com.example.vihaan.dynamicviewpager; import android.content.Context; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.MotionEvent; import android.widget.ScrollView; /** * Created by vihaan on 1/9/15. */ 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 GestureDetector.SimpleOnGestureListener { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return (Math.abs(distanceY) > Math.abs(distanceX)); } } }
CustomPager
/** * Created by vihaan on 1/9/15. */ 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(); } }
MyPagerAdapter
public class MyPagerAdapter extends FragmentPagerAdapter { private List<Fragment> fragments; public MyPagerAdapter(FragmentManager fm) { super(fm); this.fragments = new ArrayList<Fragment>(); fragments.add(new FirstFragment()); fragments.add(new SecondFragment()); fragments.add(new ThirdFragment()); fragments.add(new FourthFragment()); } @Override public Fragment getItem(int position) { return fragments.get(position); } @Override public int getCount() { return fragments.size(); } }
I was hoping that this would wrap around current fragments height but it is only taking the height of first child into consideration.
Sample github project : https://github.com/VihaanVerma89/DynamicViewPager
-
Timmiej93 about 8 yearsIsn't this horribly laggy / not-smooth? It is for me at least (on emulator).
-
Vihaan Verma about 8 years@Timmiej93 You can check it out live on my app play.google.com/store/apps/details?id=com.mirraw.android . Go onto product detail page. It works flawlessly :)
-
Timmiej93 about 8 yearsI'm sorry, I'm probably (most likely) being very stupid, but where would I find your app? I could pull it from GitHub, but that version doesn't have the changes in it (as far as I can see). EDIT: You just added the link as I typed this, thanks!
-
Timmiej93 about 8 yearsWith 'product detail page' do you mean the section with [Specifications - Shipping - Payment - Returns]? If so, that's indeed very smooth. I guess mine is incredibly laggy because I'm resizing an AlertDialog. Why do I always want the impossible, lol.
-
Narayan Acharya almost 8 yearsHi! I used your solution to nest my ViewPager within a ScrollView. The first fragment of the ViewPager always gets a height of 0 even though I have content there. Other fragments work properly. When I slide to the first fragment, I can see the content in there but as soon as I am completely on that tab the fragment collapses. I can't seem to figure out what is going wrong.
-
Abhishek V almost 8 years@NarayanAcharya Can you post your code? preferably as a new question & put the link to the question here.
-
Narayan Acharya almost 8 yearsI added my code in this question stackoverflow.com/questions/38349809/… . Please have a look. Thanks!
-
Bartando almost 8 years@NarayanAcharya Hey, I know its a bit late, but use the mCurrentPosition like ... private int mCurrentPosition = -1; ... its important to set it to -1
-
Narayan Acharya almost 8 yearsHi @Bartando Thanks for the reply. I have initialized the mCurrentPosition to -1. You can check out a separate question here stackoverflow.com/questions/38349809/… . I am yet to find a solution to this :(
-
Govind over 7 yearsThis code will lead to blank first page in few cases. "heightMeasureSpec" in the last "super.onMeasure(widthMeasureSpec, heightMeasureSpec)" statement should have the new height.. basically use "super.onMeasure(widthMeasureSpec, newHeight);
-
Faisal Naseer about 7 yearswhats the versionUtils
-
cammando about 7 years@AbhishekV why did you created the custom ScrollView
-
cammando about 7 yearsIt is giving me class cast exception i am using it without scroll view
-
cammando about 7 yearsresolved the class cast exception with cleaning and rebuilding the project
-
cammando about 7 years@AbhishekV this solution is not inflating the view Pager for the first time
-
Asalas77 about 7 years@AbhishekV i know this is an old post, but could you tell me how to modify this to always fit height to the biggest child view?
-
Felipe A. almost 7 years@AbhishekV, can you help me man? I'm having a problem because I have a recyclerview inside the fragment I inflate on viewpager... And the layoutmanager trigger after onMeasure method have triggered... What i need to do with this?
-
Vucko almost 6 yearsVery helpful, my height was NOT being properly calculated cause it's counting the TabLayout as the child at 0, so taking max height of all the children was not a good solution. I needed max height + height of child at 0. Thanks man, you saved my day.
-
John61590 almost 6 years
measureCurrentView
isn't used anywhere? -
ITurchenko almost 6 years@John61590 thanks for comment, I added custom adapter example
-
Sonu Sanjeev over 5 yearsRemoving the return method inside if (mCurrentView == null) on the onMeasure, solved my issues of the first fragment not getting shown.
-
ziniestro almost 5 yearsgreat example, I've combined it with my customViewPager and works beautifully, thanks
-
arberg about 4 yearsThis looks nearly identical to ITurchenko answer, but is 2 years older. It is unclear what is problem of ITurchenko solution that this should solve.
-
Noorul about 4 yearsI think it is not working on view pager inside nestedscrollview.
-
Ali Rezaiyan almost 4 yearsTake care of which adapter you are using. this line
View child = ((FragmentPagerAdapter) getAdapter()).getItem(getCurrentItem()).getView();
might crash duo to cast exception. -
Sarvesh Athawale about 3 yearsIt seems like it doesn't work if ConstraintLayout is used in one of your Fragment's layout.
-
David Kariuki almost 3 yearsThis folds the longest view and does not stretch it after refocus