36

i'm trying to place a listview inside a listviewitem. the inner listview should not be scrollable but take all size it needs to display all it's rows. is there a better way to to this? table, grid, ...? the problem i'm facing right now is that the inner listview doesn't take the space it needs, so it's cut at about the end of the first listitem. if i try to scroll, just the outer listview is scrolling which is exactly what i want.

how_it_should_look_like thanks, my final solution is

LinearLayout layout = (LinearLayout) row.findViewById(R.id.LLBroadcasts);
layout.removeAllViews();

for (Item b : bs.getItems()) {

  View child = _inflater.inflate(R.layout.item_row, null);

  TextView tvTitle = (TextView) child.findViewById(R.id.TVItemTitle);
  tvTitle.setText(b.getTitle());

  TextView tvDesc = (TextView) child.findViewById(R.id.TVItemDescription);
  tvDesc.setText(b.getDescription());
  layout.addView(child);
}
kopi_b
  • 1,453
  • 3
  • 16
  • 20
  • 1
    From your description, you do not want a `ListView` inside a `ListView`, which is good because that will not work well. Instead, you want a vertical `LinearLayout` inside your `ListView` rows, which you would set up no differently than you would the rest of the content of your rows. – CommonsWare Jul 25 '12 at 15:10
  • Maybe you can try to use fragment. Each fragment is a listview, and you can add as many fragment as you want to create a listview of listview! – Kingfisher Phuoc Jul 25 '12 at 15:11
  • @CommonsWare yes, i don't want to use a listview in a listview. but since the inner-'what ever it is' is a list of one to about four items (can be different from row to row) i need some kind of container. – kopi_b Jul 25 '12 at 15:20
  • i will look into fragments. seems link this http://developer.android.com/guide/components/fragments.html#UI could work. – kopi_b Jul 25 '12 at 15:31
  • 1
    @kopi_b LinearLayout *is* a container. – Kevin Coppock Jul 25 '12 at 15:34
  • after a quick test i decided to go with @ChrLipp 's answer – kopi_b Jul 25 '12 at 16:21
  • @CommonsWare Is there any way to do this very efficiently? ie I dont want too many children for the linearlayout and I dont want to use nested weights? – Sreekanth Karumanaghat Sep 25 '17 at 07:49
  • 1
    @SreekanthKarumanaghat: That is impossible to answer in the abstract. – CommonsWare Sep 25 '17 at 13:23
  • @CommonsWare I just read about Epoxy a library by Airbnb... Do you know whether the library is helpful for the purpose? – Sreekanth Karumanaghat Sep 25 '17 at 13:50
  • @SreekanthKarumanaghat: Only if you are using `RecyclerView`. This question is from several years ago, and it is not about `RecyclerView`. – CommonsWare Sep 25 '17 at 16:38
  • @CommonsWare Oops Sorry, Yes I am using a RecyclerView. Thanks for the insight. – Sreekanth Karumanaghat Sep 26 '17 at 05:06

5 Answers5

40

From the Android documentation - Listview: ListView is a view group that displays a list of scrollable items

You do not really want to scroll that inner list view, you want to scroll the outer listview. However I asume that the inner listview may vary on the amount of elements it contains.

Instead of the inner list view you could use a

For the linear layout (some sample code):

// access your linear layout
LinearLayout layout = (LinearLayout)findViewById(R.id.layout);
// load the xml structure of your row
View child = getLayoutInflater().inflate(R.layout.row);
// now fill the row as you would do with listview
//e.g. (TextView) child.findViewById(...
...
// and than add it
layout.addView(child);

You should save the linear layout in a view holder (see View Holder pattern). I think the removeAllViews() is only necessary when the current row has lesser inner rows than the reused one, so I would also save the number of rows in the view holder.

If the maximum number of inner rows is not to high you could also think about caching them in the view holder to avoid the inflate and findByViewId (lets say in an ArrayList).

ChrLipp
  • 15,526
  • 10
  • 75
  • 107
7

I have the same problem in my App but I needed to use a ListView cause it was a shared item and I didn't want to replicate equal components. So.. I just fixed the size of inner ListView programatically to show all rows and.. voila! Problem solved:

ViewGroup.LayoutParams layoutParams = innerListView.getLayoutParams();
layoutParams.height = (int) context.getResources().getDimension(R.dimen.rowheight) * innerListView.getCount();
innerListView.setLayoutParams(layoutParams);
CustomAdapter adapter = new CustomAdapter(context, blabla..);
innerListView.setAdapter(adapter);

rowListView.invalidate();
Ian Holing
  • 119
  • 1
  • 3
7

Maybe somebody will find my solution useful. It is based on @ChrLipp answer and uses LinearLayout.

public class NotScrollableListView extends LinearLayout {

private ListAdapter adapter;
private DataChangeObserver dataChangeObserver;
private Drawable divider;
private int dividerHeight;

private List<View> reusableViews = new ArrayList<>();

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

public NotScrollableListView(Context context, AttributeSet attrs) {
    super(context, attrs);

    setAttributes(attrs);
}

public NotScrollableListView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);

    setAttributes(attrs);
}

public ListAdapter getAdapter() {
    return adapter;
}

public void setAdapter(ListAdapter adapter) {
    if (this.adapter != null && dataChangeObserver != null) {
        this.adapter.unregisterDataSetObserver(dataChangeObserver);
    }

    this.adapter = adapter;
}

@Override
protected void onAttachedToWindow() {
    super.onAttachedToWindow();

    if (adapter != null) {
        dataChangeObserver = new DataChangeObserver();
        adapter.registerDataSetObserver(dataChangeObserver);

        fillContents();
    }
}

@Override
protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();

    if (adapter != null) {
        adapter.unregisterDataSetObserver(dataChangeObserver);
        dataChangeObserver = null;
    }
}

private void fillContents() {

    // clearing contents
    this.removeAllViews();

    final int count = adapter.getCount();   // item count
    final int reusableCount = reusableViews.size(); // count of cached reusable views

    // calculating of divider properties
    ViewGroup.LayoutParams dividerLayoutParams = null;
    if (divider != null && dividerHeight > 0) {
        dividerLayoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dividerHeight);
    }

    // adding items
    for (int i = 0; i < count; i++) {
        // adding item
        View converView = null;
        if (i < reusableCount) {    // we have cached view
            converView = reusableViews.get(i);
        }
        View view = adapter.getView(i, converView, this);

        if (i >= reusableCount) {   // caching view
            reusableViews.add(view);
        }

        addView(view);

        // adding divider
        if (divider != null && dividerHeight > 0) {
            if (i < count - 1) {
                ImageView dividerView = new ImageView(getContext());
                dividerView.setImageDrawable(divider);
                dividerView.setLayoutParams(dividerLayoutParams);
                addView(dividerView);
            }
        }
    }
}

private void setAttributes(AttributeSet attributes) {
    int[] dividerAttrs = new int[]{android.R.attr.divider, android.R.attr.dividerHeight};

    TypedArray a = getContext().obtainStyledAttributes(attributes, dividerAttrs);
    try {
        divider = a.getDrawable(0);
        dividerHeight = a.getDimensionPixelSize(1, 0);
    } finally {
        a.recycle();
    }

    setOrientation(VERTICAL);
}

private class DataChangeObserver extends DataSetObserver {
    @Override
    public void onChanged() {
        super.onChanged();

        fillContents();
    }

    @Override
    public void onInvalidated() {
        super.onInvalidated();

        fillContents();
    }
}
    }

    <com.sample.ui.view.NotScrollableListView
    android:id="@+id/internalList"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:divider="@color/list_divider_color"
    android:dividerHeight="@dimen/list_divider_width"
    />
lobzik
  • 10,974
  • 1
  • 27
  • 32
  • Where/When is reusableViews being populated? – Corey Horn Feb 20 '15 at 18:57
  • @lobzik - I currently using your code and it makes it really easy to have a listview inside another. But I've been trying to implement onItemClick for this inner listview and have it work well within the outer one, and I've had no success at the moment. Any tips on how to accomplish this? – Stralo Jul 17 '15 at 01:07
  • @Stralo you could try to use http://stackoverflow.com/questions/15062365/how-to-work-when-listview-inside-the-scrollview or http://stackoverflow.com/questions/3045872/listview-and-buttons-inside-listview – lobzik Jul 17 '15 at 07:14
  • you are not caching any views. – q126y Jan 11 '17 at 15:33
  • @q126y what do you mean? – lobzik Jan 11 '17 at 20:25
  • You are caaching but not recycling them which is same as not caching. Infact you are also recycling, but For n item n views are needed. – q126y Jan 11 '17 at 21:45
