1

I've been trying to search around for a while now on SO and Google however can't quite get this right. I have a ListView with a customadapter associated with it. Within the layout for this ListView, I have another ListView (also with its own customadapter), which only has between 0 and 4 items in it.

The height of the row in the outer/first listview needs to expand and contract based on how many rows are in the inner/nested listview, however this is not happening. It will only ever, when left to its own devices, show one row.

I've been pretty much randomly assigning various LinearLayouts and ListViews with layout_height:0 and layout_weight:1, while varying values of match_parent vs wrap_content, but to no avail.

It's a simple fix, I know, but can't work out which element in the layout needs which parameters. I don't want to do this programatically either. It should only need the right combination of layout attributes.

Thanks in advance.

Layout file for the OUTER ListView

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

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="5dp">

        <TextView
            android:id="@+id/tvDate"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    </LinearLayout>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:background="#ffdddd">

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

            android:background="#ddffdd"
            >

            <ListView
                android:id="@+id/lvInnerListView"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                />

        </LinearLayout>

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#eeeeee">

            <TextView
                android:id="@+id/tvSomeOtherText"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />

        </LinearLayout>


    </LinearLayout>

</LinearLayout>

Layout file for the INNER/NESTED Listview

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:padding="5dp"
    android:background="#ddddff">

    <TextView
        android:id="@+id/tvText1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/tvText2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/tvText3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/tvText4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/tvText5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

Layout file for the fragment itself

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

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:text="Title" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Get Values"
        android:id="@+id/btnGetValues"
        android:layout_marginTop="24dp"
        android:layout_marginBottom="24dp"
        android:padding="12dp"
        android:textColor="#ffffff"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Response from server"
        android:id="@+id/tvResponse"/>

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

        <ListView
            android:id="@+id/lvOuterListView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            />
    </LinearLayout>

</LinearLayout>
Jammo
  • 1,838
  • 4
  • 25
  • 39

1 Answers1

4

The problem you facing is lying deeper, than in simple adjusting the parameters. Check this topic: How can I put a ListView into a ScrollView without it collapsing? - it has explanation behind it.

First and the most important, to have nested scrollable view with within scrollable view (ListView within ListView, RecyclerView within RecyclerView, ScrollView within ScrollView, etc.) is a bad practice.

What people recommend to do is to dynamically add and remove elements to the LinearLayout dynamically instead. It solves your problem automatically. I.e. LinearLayout would always be "expanded".


Here's how you can implement it (I'm gonna use RecyclerView instead of ListView):

enter image description here
(yep, I know it looks horrible without design, use the code just as an example)

  1. You can find the source code of this sample here;

  2. In the Activity, I'm initializing the "outer" RecyclerView(and the only, actually):

    Xml:

    <android.support.v7.widget.RecyclerView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
    

    Code:

    RecyclerView recyclerView = (RecyclerView)findViewById(R.id.recyclerView);
    recyclerView.setLayoutManager(new LinearLayoutManager(this));
    recyclerView.setAdapter(new CustomAdapter());
    
  3. Then I create a CustomAdapter:

    public final class CustomAdapter extends RecyclerView.Adapter {
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            return new CustomViewHolder(new CustomView(parent.getContext()));
        }
    
        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            ((CustomView)holder.itemView).bind(String.format("Position %s", position), position % 4);
        }
    
        @Override
        public int getItemCount() {
            return 42;
        }
    
        class CustomViewHolder extends RecyclerView.ViewHolder {
            public CustomViewHolder(View itemView) {
                super(itemView);
            }
        }
    }
    
  4. Next step, to create CustomView(the one, which is going to contain what used to be your inner ListView, in my case - just a LinearLayout) for the adapter:

    Xml:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:id="@+id/titleTextView"
            android:layout_weight="1"
            android:background="#F0F000"
            android:layout_width="0dp"
            android:layout_height="wrap_content" />
        <LinearLayout
            android:id="@+id/containerLinearLayout"
            android:layout_weight="1"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:orientation="vertical"/>
    </LinearLayout>
    

    Code:

    public final class CustomView extends FrameLayout {
        public CustomView(Context context) {
            super(context);
            LayoutInflater.from(context).inflate(R.layout.view_item, this);
        }
    
        public void bind(String name, int value) {
            TextView textView = (TextView)getRootView().findViewById(R.id.titleTextView);
            LinearLayout containerLinearLayout = (LinearLayout)getRootView().findViewById(R.id.containerLinearLayout);
    
            textView.setText(name);
            containerLinearLayout.removeAllViews();
    
            for (int i = 0; i < value; i++) {
                containerLinearLayout.addView(new NestedCustomView(getContext()));
            }
        }
    }
    
  5. Last step to have an entry to be added into the nested LinearLayout(old inner ListView):

    Xml:

    <merge xmlns:android="http://schemas.android.com/apk/res/android">
        <TextView
            android:text="I'm a nested View"
            android:background="#aa0a0a"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="8dp"/>
    </merge>
    

    Code:

    public final class NestedCustomView extends FrameLayout {
        public NestedCustomView(Context context) {
            super(context);
            LayoutInflater.from(context).inflate(R.layout.view_nested_item, this);
        }
    } 
    

