Reputation: 9049
I have setup a simple ViewPager that has an ImageView with a height of 200dp on each page.
Here is my pager:
pager = new ViewPager(this);
pager.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
pager.setBackgroundColor(Color.WHITE);
pager.setOnPageChangeListener(listener);
layout.addView(pager);
Despite the height set as wrap_content, the pager always fills the screen even though the imageview is only 200dp. I tried to replace the height of the pager with "200" but that gives me different results with multiple resolutions. I am unable to add "dp" to that value. How do I add 200dp to the pager's layout?
Upvotes: 279
Views: 133796
Reputation: 6775
Overriding onMeasure of your ViewPager
as follows will make it get the height of the biggest child it currently has.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int height = 0;
int childWidthSpec = MeasureSpec.makeMeasureSpec(
Math.max(0, MeasureSpec.getSize(widthMeasureSpec) -
getPaddingLeft() - getPaddingRight()),
MeasureSpec.getMode(widthMeasureSpec)
);
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.measure(childWidthSpec, MeasureSpec.UNSPECIFIED);
int h = child.getMeasuredHeight();
if (h > height) height = h;
}
if (height != 0) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
Upvotes: 436
Reputation: 116392
For those who want a solution of ViewPager2 that will have the ViewPager2 have the same height as the max height of all of its pages, sadly I've found only this workaround:
viewPager.doOnPreDraw {
//workaround to set the viewPagerheight the same as its children
var height = 0
for (i in 0 until featuresViewPager.adapter!!.itemCount) {
val viewHolder = viewPager.adapter!!.createViewHolder(viewPager, 0)
viewPager.adapter!!.bindViewHolder(viewHolder, i)
val child: View = viewHolder.itemView
child.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(viewPager.width, View.MeasureSpec.EXACTLY)
val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
child.measure(widthMeasureSpec, heightMeasureSpec)
val childHeight = child.measuredHeight
child.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
if (childHeight > height)
height = childHeight
}
viewPager.layoutParams.height = height
}
I say "sadly" because it goes over all of the pages, creates their views, measures them, and on the way it calls functions that are meant for other purposes.
Should work fine in most cases.
Please let me know if you know of a nicer solution.
Upvotes: 0
Reputation: 8552
Another Kotlin code
class DynamicViewPager @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : ViewPager(context, attrs) {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
var height = 0
(0 until childCount).forEach {
val child = getChildAt(it)
child.measure(
widthMeasureSpec,
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
)
height = max(height, child.measuredHeight)
}
if (height > 0) {
super.onMeasure(
widthMeasureSpec,
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
)
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
}
}
Upvotes: 2
Reputation: 483
All answers do not work perfectly. So I created one. Below class would request layout when a new page was selected to make viewPager
's height is the current child view's height.
class WrapContentViewPager : ViewPager {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
private var curPos = 0
init {
addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {}
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {}
override fun onPageSelected(position: Int) {
curPos = position
requestLayout()
}
})
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
if (childCount == 0) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
return
}
measureChildren(widthMeasureSpec, heightMeasureSpec)
setMeasuredDimension(measuredWidth, getChildAt(curPos).measuredHeight)
}
}
Upvotes: 0
Reputation: 129
I edit cybergen answer for make viewpager to change height depending on selected item The class is the same of cybergen's, but I added a Vector of integers that is all viewpager's child views heights and we can access it when page change to update height
This is the class:
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager.widget.ViewPager;
import java.util.Vector;
public class WrapContentHeightViewPager extends ViewPager {
private Vector<Integer> heights = new Vector<>();
public WrapContentHeightViewPager(@NonNull Context context) {
super(context);
}
public WrapContentHeightViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
for(int i=0;i<getChildCount();i++) {
View view = getChildAt(i);
if (view != null) {
view.measure(widthMeasureSpec, heightMeasureSpec);
heights.add(measureHeight(heightMeasureSpec, view));
}
}
setMeasuredDimension(getMeasuredWidth(), measureHeight(heightMeasureSpec, getChildAt(0)));
}
public int getHeightAt(int position){
return heights.get(position);
}
private int measureHeight(int measureSpec, View view) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
if (view != null) {
result = view.getMeasuredHeight();
}
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
}
Then in your activity add an OnPageChangeListener
WrapContentHeightViewPager viewPager = findViewById(R.id.my_viewpager);
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}
@Override
public void onPageSelected(int position) {
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) viewPager.getLayoutParams();
params.height = viewPager.getHeightAt(position);
viewPager.setLayoutParams(params);
}
@Override
public void onPageScrollStateChanged(int state) {}
});
And here is xml:
<com.example.example.WrapContentHeightViewPager
android:id="@+id/my_viewpager"
android:fillViewport="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
Please correct my English if necessary
Upvotes: 2
Reputation: 55
Give parent layout of ViewPager as NestedScrollView
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:fillViewport="true">
<androidx.viewpager.widget.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</androidx.viewpager.widget.ViewPager>
</androidx.core.widget.NestedScrollView>
Don't forget to set android:fillViewport="true"
This will stretch scrollview and its child's content to fill the viewport.
https://developer.android.com/reference/android/widget/ScrollView.html#attr_android:fillViewport
Upvotes: -1
Reputation: 8450
Improved Daniel López Lacalle answer, rewritten in Kotlin:
class MyViewPager(context: Context, attrs: AttributeSet): ViewPager(context, attrs) {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val zeroHeight = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
val maxHeight = children
.map { it.measure(widthMeasureSpec, zeroHeight); it.measuredHeight }
.max() ?: 0
if (maxHeight > 0) {
val maxHeightSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.EXACTLY)
super.onMeasure(widthMeasureSpec, maxHeightSpec)
return
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
}
Upvotes: 5
Reputation: 53
Measure the height of ViewPager:
public class WrapViewPager extends ViewPager {
View primaryView;
public WrapViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (primaryView != null) {
int height = 0;
for (int i = 0; i < getChildCount(); i++) {
if (primaryView == getChildAt(i)) {
int childHeightSpec = MeasureSpec.makeMeasureSpec(0x1 << 30 - 1, MeasureSpec.AT_MOST);
getChildAt(i).measure(widthMeasureSpec, childHeightSpec);
height = getChildAt(i).getMeasuredHeight();
}
}
setMeasuredDimension(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
}
}
public void setPrimaryView(View view) {
primaryView = view;
}
}
call setPrimaryView(View) :
public class ZGAdapter extends PagerAdapter {
@Override
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
super.setPrimaryItem(container, position, object);
((WrapViewPager)container).setPrimaryView((View)object);
}
}
Upvotes: 1
Reputation: 5585
This ViewPager only resizes to the current visible children (not the biggest of its actual children)
The idea from https://stackoverflow.com/a/56325869/4718406
public class DynamicHeightViewPager extends ViewPager {
public DynamicHeightViewPager (Context context) {
super(context);
initPageChangeListener();
}
public DynamicHeightViewPager (Context context, AttributeSet attrs) {
super(context, attrs);
initPageChangeListener();
}
private void initPageChangeListener() {
addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
requestLayout();
}
});
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//View child = getChildAt(getCurrentItem());
View child = getCurrentView(this);
if (child != null) {
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0,
MeasureSpec.UNSPECIFIED));
int h = child.getMeasuredHeight();
heightMeasureSpec = MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
View getCurrentView(ViewPager viewPager) {
try {
final int currentItem = viewPager.getCurrentItem();
for (int i = 0; i < viewPager.getChildCount(); i++) {
final View child = viewPager.getChildAt(i);
final ViewPager.LayoutParams layoutParams = (ViewPager.LayoutParams)
child.getLayoutParams();
Field f = layoutParams.getClass().getDeclaredField("position");
//NoSuchFieldException
f.setAccessible(true);
int position = (Integer) f.get(layoutParams); //IllegalAccessException
if (!layoutParams.isDecor && currentItem == position) {
return child;
}
}
} catch (NoSuchFieldException e) {
e.fillInStackTrace();
} catch (IllegalArgumentException e) {
e.fillInStackTrace();
} catch (IllegalAccessException e) {
e.fillInStackTrace();
}
return null;
}
}
Upvotes: 2
Reputation: 791
The below code is the only thing worked for me
1. Use this class to declare a HeightWrappingViewPager:
public class HeightWrappingViewPager extends ViewPager {
public HeightWrappingViewPager(Context context) {
super(context);
}
public HeightWrappingViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int mode = MeasureSpec.getMode(heightMeasureSpec);
// Unspecified means that the ViewPager is in a ScrollView WRAP_CONTENT.
// At Most means that the ViewPager is not in a ScrollView WRAP_CONTENT.
if (mode == MeasureSpec.UNSPECIFIED || mode == MeasureSpec.AT_MOST) {
// super has to be called in the beginning so the child views can be initialized.
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int height = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = child.getMeasuredHeight();
if (h > height) height = h;
}
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
}
// super has to be called again so the new specs are treated as exact measurements
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
2. Insert the height wrapping view pager to your xml file:
<com.project.test.HeightWrappingViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.project.test.HeightWrappingViewPager>
3. Declare your view pager:
HeightWrappingViewPager mViewPager;
mViewPager = (HeightWrappingViewPager) itemView.findViewById(R.id.pager);
CustomAdapter adapter = new CustomAdapter(context);
mViewPager.setAdapter(adapter);
mViewPager.measure(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
Upvotes: 2
Reputation: 1468
In my case, I needed a viewpager with a wrap_content for the currently selected element and animation when applying the size. Below you can see my implementation. Can someone come in handy.
package one.xcorp.widget
import android.animation.ValueAnimator
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import one.xcorp.widget.R
import kotlin.properties.Delegates.observable
class ViewPager : android.support.v4.view.ViewPager {
var enableAnimation by observable(false) { _, _, enable ->
if (enable) {
addOnPageChangeListener(onPageChangeListener)
} else {
removeOnPageChangeListener(onPageChangeListener)
}
}
private var animationDuration = 0L
private var animator: ValueAnimator? = null
constructor (context: Context) : super(context) {
init(context, null)
}
constructor (context: Context, attrs: AttributeSet?) : super(context, attrs) {
init(context, attrs)
}
private fun init(context: Context, attrs: AttributeSet?) {
context.theme.obtainStyledAttributes(
attrs,
R.styleable.ViewPager,
0,
0
).apply {
try {
enableAnimation = getBoolean(
R.styleable.ViewPager_enableAnimation,
enableAnimation
)
animationDuration = getInteger(
R.styleable.ViewPager_animationDuration,
resources.getInteger(android.R.integer.config_shortAnimTime)
).toLong()
} finally {
recycle()
}
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
val measuredHeight = if (heightMode == MeasureSpec.EXACTLY) {
MeasureSpec.getSize(heightMeasureSpec)
} else {
val currentViewHeight = findViewByPosition(currentItem)?.also {
measureView(it)
}?.measuredHeight ?: 0
if (heightMode != MeasureSpec.AT_MOST) {
currentViewHeight
} else {
Math.min(
currentViewHeight,
MeasureSpec.getSize(heightMeasureSpec)
)
}
}
super.onMeasure(
widthMeasureSpec,
MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY)
)
}
private fun measureView(view: View) = with(view) {
val horizontalMode: Int
val horizontalSize: Int
when (layoutParams.width) {
MATCH_PARENT -> {
horizontalMode = MeasureSpec.EXACTLY
horizontalSize = [email protected]
}
WRAP_CONTENT -> {
horizontalMode = MeasureSpec.UNSPECIFIED
horizontalSize = 0
}
else -> {
horizontalMode = MeasureSpec.EXACTLY
horizontalSize = layoutParams.width
}
}
val verticalMode: Int
val verticalSize: Int
when (layoutParams.height) {
MATCH_PARENT -> {
verticalMode = MeasureSpec.EXACTLY
verticalSize = [email protected]
}
WRAP_CONTENT -> {
verticalMode = MeasureSpec.UNSPECIFIED
verticalSize = 0
}
else -> {
verticalMode = MeasureSpec.EXACTLY
verticalSize = layoutParams.height
}
}
val horizontalMeasureSpec = MeasureSpec.makeMeasureSpec(horizontalSize, horizontalMode)
val verticalMeasureSpec = MeasureSpec.makeMeasureSpec(verticalSize, verticalMode)
measure(horizontalMeasureSpec, verticalMeasureSpec)
}
private fun findViewByPosition(position: Int): View? {
for (i in 0 until childCount) {
val childView = getChildAt(i)
val childLayoutParams = childView.layoutParams as LayoutParams
val childPosition by lazy {
val field = childLayoutParams.javaClass.getDeclaredField("position")
field.isAccessible = true
field.get(childLayoutParams) as Int
}
if (!childLayoutParams.isDecor && position == childPosition) {
return childView
}
}
return null
}
private fun animateContentHeight(childView: View, fromHeight: Int, toHeight: Int) {
animator?.cancel()
if (fromHeight == toHeight) {
return
}
animator = ValueAnimator.ofInt(fromHeight, toHeight).apply {
addUpdateListener {
measureView(childView)
if (childView.measuredHeight != toHeight) {
animateContentHeight(childView, height, childView.measuredHeight)
} else {
layoutParams.height = animatedValue as Int
requestLayout()
}
}
duration = animationDuration
start()
}
}
private val onPageChangeListener = object : OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {
/* do nothing */
}
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
/* do nothing */
}
override fun onPageSelected(position: Int) {
if (!isAttachedToWindow) {
return
}
findViewByPosition(position)?.let { childView ->
measureView(childView)
animateContentHeight(childView, height, childView.measuredHeight)
}
}
}
}
Add attrs.xml in project:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ViewPager">
<attr name="enableAnimation" format="boolean" />
<attr name="animationDuration" format="integer" />
</declare-styleable>
</resources>
And use:
<one.xcorp.widget.ViewPager
android:id="@+id/wt_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:enableAnimation="true" />
Upvotes: 1
Reputation: 1377
Most of the solutions I see here seem to be doing a double measurement: first measuring the child views, and then calling the super.onMeasure()
I have come up with a custom WrapContentViewPager
that is more efficient, works well with RecyclerView and Fragment
You can check the demo here:
github/ssynhtn/WrapContentViewPager
and the code of the class here: WrapContentViewPager.java
Upvotes: 1
Reputation: 116392
I've found a solution that is a bit like merging of some of the solutions mentioned here.
The idea is to measure the current view of the ViewPager.
Here's the full code:
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewPager.adapter = WrapHeightViewPager.CustomPagerAdapter(this)
}
}
activity_main.xml
<FrameLayout
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"
tools:context=".MainActivity">
<com.android.myapplication.WrapHeightViewPager
android:layout_width="match_parent" android:id="@+id/viewPager"
android:background="#33ff0000"
android:layout_height="wrap_content"/>
</FrameLayout>
view_pager_page.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView android:layout_width="wrap_content" android:layout_height="wrap_content"
android:src="@android:drawable/sym_def_app_icon"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/textView"/>
</LinearLayout>
WrapHeightViewPager.kt
class WrapHeightViewPager : ViewPager {
constructor(context: Context) : super(context) {}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val adapter = adapter as CustomPagerAdapter?
val currentView = adapter?.currentView
if (currentView != null) {
currentView.measure(widthMeasureSpec, heightMeasureSpec)
super.onMeasure(
widthMeasureSpec,
View.MeasureSpec.makeMeasureSpec(currentView.measuredHeight, View.MeasureSpec.EXACTLY)
)
return
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
class CustomPagerAdapter(private val context: Context) : PagerAdapter() {
var currentView: View? = null
override fun instantiateItem(parent: ViewGroup, position: Int): Any {
val inflater = LayoutInflater.from(context)
val view = inflater.inflate(R.layout.view_pager_page, parent, false)
view.textView.text = "item$position"
parent.addView(view)
return view
}
override fun setPrimaryItem(container: ViewGroup, position: Int, obj: Any) {
super.setPrimaryItem(container, position, obj)
currentView = obj as View
}
override fun destroyItem(collection: ViewGroup, position: Int, view: Any) = collection.removeView(view as View)
override fun getCount(): Int = 3
override fun isViewFromObject(view: View, obj: Any) = view === obj
override fun getPageTitle(position: Int): CharSequence? = "item $position"
}
}
If you use RecyclerPagerAdapter library, the way to get the "currentView" is by getting it from the pager-view-holder that you've set:
val item = obj as PagerViewHolder
currentView = item.itemView
Upvotes: -2
Reputation: 1691
In case you need ViewPager that adjust its size to every child, not just to biggest one, I have wrote a piece of code that does it. Note that there is no animation upon that change (not neccessary in my case)
android:minHeight flag is also supported.
public class ChildWrappingAdjustableViewPager extends ViewPager {
List<Integer> childHeights = new ArrayList<>(getChildCount());
int minHeight = 0;
int currentPos = 0;
public ChildWrappingAdjustableViewPager(@NonNull Context context) {
super(context);
setOnPageChangeListener();
}
public ChildWrappingAdjustableViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
obtainMinHeightAttribute(context, attrs);
setOnPageChangeListener();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
childHeights.clear();
//calculate child views
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = child.getMeasuredHeight();
if (h < minHeight) {
h = minHeight;
}
childHeights.add(i, h);
}
if (childHeights.size() - 1 >= currentPos) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeights.get(currentPos), MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
private void obtainMinHeightAttribute(@NonNull Context context, @Nullable AttributeSet attrs) {
int[] heightAttr = new int[]{android.R.attr.minHeight};
TypedArray typedArray = context.obtainStyledAttributes(attrs, heightAttr);
minHeight = typedArray.getDimensionPixelOffset(0, -666);
typedArray.recycle();
}
private void setOnPageChangeListener() {
this.addOnPageChangeListener(new SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
currentPos = position;
ViewGroup.LayoutParams layoutParams = ChildWrappingAdjustableViewPager.this.getLayoutParams();
layoutParams.height = childHeights.get(position);
ChildWrappingAdjustableViewPager.this.setLayoutParams(layoutParams);
ChildWrappingAdjustableViewPager.this.invalidate();
}
});
}
}
Upvotes: 4
Reputation: 826
Using Daniel López Localle answer, I created this class in Kotlin. Hope it save you more time
class DynamicHeightViewPager @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : ViewPager(context, attrs) {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
var heightMeasureSpec = heightMeasureSpec
var height = 0
for (i in 0 until childCount) {
val child = getChildAt(i)
child.measure(widthMeasureSpec, View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED))
val h = child.measuredHeight
if (h > height) height = h
}
if (height != 0) {
heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}}
Upvotes: 25
Reputation: 11
in my case adding clipToPadding
solved the problem.
<android.support.v4.view.ViewPager
...
android:clipToPadding="false"
...
/>
Cheers!
Upvotes: -1
Reputation: 3157
Another more generic solution is to get wrap_content
to just work.
I've extended ViewPager
to override onMeasure()
. The height is wraped around the first child view. This could lead to unexpected results if the child views are not exactly the same height. For that the class can be easily extended to let's say animate to the size of the current view/page. But I didn't need that.
You can use this ViewPager in yout XML layouts just like the original ViewPager:
<view
android:layout_width="match_parent"
android:layout_height="wrap_content"
class="de.cybergen.ui.layout.WrapContentHeightViewPager"
android:id="@+id/wrapContentHeightViewPager"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"/>
Advantage: This approach allows using the ViewPager in any layout including RelativeLayout to overlay other ui elements.
One drawback remains: If you want to use margins, you have to create two nested layouts and give the inner one the desired margins.
Here's the code:
public class WrapContentHeightViewPager extends ViewPager {
/**
* Constructor
*
* @param context the context
*/
public WrapContentHeightViewPager(Context context) {
super(context);
}
/**
* Constructor
*
* @param context the context
* @param attrs the attribute set
*/
public WrapContentHeightViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// find the first child view
View view = getChildAt(0);
if (view != null) {
// measure the first child view with the specified measure spec
view.measure(widthMeasureSpec, heightMeasureSpec);
}
setMeasuredDimension(getMeasuredWidth(), measureHeight(heightMeasureSpec, view));
}
/**
* Determines the height of this view
*
* @param measureSpec A measureSpec packed into an int
* @param view the base view with already measured height
*
* @return The height of the view, honoring constraints from measureSpec
*/
private int measureHeight(int measureSpec, View view) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
// set the height from the base view if available
if (view != null) {
result = view.getMeasuredHeight();
}
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
}
Upvotes: 112
Reputation: 1387
I have a similar (but more complex scenario). I have a dialog, which contains a ViewPager.
One of the child pages is short, with a static height.
Another child page should always be as tall as possible.
Another child page contains a ScrollView, and the page (and thus the entire dialog) should WRAP_CONTENT if the ScrollView contents don't need the full height available to the dialog.
None of the existing answers worked completely for this specific scenario. Hold on- it's a bumpy ride.
void setupView() {
final ViewPager.SimpleOnPageChangeListener pageChangeListener = new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
currentPagePosition = position;
// Update the viewPager height for the current view
/*
Borrowed from https://github.com/rnevet/WCViewPager/blob/master/wcviewpager/src/main/java/nevet/me/wcviewpager/WrapContentViewPager.java
Gather the height of the "decor" views, since this height isn't included
when measuring each page's view height.
*/
int decorHeight = 0;
for (int i = 0; i < viewPager.getChildCount(); i++) {
View child = viewPager.getChildAt(i);
ViewPager.LayoutParams lp = (ViewPager.LayoutParams) child.getLayoutParams();
if (lp != null && lp.isDecor) {
int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;
if (consumeVertical) {
decorHeight += child.getMeasuredHeight();
}
}
}
int newHeight = decorHeight;
switch (position) {
case PAGE_WITH_SHORT_AND_STATIC_CONTENT:
newHeight += measureViewHeight(thePageView1);
break;
case PAGE_TO_FILL_PARENT:
newHeight = ViewGroup.LayoutParams.MATCH_PARENT;
break;
case PAGE_TO_WRAP_CONTENT:
// newHeight = ViewGroup.LayoutParams.WRAP_CONTENT; // Works same as MATCH_PARENT because...reasons...
// newHeight += measureViewHeight(thePageView2); // Doesn't allow scrolling when sideways and height is clipped
/*
Only option that allows the ScrollView content to scroll fully.
Just doing this might be way too tall, especially on tablets.
(Will shrink it down below)
*/
newHeight = ViewGroup.LayoutParams.MATCH_PARENT;
break;
}
// Update the height
ViewGroup.LayoutParams layoutParams = viewPager.getLayoutParams();
layoutParams.height = newHeight;
viewPager.setLayoutParams(layoutParams);
if (position == PAGE_TO_WRAP_CONTENT) {
// This page should wrap content
// Measure height of the scrollview child
View scrollViewChild = ...; // (generally this is a LinearLayout)
int scrollViewChildHeight = scrollViewChild.getHeight(); // full height (even portion which can't be shown)
// ^ doesn't need measureViewHeight() because... reasons...
if (viewPager.getHeight() > scrollViewChildHeight) { // View pager too tall?
// Wrap view pager height down to child height
newHeight = scrollViewChildHeight + decorHeight;
ViewGroup.LayoutParams layoutParams2 = viewPager.getLayoutParams();
layoutParams2.height = newHeight;
viewPager.setLayoutParams(layoutParams2);
}
}
// Bonus goodies :)
// Show or hide the keyboard as appropriate. (Some pages have EditTexts, some don't)
switch (position) {
// This case takes a little bit more aggressive code than usual
if (position needs keyboard shown){
showKeyboardForEditText();
} else if {
hideKeyboard();
}
}
}
};
viewPager.addOnPageChangeListener(pageChangeListener);
viewPager.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// http://stackoverflow.com/a/4406090/4176104
// Do things which require the views to have their height populated here
pageChangeListener.onPageSelected(currentPagePosition); // fix the height of the first page
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
viewPager.getViewTreeObserver().removeOnGlobalLayoutListener(this);
} else {
viewPager.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
}
}
);
}
...
private void showKeyboardForEditText() {
// Make the keyboard appear.
getDialog().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
getDialog().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
inputViewToFocus.requestFocus();
// http://stackoverflow.com/a/5617130/4176104
InputMethodManager inputMethodManager =
(InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.toggleSoftInputFromWindow(
inputViewToFocus.getApplicationWindowToken(),
InputMethodManager.SHOW_IMPLICIT, 0);
}
...
/**
* Hide the keyboard - http://stackoverflow.com/a/8785471
*/
private void hideKeyboard() {
InputMethodManager inputManager = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
inputManager.hideSoftInputFromWindow(inputBibleBookStart.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
}
...
//https://github.com/rnevet/WCViewPager/blob/master/wcviewpager/src/main/java/nevet/me/wcviewpager/WrapContentViewPager.java
private int measureViewHeight(View view) {
view.measure(ViewGroup.getChildMeasureSpec(-1, -1, view.getLayoutParams().width), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
return view.getMeasuredHeight();
}
Much thanks to @Raanan for the code to measure views and measure the decor height. I ran into problems with his library- the animation stuttered, and I think my ScrollView wouldn't scroll when the height of the dialog was short enough to require it.
Upvotes: 0
Reputation: 4775
I have faced this issue in several project already and never had a complete solution. So I created a WrapContentViewPager github project as a in-place replacement for ViewPager.
https://github.com/rnevet/WCViewPager
The solution was inspired by some of the answers here, but improves on:
Updated for support library version 24 which broke previous implementation.
Upvotes: 16
Reputation: 41
I have an version of WrapContentHeightViewPager that was working correctly before API 23 that will resize the parent view's height base on the current child view selected.
After upgrading to API 23, it stopped working. It turns out the old solution was using getChildAt(getCurrentItem())
to get the current child view to measure which is not working. See solution here: https://stackoverflow.com/a/16512217/1265583
Below works with API 23:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int height = 0;
ViewPagerAdapter adapter = (ViewPagerAdapter)getAdapter();
View child = adapter.getItem(getCurrentItem()).getView();
if(child != null) {
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
height = child.getMeasuredHeight();
}
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
Upvotes: 2
Reputation: 10245
When using static content inside the viewpager and you wish no fancy animation you may use the following view pager
public class HeightWrappingViewPager extends ViewPager {
public HeightWrappingViewPager(Context context) {
super(context);
}
public HeightWrappingViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
View firstChild = getChildAt(0);
firstChild.measure(widthMeasureSpec, heightMeasureSpec);
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(firstChild.getMeasuredHeight(), MeasureSpec.EXACTLY));
}
}
Upvotes: 7
Reputation: 21
For people having this problem and coding for Xamarin Android in C#, this might also be a quick solution:
pager.ChildViewAdded += (sender, e) => {
e.Child.Measure ((int)MeasureSpecMode.Unspecified, (int)MeasureSpecMode.Unspecified);
e.Parent.LayoutParameters.Height = e.Child.MeasuredHeight;
};
This is mainly useful if your child views are of the same height. Otherwise, you would be required to store some kind of "minimumHeight" value over all children that you check against, and even then you might not want to have empty spaces visible beneath your smaller child views.
The solution itself is not sufficient for me though, but that is because my child items are listViews and their MeasuredHeight is not calculated correctly, it seems.
Upvotes: 2
Reputation: 13143
From Popcorn time android app's source code I found this solution which dynamically adjusts size of viewpager with nice animation depending on the size of current child.
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);
}
}
Upvotes: 4
Reputation: 4279
If the ViewPager
you're using is a child of a ScrollView
AND has a PagerTitleStrip
child you'll need to use a slight modification of the great answers already provided. For reference my XML looks like this:
<ScrollView
android:id="@+id/match_scroll_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@color/white">
<LinearLayout
android:id="@+id/match_and_graphs_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<view
android:id="@+id/pager"
class="com.printandpixel.lolhistory.util.WrapContentHeightViewPager"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v4.view.PagerTitleStrip
android:id="@+id/pager_title_strip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:background="#33b5e5"
android:paddingBottom="4dp"
android:paddingTop="4dp"
android:textColor="#fff" />
</view>
</LinearLayout>
</ScrollView>
In your onMeasure
you have to ADD the measuredHeight of the PagerTitleStrip
if one is found. Otherwise its height won't be considered into the largest height of all the children even though it takes up additional space.
Hope this helps someone else. Sorry that it's a bit of a hack...
public class WrapContentHeightViewPager extends ViewPager {
public WrapContentHeightViewPager(Context context) {
super(context);
}
public WrapContentHeightViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int pagerTitleStripHeight = 0;
int height = 0;
for(int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = child.getMeasuredHeight();
if (h > height) {
// get the measuredHeight of the tallest fragment
height = h;
}
if (child.getClass() == PagerTitleStrip.class) {
// store the measured height of the pagerTitleStrip if one is found. This will only
// happen if you have a android.support.v4.view.PagerTitleStrip as a direct child
// of this class in your XML.
pagerTitleStripHeight = h;
}
}
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height+pagerTitleStripHeight, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
Upvotes: 1
Reputation: 2879
I just bumped into the same issue. I had a ViewPager and I wanted to display an ad at the button of it. The solution I found was to get the pager into a RelativeView and set it's layout_above to the view id i want to see below it. that worked for me.
here is my layout XML:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:id="@+id/AdLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="vertical" >
</LinearLayout>
<android.support.v4.view.ViewPager
android:id="@+id/mainpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/AdLayout" >
</android.support.v4.view.ViewPager>
</RelativeLayout>
Upvotes: 17
Reputation: 180
public CustomPager (Context context) {
super(context);
}
public CustomPager (Context context, AttributeSet attrs) {
super(context, attrs);
}
int getMeasureExactly(View child, int widthMeasureSpec) {
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int height = child.getMeasuredHeight();
return MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
}
@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);
if (tab == null) {
return;
}
int width = getMeasuredWidth();
if (wrapHeight) {
// Keep the current measured width.
widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
}
Fragment fragment = ((Fragment) getAdapter().instantiateItem(this, getCurrentItem()));
heightMeasureSpec = getMeasureExactly(fragment.getView(), widthMeasureSpec);
//Log.i(Constants.TAG, "item :" + getCurrentItem() + "|height" + heightMeasureSpec);
// super has to be called again so the new specs are treated as
// exact measurements.
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
Upvotes: 4
Reputation: 2856
Nothing of suggested above worked for me. My use case is having 4 custom ViewPagers in ScrollView
. Top of them is measured based on aspect ratio and the rest just has layout_height=wrap_content
. I've tried cybergen , Daniel López Lacalle solutions. None of them work fully for me.
My guess why cybergen doesn't work on page > 1 is because it calculates height of pager based on page 1, that is hidden if you scroll further.
Both cybergen and Daniel López Lacalle suggestions have weird behavior in my case: 2 of 3 are loaded ok and 1 randomly height is 0. Appears that onMeasure
was called before children were populated. So I came up with a mixture of these 2 answers + my own fixes:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
// find the first child view
View view = getChildAt(0);
if (view != null) {
// measure the first child view with the specified measure spec
view.measure(widthMeasureSpec, heightMeasureSpec);
int h = view.getMeasuredHeight();
setMeasuredDimension(getMeasuredWidth(), h);
//do not recalculate height anymore
getLayoutParams().height = h;
}
}
}
Idea is to let ViewPager
calculate children's dimensions and save calculated height of first page in layout params of the ViewPager
. Don't forget to set fragment's layout height to wrap_content
otherwise you can get height=0. I've used this one:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- Childs are populated in fragment -->
</LinearLayout>
Please note that this solution works great if all of your pages have same height. Otherwise you need to recalculate ViewPager
height based on current child active. I don't need it, but if you suggest the solution I would be happy to update the answer.
Upvotes: 4
Reputation: 7592
I based my answer on Daniel López Lacalle and this post http://www.henning.ms/2013/09/09/viewpager-that-simply-dont-measure-up/. The problem with Daniel's answer is that in some cases my children had a height of zero. The solution was to unfortunately measure twice.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int mode = MeasureSpec.getMode(heightMeasureSpec);
// Unspecified means that the ViewPager is in a ScrollView WRAP_CONTENT.
// At Most means that the ViewPager is not in a ScrollView WRAP_CONTENT.
if (mode == MeasureSpec.UNSPECIFIED || mode == MeasureSpec.AT_MOST) {
// super has to be called in the beginning so the child views can be initialized.
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int height = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = child.getMeasuredHeight();
if (h > height) height = h;
}
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
}
// super has to be called again so the new specs are treated as exact measurements
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
This also lets you set a height on the ViewPager if you so want to or just wrap_content.
Upvotes: 63
Reputation: 440
I also ran into this problem, but in my case I had a FragmentPagerAdapter
that was supplying the ViewPager
with its pages. The problem I had was that onMeasure()
of the ViewPager
was called before any of the Fragments
had been created (and therefore could not size itself correctly).
After a bit of trial and error, I found that the finishUpdate()
method of the FragmentPagerAdapter is called after the Fragments
have been initialized (from instantiateItem()
in the FragmentPagerAdapter
), and also after/during the page scrolling. I made a small interface:
public interface AdapterFinishUpdateCallbacks
{
void onFinishUpdate();
}
which I pass into my FragmentPagerAdapter
and call:
@Override
public void finishUpdate(ViewGroup container)
{
super.finishUpdate(container);
if (this.listener != null)
{
this.listener.onFinishUpdate();
}
}
which in turn allows me to call setVariableHeight()
on my CustomViewPager
implementation:
public void setVariableHeight()
{
// super.measure() calls finishUpdate() in adapter, so need this to stop infinite loop
if (!this.isSettingHeight)
{
this.isSettingHeight = true;
int maxChildHeight = 0;
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY);
for (int i = 0; i < getChildCount(); i++)
{
View child = getChildAt(i);
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(ViewGroup.LayoutParams.WRAP_CONTENT, MeasureSpec.UNSPECIFIED));
maxChildHeight = child.getMeasuredHeight() > maxChildHeight ? child.getMeasuredHeight() : maxChildHeight;
}
int height = maxChildHeight + getPaddingTop() + getPaddingBottom();
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
super.measure(widthMeasureSpec, heightMeasureSpec);
requestLayout();
this.isSettingHeight = false;
}
}
I am not sure it is the best approach, would love comments if you think it is good/bad/evil, but it seems to be working pretty well in my implementation :)
Hope this helps someone out there!
EDIT: I forgot to add a requestLayout()
after calling super.measure()
(otherwise it doesn't redraw the view).
I also forgot to add the parent's padding to the final height.
I also dropped keeping the original width/height MeasureSpecs in favor of creating a new one as required. Have updated the code accordingly.
Another problem I had was that it wouldn't size itself correctly in a ScrollView
and found the culprit was measuring the child with MeasureSpec.EXACTLY
instead of MeasureSpec.UNSPECIFIED
. Updated to reflect this.
These changes have all been added to the code. You can check the history to see the old (incorrect) versions if you want.
Upvotes: 9