Calculate the size of a list view or how to tell it to fully expand
Asked Answered
S

12

37

I am currently trying to use a ListView inside of a ScrollView. I know from what I've read that this is looked down upon, but I'm trying to get the ListView to expand completely by showing all of its rows so there is no need for it to scroll. I've been struggling, however, with how to tell the ListView to completely expand to show all of its rows since it needs a defined height. How do I calculate the height of a fully expanded ListView before it is drawn?

This problem mainly stems from the fact that you can't put a scrollable view inside of another scrollable view. I am okay with the fact that the ListView won't be able to scroll as long as I can make it expand to show all of its rows. I cannot do this, however, without being able to give it a defined height, which it seems I would need to calculate.

My full layout is too big for the "physical" screen and needs to scroll in order to show the rest of the list and buttons at the bottom. I'm trying to get across that the "virtual" screen is too big to fit on one screen even without the ListView there.

Sever answered 22/2, 2010 at 17:18 Comment(3)
What does "expand completely" mean?Debauchee
By expand completely, I mean show all of its rows. The question is edited appropriately to explain that.Sever
It might help if you posted a screenshot or sketch of what you are trying to accomplish. I am having a hard time understanding why the default behavior of "expand as much as you can, then scroll if there are too many items to fit" is not sufficient.Laurelaureano
K
57

Well, thanks to Rudy, his suggestions was very helpful. Here is how it can be implemented.

1) Create a new class that extends ListView:

package com.example.android.views;

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.widget.ListView;

public class ExpandedListView extends ListView {

    private android.view.ViewGroup.LayoutParams params;
    private int old_count = 0;

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

    @Override
    protected void onDraw(Canvas canvas) {
        if (getCount() != old_count) {
            old_count = getCount();
            params = getLayoutParams();
            params.height = getCount() * (old_count > 0 ? getChildAt(0).getHeight() : 0);
            setLayoutParams(params);
        }

        super.onDraw(canvas);
    }

}

2) ... and finally add the new view to your xml layout file:

<com.example.android.views.ExpandedListView
    android:id="@+id/list"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:scrollbars="none"
    android:padding="0px"
    />
Katsuyama answered 19/4, 2011 at 17:15 Comment(2)
I havent tried it, but from what I see, it only works if all elements have the same height as getChildAt(0) (or if by luck the exact average size of the views is the than getChildAt(0).getHeight())Hallucinosis
does anyone know how I would use the above in Xamarin.Android?Nadda
G
14

The correct way to calculate the height is precisely:

int height = 0;
for (int i = 0; i < listView.getChildCount(); i++) {
    height += listView.getChildAt(i).getMeasuredHeight();
    height += listView.getDividerHeight();
}
Gopher answered 28/6, 2013 at 10:31 Comment(1)
This doesn't mean that some children may have different weights. It always returns the same height.Cloche
C
11

Attention! Correct way to calculate height is not

params.height = getCount() * (old_count > 0 ? getChildAt(0).getHeight() : 0);

We have to add height of each (minus one) divider heights.

if(oldCount > 0 && getCount() > 0)
    params.height = getCount()
                  * (getChildAt(0).getHeight() + getDividerHeight())
                  - getDividerHeight();
else
    params.height = 0;
Canvasback answered 30/9, 2012 at 12:35 Comment(2)
Note, that this solution is still assuming that all items are of the same height and there is no footer nor headerCuthbert
listView.getChildAt(0) is nullWaterish
M
4
@Override
public void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
    if (this.isExpanded) {
        final int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, expandSpec);
        final ViewGroup.LayoutParams params = this.getLayoutParams();
        params.height = this.getMeasuredHeight();
    } else {
       super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}
Marris answered 15/7, 2013 at 6:10 Comment(0)
A
3

If you want a ListView which won't scroll couldn't you just use a LinearLayout?

Andria answered 22/2, 2010 at 19:15 Comment(4)
I'd like to keep the native android look of a list without having to roll my own.Sever
do you mean the highlight states? If so you can use android.R.drawable.list_selector_background to get the same kind of behavior I believe.Andria
Yes that, as well as the default header and footer functionality, list gradient seperators, and use of the adapter constructs. I could end up trying to make a clone of the ListView by copying all of the built in android styles, but I would like to avoid this if possible.Sever
I was fighting this as well. Your suggestion to just use a LinearLayout hit me like a hammer to the forehead. Works just right and live is simpler now. Thanks!Ramsay
L
2