That's it. Again, this HelloWorld is available here.

I hope, it helps.

Community
  • 1
  • 1
Konstantin Loginov
  • 15,802
  • 5
  • 58
  • 95
  • Thanks. Very thorough answer. I put in your code sections and replaced my adapters with yours, and it seems to be working just fine, showing similar to your screenshot. Now I'll deconstruct your code and try and get my custom adapters working with my data instead. Appreciate the effort in your post/code. +1 – Jammo Feb 05 '16 at 10:12
  • Konstantin, on your app, when you're scrolling down the list, does the main window of the app (title "My Application") and the status bar also move? Apart from that minor issue, this is working for me very nicely, and happy to accept this answer. Thanks – Jammo Feb 05 '16 at 11:22
  • @Jammo well, in this HelloWorld it remains on it's original place, but you can collapse it or do some animations around it with `CoordinatorLayout` & `CollapsingToolbarLayout` (for example, like this: http://stackoverflow.com/questions/34978250/coordinatorlayout-with-recyclerview-and-collapsing-header/34980319#34980319) – Konstantin Loginov Feb 05 '16 at 11:26
  • Konstantin, yeah same problems here. the recyclerview (or `containerLinearLayout`) seems to be stretching to a bigger height than the screen allows, meaning that the whole page (including title bar and pager tabs) are also scrolling. if I limit the view to 100dp then this stops the occurrence. It seems to be expanding by the same height as the Pager Tabs, meaning perhaps the `view` is looking at the upper most view for calculations, instead of just its parent view? Not really sure – Jammo Feb 08 '16 at 10:53
  • @Jammo Have you replaced inner ListView with LinearLayout? – Konstantin Loginov Feb 08 '16 at 13:07
  • Konstantin, yes the structure (view types) are the same as yours. If I limit my data to one "outer" row, which then has 4 "inner" row records, and then set the recyclerViewOuter height to 200dp, I know that the view has a finite height and not scrolling off the end of the page. I set the background color to #00ff00 to visually confirm this. However, I still get the same behaviour where a swipe up (scroll down) will make my pager buttons and title bar scrolll upwards. Could it be something to do with the `recyclerView.setLayoutManager(new LinearLayoutManager(activity));` from within a fragment – Jammo Feb 08 '16 at 14:00
  • Fixed it. Turns out it was `android.support.v7.widget.Toolbar` XML setting which had `app:layout_scrollFlags="scroll|enterAlways"` set. It must have been conflicting with the scroll behaviour of the Recycler. I changed the value to `snap` for now, and that seems to have fixed the problem. The recycler content itself isn't expanding full length, as it turns out, but this is a separate issue – Jammo Feb 08 '16 at 16:35