1

I tried making this exact structure (a ListView inside of a ListView) and had the same problem of it only showing the first item of the inner ListView. I fixed it by changing the layout_height of the inner list from match_parent to a set dp.

It seemed to work exactly as I wanted it to.

Milad Faridnia
  • 9,113
  • 13
  • 65
  • 78
Fiberwire
  • 55
  • 1
  • 8
  • doesn't work for me. The innerListView is still unscrollable. When I try to scroll in the InnerListView, I scroll in the parent ListView... – Cocorico Dec 29 '14 at 11:05
  • @Cocorico I should specify that my inner list view is actually inside of a linear layout which is inside of the outer list view – Fiberwire Jan 03 '15 at 22:36
  • after a couple of hours, I find this answer, thanks, and excellent hack – Noor Hossain Jan 27 '21 at 13:13
0

@Try this nested class

this works for scroll listView inside listView Or 2 listviews in same activity

        <com.example.taskgrptaskslistview.NestedListView
            android:id="@+id/listviewTasks"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_marginBottom="2dp"
            android:layout_weight="1"
            android:cacheColorHint="#00000000" >
        </com.example.taskgrptaskslistview.NestedListView>
    </LinearLayout>

NestedListView :

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ListAdapter;
import android.widget.ListView;

public class NestedListView extends ListView implements OnTouchListener, OnScrollListener {

private int listViewTouchAction;
private static final int MAXIMUM_LIST_ITEMS_VIEWABLE = 99;

public NestedListView(Context context, AttributeSet attrs) {
    super(context, attrs);
    listViewTouchAction = -1;
    setOnScrollListener(this);
    setOnTouchListener(this);
}

@Override
public void onScroll(AbsListView view, int firstVisibleItem,
        int visibleItemCount, int totalItemCount) {
    if (getAdapter() != null && getAdapter().getCount() > MAXIMUM_LIST_ITEMS_VIEWABLE) {
        if (listViewTouchAction == MotionEvent.ACTION_MOVE) {
            scrollBy(0, -1);
        }
    }
}

@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}

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

    int newHeight = 0;
    final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    if (heightMode != MeasureSpec.EXACTLY) {
        ListAdapter listAdapter = getAdapter();
        if (listAdapter != null && !listAdapter.isEmpty()) {
            int listPosition = 0;
            for (listPosition = 0; listPosition < listAdapter.getCount()
                    && listPosition < MAXIMUM_LIST_ITEMS_VIEWABLE; listPosition++) {
                View listItem = listAdapter.getView(listPosition, null, this);
                //now it will not throw a NPE if listItem is a ViewGroup instance
                if (listItem instanceof ViewGroup) {
                    listItem.setLayoutParams(new LayoutParams(
                            LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
                }
                listItem.measure(widthMeasureSpec, heightMeasureSpec);
                newHeight += listItem.getMeasuredHeight();
            }
            newHeight += getDividerHeight() * listPosition;
        }
        if ((heightMode == MeasureSpec.AT_MOST) && (newHeight > heightSize)) {
            if (newHeight > heightSize) {
                newHeight = heightSize;
            }
        }
    } else {
        newHeight = getMeasuredHeight();
    }
    setMeasuredDimension(getMeasuredWidth(), newHeight);
}

@Override
public boolean onTouch(View v, MotionEvent event) {
    if (getAdapter() != null && getAdapter().getCount() > MAXIMUM_LIST_ITEMS_VIEWABLE) {
        if (listViewTouchAction == MotionEvent.ACTION_MOVE) {
            scrollBy(0, 1);
        }
    }
    return false;
}
}
Community
  • 1
  • 1
madhu527
  • 4,644
  • 1
  • 28
  • 29