205

View's have a minHeight but somehow are lacking a maxHeight:

What I'm trying to achieve is having some items (views) filling up a ScrollView. When there are 1..3 items I want to display them directly. Meaning the ScrollView has the height of either 1, 2 or 3 items.

When there are 4 or more items I want the ScrollView to stop expanding (thus a maxHeight) and start providing scrolling.

However, there is unfortunately no way to set a maxHeight. So I probably have to set my ScrollView height programmatically to either WRAP_CONTENT when there are 1..3 items and set the height to 3*sizeOf(View) when there are 4 or more items.

Can anyone explain why there is no maxHeight provided, when there is already a minHeight?

(BTW: some views, like ImageView have a maxHeight implemented.)

Andrew Whitaker
  • 124,656
  • 32
  • 289
  • 307
znq
  • 44,613
  • 41
  • 116
  • 144
  • I've posted a solution in other thread: http://stackoverflow.com/questions/18425758/how-to-set-a-maximum-height-with-wrap-content-in-android/26758657#26758657 – JMPergar Nov 05 '14 at 13:45
  • 2
    I've created a solution for this at http://chintanrathod.com/maxheightscrollview-in-android-using-android-studio/ – Chintan Rathod Apr 28 '15 at 06:20
  • Google removing maxHeight is annoying and inconvenient. Look at all the stuff you have to do now to get the same result. – Ian Wambai Feb 28 '17 at 13:46

18 Answers18

97

None of these solutions worked for what I needed which was a ScrollView set to wrap_content but having a maxHeight so it would stop expanding after a certain point and start scrolling. I just simply overrode the onMeasure method in ScrollView.

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    heightMeasureSpec = MeasureSpec.makeMeasureSpec(300, MeasureSpec.AT_MOST);
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

This might not work in all situations, but it certainly gives me the results needed for my layout. And it also addresses the comment by madhu.

If some layout present below the scrollview then this trick wont work – madhu Mar 5 at 4:36

whizzle
  • 2,053
  • 17
  • 21
  • 1
    When wouldn't this work ? This seems like a very clean solution. Makes me wonder why they didn't implement it into the xml. – Christophe Smet Jun 02 '14 at 06:03
  • 2
    It probably wouldn't work if you manually set the height to say 240. I didn't bother checking the mode the scrollview is in because I only needed to support 1 specific mode (wrap_content). – whizzle Jun 02 '14 at 23:44
  • There is an edit for this answer that says 'If some layout present below the scrollview then this trick wont work', which isn't true. I have a layout aligned to the bottom of the page and this custom layout has "android:layout_above='@+...' " which always places the custom layout above the bottom layout :) – Machine Tribe Jan 31 '19 at 14:02
76

In order to create a ScrollView or ListView with a maxHeight you just need to create a Transparent LinearLayout around it with a height of what you want the maxHeight to be. You then set the ScrollView's Height to wrap_content. This creates a ScrollView that appears to grow until its height is equal to the parent LinearLayout.

JustinMorris
  • 7,259
  • 3
  • 30
  • 36
  • 3
    This was useful for preventing a dialog with scrolling contents from expanding to the full height of the screen. – Kyle Ivey Dec 05 '13 at 00:36
  • Small suggestion would be to use a FrameLayout instead of a LinearLayout but I doubt it matters. – Kyle Ivey Dec 05 '13 at 00:37
  • 46
    If some layout present below the scrollview then this trick wont work – Sreedhu Madhu Mar 05 '14 at 04:36
  • 3
    can you please provide some code? it didn't work for me, so maybe i'm doing something wrong... – android developer Apr 10 '14 at 08:58
  • won't work if you have buttons below it. it will push the buttons off screen – Li Tian Gong Jan 04 '15 at 23:32
  • This worked for me. I have a bunch of Views in a LinearLayout in a ScrollView, now all in another LineaerLayout, lol. Complicated, but works fine. – Peter Griffin Aug 07 '18 at 00:21
  • how to make the scrollview to adjust in height as per the contents of the text within it? What if the contents of the textview are only consists of 1-2 lines. In that case, there will be too muh of white space shown for the scrollview. – Prajwal Waingankar Jan 20 '20 at 08:14
  • This works. I needed to limit the HorizontalScrollView so I did: <-- here the original content --> – pepan Feb 28 '22 at 11:05
51

This worked for me to make it customizable in xml:

MaxHeightScrollView.java:

public class MaxHeightScrollView extends ScrollView {

private int maxHeight;
private final int defaultHeight = 200;

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

public MaxHeightScrollView(Context context, AttributeSet attrs) {
    super(context, attrs);
    if (!isInEditMode()) {
        init(context, attrs);
    }
}

public MaxHeightScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    if (!isInEditMode()) {
        init(context, attrs);
    }
}

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public MaxHeightScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
    if (!isInEditMode()) {
        init(context, attrs);
    }
}

