35

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

0

5 Answers 5

77
+50

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

Sign up to request clarification or add additional context in comments.

14 Comments

Hi! 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.
@NarayanAcharya Can you post your code? preferably as a new question & put the link to the question here.
I added my code in this question stackoverflow.com/questions/38349809/… . Please have a look. Thanks!
@NarayanAcharya Hey, I know its a bit late, but use the mCurrentPosition like ... private int mCurrentPosition = -1; ... its important to set it to -1
Hi @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 :(
|
26

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

8 Comments

Isn't this horribly laggy / not-smooth? It is for me at least (on emulator).
@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 :)
I'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!
With '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.
whats the versionUtils
|
21

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

3 Comments

measureCurrentView isn't used anywhere?
@John61590 thanks for comment, I added custom adapter example
It seems like it doesn't work if ConstraintLayout is used in one of your Fragment's layout.
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.

1 Comment

Very 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.
0
public class WrapContentViewPager extends ViewPager { private Boolean mAnimStarted = false; public WrapContentViewPager(Context context) { super(context); } public WrapContentViewPager(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 = ((CommonViewPagerAdapter) getAdapter()).getItem(getCurrentItem()).getView(); if (child != null) { child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); height = child.getMeasuredHeight(); if (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(100); startAnimation(a); mAnimStarted = true; } else { heightMeasureSpec = newHeight; } } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } } 

==============================

wrapContentViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { wrapContentViewPager.measure(wrapContentViewPager.getMeasuredWidth(), wrapContentViewPager.getMeasuredHeight()); } @Override public void onPageScrollStateChanged(int state) { } }); 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.