android: listview in listview

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.

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);
}

Answers


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).


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();

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"
    />

@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;
}
}

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.


Need Your Help

Creating a DateTime in a specific Time Zone in c#

c# .net datetime .net-3.5

I'm trying to create a unit test to test the case for when the timezone changes on a machine because it has been incorrectly set and then corrected.

MongoError: Mod on _id not allowed

node.js mongodb express mongoose

There are a few questions out there on this but I can't seem to find a solution that seems to be the accepted approach at the moment.