private void init(Context context, AttributeSet attrs) {
    if (attrs != null) {
        TypedArray styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.MaxHeightScrollView);
        //200 is a defualt value
        maxHeight = styledAttrs.getDimensionPixelSize(R.styleable.MaxHeightScrollView_maxHeight, defaultHeight);

        styledAttrs.recycle();
    }
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST);
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}

attr.xml

<declare-styleable name="MaxHeightScrollView">
        <attr name="maxHeight" format="dimension" />
    </declare-styleable>

example layout

<blah.blah.MaxHeightScrollView android:layout_weight="1"
                app:maxHeight="90dp"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content">
                <EditText android:id="@+id/commentField"
                    android:hint="Say Something"
                    android:background="#FFFFFF"
                    android:paddingLeft="8dp"
                    android:paddingRight="8dp"
                    android:gravity="center_vertical"
                    android:maxLines="500"
                    android:minHeight="36dp"
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content" />
            </blah.blah.MaxHeightScrollView>
SlickDev
  • 607
  • 7
  • 7
37

(I know this does not directly answer the question but might be helpful to others looking for maxHeight functionality)

ConstraintLayout offers maximum height for its children via

app:layout_constraintHeight_max="300dp"
app:layout_constrainedHeight="true" 

or

app:layout_constraintWidth_max="300dp"
app:layout_constrainedWidth="true" 

Sample usage here.

user1506104
  • 6,554
  • 4
  • 71
  • 89
10

I would have commented on whizzle's answer if I could, but thought it useful to note that in order for me to solve this problem in the context of multi-window mode in Android N, I needed to change the code slightly to this:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if(MeasureSpec.getSize(heightMeasureSpec) > maxHeight) {
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST);
    }
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

This allows for the layout to resize to be smaller than the max height, but also prevent it from being larger than the max height. I used this is a layout class that Overrides RelativeLayout and this allowed me to create a custom dialog with a ScrollView as the child of MaxHeightRelativeLayout that does not expand the full height of the screen and also shrinks to fit within the smallest widow size in multi-window for Android N.

J. P. Krause
  • 171
  • 2
  • 7
10

As mentioned above, ConstraintLayout offers maximum height for its children via:

app:layout_constraintHeight_max="300dp"
app:layout_constrainedHeight="true"

Besides, if maximum height for one ConstraintLayout's child is uncertain until App running, there still has a way to make this child automatically adapt a mutable height no matter where it was placed in the vertical chain.

For example, we need to show a bottom dialog with a mutable header TextView, a mutable ScrollView and a mutable footer TextView. The dialog's max height is 320dp,when total height not reach 320dp ScrollView act as wrap_content, when total height exceed ScrollView act as "maxHeight=320dp - header height - footer height".

We can achieve this just through xml layout file:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="320dp">

    <TextView
        android:id="@+id/tv_header"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/black_10"
        android:gravity="center"
        android:padding="10dp"
        app:layout_constraintBottom_toTopOf="@id/scroll_view"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="1"
        app:layout_constraintVertical_chainStyle="packed"
        tools:text="header" />

    <ScrollView
        android:id="@+id/scroll_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/black_30"
        app:layout_constrainedHeight="true"
        app:layout_constraintBottom_toTopOf="@id/tv_footer"
        app:layout_constraintHeight_max="300dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tv_header">

        <LinearLayout
            android:id="@+id/ll_container"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <TextView
                android:id="@+id/tv_sub1"
                android:layout_width="match_parent"
                android:layout_height="160dp"
                android:gravity="center"
                android:textColor="@color/orange_light"
                tools:text="sub1" />

            <TextView
                android:id="@+id/tv_sub2"
                android:layout_width="match_parent"
                android:layout_height="160dp"
                android:gravity="center"
                android:textColor="@color/orange_light"
                tools:text="sub2" />

        </LinearLayout>
    </ScrollView>

    <TextView
        android:id="@+id/tv_footer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/black_50"
        android:gravity="center"
        android:padding="10dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/scroll_view"
        tools:text="footer" />
</android.support.constraint.ConstraintLayout>

Most import code is short:

app:layout_constraintVertical_bias="1"
app:layout_constraintVertical_chainStyle="packed"

app:layout_constrainedHeight="true"

Horizontal maxWidth usage is quite the same.

xionghg
  • 101
  • 1
  • 2
4

There is no way to set maxHeight. But you can set the Height.

To do that you will need to discovery the height of each item of you scrollView. After that just set your scrollView height to numberOfItens * heightOfItem.

To discovery the height of an item do that:

View item = adapter.getView(0, null, scrollView);
item.measure(0, 0);
int heightOfItem = item.getMeasuredHeight();

To set the height do that:

// if the scrollView already has a layoutParams:
scrollView.getLayoutParams().height = heightOfItem * numberOfItens;
// or
// if the layoutParams is null, then create a new one.
scrollView.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, heightOfItem * numberOfItens));
Derzu
  • 7,011
  • 3
  • 57
  • 60
4

Wrap your ScrollView around your a plainLinearLayout with layout_height="max_height", this will do a perfect job. In fact, I have this code in production from last 5 years with zero issues.

<LinearLayout
        android:id="@+id/subsParent"
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:gravity="bottom|center_horizontal"
        android:orientation="vertical">

        <ScrollView
            android:id="@+id/subsScroll"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:layout_marginEnd="15dp"
            android:layout_marginStart="15dp">

            <TextView
                android:id="@+id/subsTv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/longText"
                android:visibility="visible" />

        </ScrollView>
    </LinearLayout>
Sai
  • 15,188
  • 20
  • 81
  • 121
3

My MaxHeightScrollView custom view

public class MaxHeightScrollView extends ScrollView {
    private int maxHeight;

    public MaxHeightScrollView(Context context) {
        this(context, null);
    }

    public MaxHeightScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MaxHeightScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        TypedArray styledAttrs =
                context.obtainStyledAttributes(attrs, R.styleable.MaxHeightScrollView);
        try {
            maxHeight = styledAttrs.getDimensionPixelSize(R.styleable.MaxHeightScrollView_mhs_maxHeight, 0);
        } finally {
            styledAttrs.recycle();
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (maxHeight > 0) {
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

style.xml

<declare-styleable name="MaxHeightScrollView">
    <attr name="mhs_maxHeight" format="dimension" />
</declare-styleable>

Using

<....MaxHeightScrollView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:mhs_maxHeight="100dp"
    >

    ...

</....MaxHeightScrollView>
Linh
  • 57,942
  • 23
  • 262
  • 279
3

I have an answer here:

https://stackoverflow.com/a/29178364/1148784

Just create a new class extending ScrollView and override it's onMeasure method.

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (maxHeight > 0){
            int hSize = MeasureSpec.getSize(heightMeasureSpec);
            int hMode = MeasureSpec.getMode(heightMeasureSpec);

            switch (hMode){
                case MeasureSpec.AT_MOST:
                    heightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.min(hSize, maxHeight), MeasureSpec.AT_MOST);
                    break;
                case MeasureSpec.UNSPECIFIED:
                    heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST);
                    break;
                case MeasureSpec.EXACTLY:
                    heightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.min(hSize, maxHeight), MeasureSpec.EXACTLY);
                    break;
            }
        }

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
babay
  • 4,689
  • 1
  • 26
  • 40
2

In case anyone needs it:

app:layout_constraintHeight_max="300dp"

It forces the View (that is inside a ConstraintLayout) to be 300dp as a max height. For those who want to do this programmatically, it goes like this:

val totalScreenHeight = displayMetrics.heightPixels
val layoutParams: ConstraintLayout.LayoutParams = viewThatIsInsideAConstraintLayout.layoutParams as ConstraintLayout.LayoutParams
        layoutParams.matchConstraintMaxHeight = totalScreenHeight/2
viewThatIsInsideAConstraintLayout.layoutParams = layoutParams
Mateus Nascimento
  • 815
  • 10
  • 11
1

If anyone is considering using exact value for LayoutParams e.g.

setLayoutParams(new LayoutParams(Y, X );

Do remember to take into account the density of the device display otherwise you might get very odd behaviour on different devices. E.g:

Display display = getWindowManager().getDefaultDisplay();
DisplayMetrics d = new DisplayMetrics();
display.getMetrics(d);
setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, (int)(50*d.density) ));
Nicklas Gnejs Eriksson
  • 3,395
  • 2
  • 21
  • 19
1

First get the item height in pixels

View rowItem = adapter.getView(0, null, scrollView);   
rowItem.measure(0, 0);    
int heightOfItem = rowItem.getMeasuredHeight();

then simply

Display display = getWindowManager().getDefaultDisplay();    
DisplayMetrics displayMetrics = new DisplayMetrics();    
display.getMetrics(displayMetrics);    
scrollView.getLayoutParams().height = (int)((heightOfItem * 3)*displayMetrics .density);
1

Have you tried using the layout_weight value? If you set one it to a value greater than 0, it will stretch that view into the remaining space available.

If you had multiple views that needed to be stretched, then the value will become a weight between them.

So if you had two views both set to a layout_weight value of 1, then they would both stretch to fill in the space but they would both stretch to an equal amount of space. If you set one of them to the value of 2, then it would stretch twice as much as the other view.

Some more info here listed under Linear Layout.