You should not place your ListView in a ScrollView. The ListView is already scrollable.

The ListView should expand fully by default, if it is the only thing in the layout.

If it is not the only thing in the layout, and you want the ListView to expand to take up all available space, set the layout_weight on the ListView to 1, where all other layout_weight=0.

<LinearLayout android:layout_width="fill_parent" 
             android:layout_height="fill_parent" orientation="vertical">
  <TextView id="@+id/title" 
             android:layout_width="wrap_content" 
            android:layout_height="wrap_content"/>
  <ListView id="@+id/listView" 
            android:layout_width="fill_parent" 
            android:layout_height="0dp" 
            android:layout_weight="1"/>
</LinearLayout>

Edit: The ListView is really designed to be scrollable...the screen layout you have in your screenshot doesn't really seem like the "android way".

However, if your really want to circumvent that, you could try inflating one of the rows, get its minHeight, and multiply that by the number of items in the ListView adapter.

LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.label_value_row, null, false);
int height = adapter.getCount() * view.getMinHeight();
Laurelaureano answered 22/2, 2010 at 19:4 Comment(5)
The ListView, however, will not be scrollable if all its rows are showing. My problem is that I need to show more views in the layout than will fit on just one screen. I don't want the ListView to just expand to the screen, I want it to expand to show all of its rows.Sever
The ListView will automatically scroll if there is more content than will fit on the screen. From the ListView API, a ListView is "A view that shows items in a vertically scrolling list." The scrolling is built in. Try it!Laurelaureano
Even without the ListView, the other views on the screen are too tall to fit so I need the overlaying view to be a ScrollView. I want the ListView to size itself to be tall enough to show all of its rows. I can accomplish this by giving it a set height in its LayoutParams but the list is dynamic so I am trying to figure out a way to calculate this height. See the edit I provided to the question.Sever
So this is true but the data in my ListView is dynamic from a database and the actual height of each row will be variable based on how the text wraps. Would there be anyway after setting the height of the ListView to this estimation to figure out how much less or more the actual height is? And by the way, thank you for your help so far, much appreciated.Sever
You could compare the number of visible children to the number of children in the adapter, that seems like it could get really messy though. Honestly, it sounds like you might be better off with the LinearLayout jqpubliq suggested. The ListView is really designed to scroll...it lazily loads its content, so only the size of the visible components will be determined until you start scrolling. Its not that hard to replicate the click behavior of the ListView.Laurelaureano
D
1

I've been researching on how to do this, and although the question has already been answered, I'd like to provide a solution that I think is better:

Looking at the ListView's source code you can see the following inside the onMeasure(...) code:

if (heightMode == MeasureSpec.AT_MOST) {
     // TODO: after first layout we should maybe start at the first visible position, not 0
     heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
}

So simply call the listView's measure and pass MeasureSpec.AT_MOST for the height instead of the usual MeasureSpec.UNSPECIFIED.

I do the following to measure a listview and place it inside a popup-window:

    int widthMeasureSpec = MeasureSpec.makeMeasureSpec(displayMetrics.widthPixels, MeasureSpec.EXACTLY);
    int heightMeasureSpec = MeasureSpec.makeMeasureSpec(displayMetrics.heightPixels, MeasureSpec.AT_MOST);
    mCatalogView.measure(widthMeasureSpec, heightMeasureSpec);

    mPopup.setWidth(mCatalogView.getMeasuredWidth());
    mPopup.setHeight(mCatalogView.getMeasuredHeight());
Doubles answered 15/3, 2012 at 9:51 Comment(0)
H
1

I took the function from djunod's answer (the function in static class)

calculate listview size

and change ondraw method as follow. it works after delete operations successfully.

here is my final class

