127

I'm trying to display 8 items inside a gridview. Sadly, the gridview height is always too little, so that it only shows the first row, and a little part of the second.

Setting android:layout_height="300dp" makes it work. wrap_content and fill_parent apparently not.

My grid view:

<GridView
    android:id="@+id/myId"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:horizontalSpacing="2dp"
    android:isScrollContainer="false"
    android:numColumns="4"
    android:stretchMode="columnWidth"
    android:verticalSpacing="20dp" />

My items resource:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:minHeight="?android:attr/listPreferredItemHeight" >

    <ImageView
        android:id="@+id/appItemIcon"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:src="@android:drawable/ic_dialog_info"
        android:scaleType="center" />      

    <TextView
        android:id="@+id/appItemText"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="My long application name"
        android:gravity="center_horizontal"
        android:textAppearance="?android:attr/textAppearanceSmall" />

</LinearLayout>

The issue does not seem related to a lack of vertical space.

What can I do ?

Wryday
  • 161
  • 7
tacone
  • 11,371
  • 8
  • 43
  • 60
  • Does this answer your question? [How to have a GridView that adapts its height when items are added](https://stackoverflow.com/questions/6005245/how-to-have-a-gridview-that-adapts-its-height-when-items-are-added) – Shivam Agarwal May 23 '20 at 15:54

6 Answers6

361

After (too much) research, I stumbled on the excellent answer of Neil Traft.

Adapting his work for the GridView has been dead easy.

ExpandableHeightGridView.java:

package com.example;
public class ExpandableHeightGridView extends GridView
{

    boolean expanded = false;

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

    public ExpandableHeightGridView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }

    public ExpandableHeightGridView(Context context, AttributeSet attrs,
            int defStyle)
    {
        super(context, attrs, defStyle);
    }

    public boolean isExpanded()
    {
        return expanded;
    }

    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        // HACK! TAKE THAT ANDROID!
        if (isExpanded())
        {
            // Calculate entire height by providing a very large height hint.
            // View.MEASURED_SIZE_MASK represents the largest height possible.
            int expandSpec = MeasureSpec.makeMeasureSpec(MEASURED_SIZE_MASK,
                    MeasureSpec.AT_MOST);
            super.onMeasure(widthMeasureSpec, expandSpec);

            ViewGroup.LayoutParams params = getLayoutParams();
            params.height = getMeasuredHeight();
        }
        else
        {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }

    public void setExpanded(boolean expanded)
    {
        this.expanded = expanded;
    }
}

Include it in your layout like this:

<com.example.ExpandableHeightGridView
    android:id="@+id/myId"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:horizontalSpacing="2dp"
    android:isScrollContainer="false"
    android:numColumns="4"
    android:stretchMode="columnWidth"
    android:verticalSpacing="20dp" />

Lastly you just need to ask it to expand:

mAppsGrid = (ExpandableHeightGridView) findViewById(R.id.myId);
mAppsGrid.setExpanded(true);
Community
  • 1
  • 1
