Search code examples
androidandroid-viewpagerandroid-custom-viewandroid-scrollview

Dynamic height viewpager


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


Solution

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