class ExpandedListView extends ListView {

private android.view.ViewGroup.LayoutParams params;
private int old_count = 0;

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

@Override
protected void onDraw(Canvas canvas) {

    if (getCount() != old_count) {
        params = getLayoutParams();
        old_count = getCount();
        int totalHeight = 0;
        for (int i = 0; i < getCount(); i++) {
            this.measure(0, 0);
            totalHeight += getMeasuredHeight();
        }

        params = getLayoutParams();
        params.height = totalHeight + (getDividerHeight() * (getCount() - 1));
        setLayoutParams(params);
    }

    super.onDraw(canvas);
}

}

now my listview expands after row insert without scrolling. and after row delete this code calculates the correct size for listview. i don't know how it is working. but it is working. BR

Haley answered 6/10, 2013 at 14:19 Comment(1)
"i don't know how it is working. but it is working" LOLBraggadocio
N
1

use this will be worked:

public class ListViewEx extends ListView {

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

/**
 * @param context
 * @param attrs
 */
public ListViewEx(Context context, AttributeSet attrs) {
    super(context, attrs);
}

/**
 * 设置不滚动
 */
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,MeasureSpec.AT_MOST);
    super.onMeasure(widthMeasureSpec, expandSpec);
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    return ev.getAction()==MotionEvent.ACTION_MOVE||super.dispatchTouchEvent(ev);
}

}

Neoclassic answered 23/9, 2016 at 2:23 Comment(0)
S
0

I ended up overriding onDraw() of the ListView and calculating the average height of the visible rows in the view then estimating the actual height of the list based on how many items are in the adapter. This estimating happens a couple of times until all of the rows are visible.

Sever answered 1/3, 2010 at 16:39 Comment(0)
K
0

I know it's late and there is already and answer but if you really want to do this there is an easy way:

Create the list view Item with a defined height

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="100dp"
    android:orientation="horizontal" >

    <ImageView
        android:id="@+id/lvItemSurpriseMeImgRestaurant"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_marginLeft="5dp"
        android:contentDescription="@string/restaurantImageDescriptionColon" />
</LinearLayout>

Now we know that the Item is 100dp high, and if we know the number of items then it is very simple

set the ListView height literally to the value of NoOfItems*Height + 10dp extra

It will never change since all unites are in dp.

If you have more than 1 item type then calculate it with basic math and set it

        <ListView
            android:id="@+id/menuListView"
            android:layout_width="match_parent"
            android:layout_height="210dp"
            android:layout_weight="1.0"
            tools:ignore="NestedScrolling"
            tools:listitem="@layout/menu_item" >
        </ListView>
Kippy answered 20/4, 2014 at 12:10 Comment(3)
Doesn't handle rows being different heights, such as when long text wraps.Morganite
I recommend you try using elipsize (not sure of the spelling) but it will not let text be longer than the limit u set using max lines or so, search for it as I am from mobile phoneKippy
ellipses don't work in my case. Have to calculate size programmatically to expand the listviewHelbonnas
T
0

If you have items with different height you need to use this method:

public void setListViewHeightBasedOnChildren(ListView listView) {

    ListAdapter listAdapter = listView.getAdapter();
    if (listAdapter != null) {

        int numberOfItems = listAdapter.getCount();

        // Get total height of all items.
        int totalItemsHeight = 0;
        for (int itemPos = 0; itemPos < numberOfItems; itemPos++) {
            View item = listAdapter.getView(itemPos, null, listView);
            float px = 300 * (listView.getResources().getDisplayMetrics().density);
            item.measure(
                    View.MeasureSpec.makeMeasureSpec((int)px, View.MeasureSpec.AT_MOST),
                    View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
            int height = item.getMeasuredHeight();
            totalItemsHeight += height;
        }

        // Get total height of all item dividers.
        int totalDividersHeight = listView.getDividerHeight() *
                (numberOfItems - 1);

        // Set list height.
        ViewGroup.LayoutParams params = listView.getLayoutParams();
        params.height = totalItemsHeight + totalDividersHeight;
        listView.setLayoutParams(params);
        listView.requestLayout();
    }
}

it work just fine with any ListView with any items

Thacher answered 7/7, 2016 at 10:51 Comment(2)
this does not work & freezes the UI if your list contains around 1000 items. I even tried removing the for loop & getView just for 0th item. Even then, bindView method gets called 1000 times.. :(Waterish
well, what did you expected when UI is trying to render 1000 elements at one time?Thacher

© 2022 - 2024 — McMap. All rights reserved.