tacone
  • 11,371
  • 8
  • 43
  • 60
  • I was wondering why my gridview was so short then I found this! works great :) thanks! – AndroidNoob May 01 '12 at 16:11
  • 1
    Great solution! However, the expanded boolean is possibly redundant. You could just check if the layout params height is set to 'wrap_content' instead of checking the expanded boolean. – Charles Harley May 31 '12 at 14:35
  • My previous comment also would require removing the line `params.height = getMeasuredHeight();` – Charles Harley May 31 '12 at 16:36
  • Thank you so much for this. Just after sliced bread you are the next best thing :D – BrettS Apr 17 '13 at 16:06
  • 23
    this solution is not memory efficient and the app will crash if the cells are images. This solution tells the scrollview what's the height of the full gridview so it can go down, but the problem is that to do so it renders everything without using recycling. No m ore than 200 items could work. – Mariano Latorre May 31 '13 at 15:20
  • 8
    @adamp I think there are useful cases for this. If you have a limited number of items to display into a 2d array, using this kind of GridView seems easier than trying to create some kind of custom / dynamic TableLayout, no? – greg7gkb Jun 07 '13 at 20:15
  • 7
    Not works for me, I have put ExpandableHeightGridView under ScrollView, it cuts the last view. – Chirag Nagariya Jul 24 '13 at 07:28
  • @tacone This is great but it stretches to the height of the full GridView, not the current content height. Since my GridView item number are dynamic, the rows vary. – Compaq LE2202x Sep 20 '13 at 07:00
  • 2
    @adamp so if i have a "normal" GridView that works as expected, and I want to display a "preview" of those items (top 6 items) inside a ScrollView of another Activity with a "View All" button that launches the "normal" GridView, then your recommendation is to create a completely new UI using tables that replicates *exactly* what I've already done in the GridView? Or did I misunderstand you? – Scott Roberts Sep 26 '13 at 19:34
  • 2
    @tacone this is great. But now the problem is that my 'GridView' is already expanding but it is still cut in its last row. Was the calculation short for the 'expandSpec'? – Compaq LE2202x Oct 24 '13 at 03:45
  • I am not use if works for fragments which are replace dynamical. Personaly, when i switch to landscape is working but no at portait – mspapant Dec 14 '13 at 23:21
  • Did anyone find the solution for showing the last image of GridView? I have static images from the sd card, so is there any way I can give specific height to GridView? – SkyWalker Feb 07 '14 at 13:01
  • @adamp "Rethink your design" — that's a luxury not everyone can afford. – chakrit Mar 23 '14 at 18:50
  • 2
    @chakrit You say you can't afford to do things the right way, but that only means you'll pay for it in the stream of bugs, edge cases and performance problems from that point forward. Pay now or keep paying later. :) – adamp Mar 25 '14 at 06:00
  • 1
    @adamp You don't get it. Some people do not get a say in the design process. They are simply handled the end result as a PSD or whatever. Please have some compassion for those in the pinch of needing to get something done. Finding out these android limitations in the midst of sprinting to meet a looming deadline is nobody's entertainment. – chakrit Mar 25 '14 at 12:14
  • Of course, if there's time/manpower/customer credibility for it most of us would've gone with the best possible architecture / design. – chakrit Mar 25 '14 at 12:17
  • Working at google he probably forgot how things work in real world. If more than a hundred people stick with a "bad decision", Android Engineers, rather than complaining about it, may try to find the time to put a better one in the framework, I guess :) – tacone Mar 25 '14 at 15:30
  • @chakrit I don't mean the design process in terms of UX design, I mean in terms of software design. If you're attempting this you don't understand what GridView is for. – adamp Mar 25 '14 at 16:32
  • 3
    @tacone There are a number of better solutions for this kind of problem readily available in the framework, support library and other open source code across the web, the easiest of which can be a simple for-loop pulling Views from an adapter or elsewhere and adding them to a GridLayout, (not GridView; GridLayout is also available in the support lib) TableLayout or similar. – adamp Mar 25 '14 at 16:34
  • @adamp This is not nonsensical at all. If you have a complex static (not adapted) view on top of the GridView. That you want to be scrollable. – Benoit May 14 '14 at 08:57
  • 13
    @adamp If this is not good please add your answer with the best solution you can think of – aleb Jul 24 '14 at 22:24
  • @tacone this solution works great for os higher than android 5.0 but when i test the following in os below 5.0 or 4.2 i get the following error `java.lang.ClassCastException: android.widget.RelativeLayout$LayoutParams cannot be cast to android.widget.AbsListView$LayoutParams` – silverFoxA Jun 05 '15 at 17:35
  • @byte very weird as I used to use it with Android 2.x. I have no clue, I'm sorry. – tacone Jun 06 '15 at 17:40
  • well it's an error that is getting displayed, i don't know how fix it – silverFoxA Jun 06 '15 at 18:10
  • 2
    This doesn't work if the grid view has multiple cell layouts. – namanhams Jun 21 '15 at 11:37
  • Its work. but have possible for only default Gridview. – Mani Aug 17 '15 at 04:37
  • This doesn't work if the Grid contains items of varying height. The trick seems to be that the last item determines the row height, but you don't know what that is till it is rendered. :( – Stephen McCormick Oct 19 '15 at 20:21
  • Friend, you have saved my my project release date. Hats off to you. – sid_09 Feb 10 '16 at 10:22
  • Now it solved height cut problem, but can anybody tell me how to scroll the item under some fixed height. – Suchith May 23 '16 at 11:38
35

After using the answer from @tacone and making sure it worked, I decided to try shorting down the code. This is my result. PS: It is the equivalent of having the boolean "expanded" in tacones answer always set to true.

public class StaticGridView extends GridView {

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

    public StaticGridView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public StaticGridView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(MEASURED_SIZE_MASK, MeasureSpec.AT_MOST));
        getLayoutParams().height = getMeasuredHeight();
    }
}
Jacob R
  • 1,243
  • 1
  • 16
  • 23
  • 1
    But wait - this still suffers the problem of memory usage; you're not recycling anymore right? – Fattie Nov 29 '16 at 16:29