Bryan Denny
  • 27,363
  • 32
  • 109
  • 125
  • 1
    The problem with the weights is that they're relative. Different screen sizes, different results, e.g. with a 800px screen I can set a weight to a certain value, so that a specific area ends up with the desired 200px. However, with a 850px screen the specific area ends up to be bigger than 200px, but the enclosed elements are still only 200px. That's why I preferred to have a `maxHeight` which I can set to a specific dip value. (I finally ended up to compute and set the heights programmatically) – znq Nov 03 '10 at 11:20
1

i think u can set the heiht at runtime for 1 item just scrollView.setHeight(200px), for 2 items scrollView.setheight(400px) for 3 or more scrollView.setHeight(600px)

Yayo28
  • 342
  • 3
  • 7
  • 9
    You don't want to set the Dimension in 'px'. It is very likely that you dont get the result you want on multiple Devices. http://developer.android.com/guide/topics/resources/more-resources.html#Dimension – Hernd DerBeld Mar 05 '12 at 10:48
  • @HerndDerBeld that's how it works in code. if you wish , you can always convert from DP to pixels quite easily : float pixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources() .getDisplayMetrics()); – android developer Apr 10 '14 at 07:33
1

As we know devices running android can have different screen sizes. As we further know views should adjust dynamically and become the space which is appropriate.

If you set a max height you maybe force the view not to get enough space or take to less space. I know that sometimes it seems to be practically to set a max height. But if the resolution will ever change dramatically, and it will!, then the view, which has a max height, will look not appropriate.

i think there is no proper way to exactly do the layout you want. i would recommend you to think over your layout using layout managers and relative mechanisms. i don't know what you're trying to achieve but it sounds a little strange for me that a list should only show three items and then the user has to scroll.

btw. minHeight is not guaranteed (and maybe shouldn't exist either). it can have some benefit to force items to be visible while other relative items get smaller.

Diego Frehner
  • 2,396
  • 1
  • 28
  • 35
  • 7
    This is wrong. By this logic, you wouldn't be able to say android:layout_width="500dp", either. – Glenn Maynard May 14 '13 at 20:09
  • I don't see what should be wrong neither your logic interpretation. If you set layout_width (which sometimes is ok), it's not guaranteed you get that. 500dp maybe look on one device appropriate, on another not. Layout managers ftw! Maybe you could explain more bcs the down vote bothers :) – Diego Frehner May 15 '13 at 08:04
  • 3
    It's just as valid to say android:maxWidth="500dp" as to say android:layout_width="500dp". They both force specific dimensions on the view. – Glenn Maynard May 15 '13 at 16:08
0

if you guys want to make a non-overflow scrollview or listview, just but it on a RelativeLayout with a topview and bottomview on top and bottom for it:

<ScrollView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_above="@+id/topview"
    android:layout_below="@+id/bottomview" >
0

I used a custom ScrollView made in Kotlin which uses maxHeight. Example of use:

<com.antena3.atresplayer.tv.ui.widget.ScrollViewWithMaxHeight
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:maxHeight="100dp">

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

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
</com.antena3.atresplayer.tv.ui.widget.ScrollViewWithMaxHeight>

Here is the code of ScrollViewWidthMaxHeight:

import android.content.Context
import android.util.AttributeSet
import android.widget.ScrollView
import timber.log.Timber

class ScrollViewWithMaxHeight @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : ScrollView(context, attrs, defStyleAttr) {

companion object {
    var WITHOUT_MAX_HEIGHT_VALUE = -1
}

private var maxHeight = WITHOUT_MAX_HEIGHT_VALUE

init {
    val a = context.obtainStyledAttributes(
        attrs, R.styleable.ScrollViewWithMaxHeight,
        defStyleAttr, 0
    )
    try {
        maxHeight = a.getDimension(
            R.styleable.ScrollViewWithMaxHeight_android_maxHeight,
            WITHOUT_MAX_HEIGHT_VALUE.toFloat()
        ).toInt()
    } finally {
        a.recycle()
    }
}

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    var heightMeasure = heightMeasureSpec
    try {
        var heightSize = MeasureSpec.getSize(heightMeasureSpec)
        if (maxHeight != WITHOUT_MAX_HEIGHT_VALUE) {
            heightSize = maxHeight
            heightMeasure = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.AT_MOST)
        } else {
            heightMeasure = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.UNSPECIFIED)
        }
        layoutParams.height = heightSize
    } catch (e: Exception) {
        Timber.e(e, "Error forcing height")
    } finally {
        super.onMeasure(widthMeasureSpec, heightMeasure)
    }
}

fun setMaxHeight(maxHeight: Int) {
    this.maxHeight = maxHeight
}
}

which needs also this declaration in values/attrs.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ScrollViewWithMaxHeight">
        <attr name="android:maxHeight" />
    </declare-styleable>
</resources>
Mario Velasco
  • 3,336
  • 3
  • 33
  • 50