6

Another similar approach that worked for me, is to calculate the height for one row and then with static data (you may adapt it to paginate) you can calculate how many rows you have and resize the GridView height easily.

    private void resizeGridView(GridView gridView, int items, int columns) {
    ViewGroup.LayoutParams params = gridView.getLayoutParams();
    int oneRowHeight = gridView.getHeight();
    int rows = (int) (items / columns);
    params.height = oneRowHeight * rows;
    gridView.setLayoutParams(params);
}

Use this code after setting the adapter and when the GridView is drawn or you will get height = 0.

gridView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                if (!gridViewResized) {
                    gridViewResized = true;
                    resizeGridView(gridView, numItems, numColumns);
                }
            }
        });
Pelanes
  • 3,451
  • 1
  • 34
  • 32
  • 1
    This worked well for me - I'm using a bunch of GridViews inside a ListView. Not sure if that's a bad idea yet or not - need to investigate the performance with a large dataset. But regardless, thanks for the code. I think there is an off-by-one error though - I had to use `int rows = items / columns + 1;` – Andrew Nov 27 '14 at 03:12
  • for below android os 5.0 i get this error `java.lang.ClassCastException: android.widget.RelativeLayout$LayoutParams cannot be cast to android.widget.AbsListView$LayoutParams` – silverFoxA Jun 06 '15 at 04:57
  • ViewGroup.LayoutParams params = gridView.getLayoutParams(); throws a NullPointerException – Luke Allison Jun 01 '16 at 11:11
5

Found tacones answer helpfull... so i ported it to C# (Xamarin)

public class ExpandableHeightGridView: GridView
{
    bool _isExpanded = false;

    public ExpandableHeightGridView(Context context) : base(context)
    {            
    }

    public ExpandableHeightGridView(Context context, IAttributeSet attrs) : base(context, attrs)
    {            
    }

    public ExpandableHeightGridView(Context context, IAttributeSet attrs, int defStyle) : base(context, attrs, defStyle)
    {            
    }

    public bool IsExpanded
    {
        get { return _isExpanded; }

        set { _isExpanded = value;  }
    }

    protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        // HACK! TAKE THAT ANDROID!
        if (IsExpanded)
        {
            // Calculate entire height by providing a very large height hint.
            // View.MEASURED_SIZE_MASK represents the largest height possible.
            int expandSpec = MeasureSpec.MakeMeasureSpec( View.MeasuredSizeMask, MeasureSpecMode.AtMost);
            base.OnMeasure(widthMeasureSpec,expandSpec);                

            var layoutParameters = this.LayoutParameters;
            layoutParameters.Height = this.MeasuredHeight;
        }
        else
        {
            base.OnMeasure(widthMeasureSpec,heightMeasureSpec);    
        }
    }
}
spaceMonster
  • 149
  • 3
  • 4
1

Jacob R solution in Kotlin:

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

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        val expandSpec = MeasureSpec.makeMeasureSpec(MEASURED_SIZE_MASK,
            MeasureSpec.AT_MOST)
        super.onMeasure(widthMeasureSpec, expandSpec)
        layoutParams.height = measuredHeight
    }
}

After adding GridView to RecyclerView I got a full-size GridView (all rows are visible), as expected.

CoolMind
  • 26,736
  • 15
  • 188
  • 224
0

Just calculate the height for AT_MOST and set to on measure. Here GridView Scroll will not work so. Need to use Vertical Scroll View explicitly.

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     int heightSpec;

     if (getLayoutParams().height == LayoutParams.WRAP_CONTENT) {

         heightSpec = MeasureSpec.makeMeasureSpec(
                        Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
     }
     else {
         // Any other height should be respected as is.
         heightSpec = heightMeasureSpec;
     }

     super.onMeasure(widthMeasureSpec, heightSpec);
 }
Narkha
  • 1,197
  • 2
  • 12
  • 30
Uday Sravan K
  • 1,305
  • 12
  • 18