356

I am trying to determine the best way to have a single ListView that contains different layouts for each row. I know how to create a custom row + custom array adapter to support a custom row for the entire list view, but how can I implement many different row styles in the ListView?

w.donahue
  • 10,790
  • 13
  • 56
  • 78
  • 1
    update:Demo for Multiple row layout using android's RecyclerView http://code2concept.blogspot.in/2015/10/android-multiple-row-layout-using.html – Nitesh Tiwari Oct 17 '15 at 06:00

6 Answers6

412

Since you know how many types of layout you would have - it's possible to use those methods.

getViewTypeCount() - this methods returns information how many types of rows do you have in your list

getItemViewType(int position) - returns information which layout type you should use based on position

Then you inflate layout only if it's null and determine type using getItemViewType.

Look at this tutorial for further information.

To achieve some optimizations in structure that you've described in comment I would suggest:

  • Storing views in object called ViewHolder. It would increase speed because you won't have to call findViewById() every time in getView method. See List14 in API demos.
  • Create one generic layout that will conform all combinations of properties and hide some elements if current position doesn't have it.

I hope that will help you. If you could provide some XML stub with your data structure and information how exactly you want to map it into row, I would be able to give you more precise advise. By pixel.

Atul Bhardwaj
  • 6,647
  • 5
  • 45
  • 63
Cristian
  • 198,401
  • 62
  • 356
  • 264
  • Thank blog was very nice, but I added checkbox. I had a problem in that will check first item and scroll the List. Weirdly anonymous items where get checked. Can you provide solution for that. Thanks – Lalit Poptani Oct 08 '11 at 13:53
  • 2
    sorry for digging this up again, but you would actually recommend having a single large layout file and control visibility of parts of it, instead of have separate layout files, which get inflated respectively using getItemViewType? – Makibo May 09 '13 at 03:08
  • 2
    You can do that too. Although I still prefer the way exposed here. It makes clearer what you want to achieve. – Cristian May 09 '13 at 21:24
  • But in multiple layout strategy we can not user view holder properly because setTag can only contain one view holder and whenever row layout switches again we need to call findViewById() . Which makes the listview very low performance. I personal experienced it what is your suggestion on it ? – Piyush Agarwal Apr 18 '14 at 18:41
  • @pyus13 you can declare as many views as you want in a single view holder and it isn't necessary to use every view declared in the view holder. If a sample code is needed, please do let me know, I'll post it. – Ahmad Ali Nasir Jun 25 '14 at 10:57
  • I could do with a code example if possible. Just came across this trying to do the same thing. – RED_ Jul 01 '14 at 08:48
  • Cool!! Thank you very much. Do you think you could help me with this question : http://stackoverflow.com/questions/25549679/how-can-i-split-a-long-single-sqliteopenhelper-into-serveral-classes-one-for-e – eddy Aug 28 '14 at 14:01
  • what if I you wanna add many type of items not just Layouts in list view? Like add objectA with layoutA and object B with LayoutB ? – Mahdi Apr 28 '15 at 12:46
  • @Cristian link to the tutorial is dead :( is it still available elsewhere? – drmrbrewer May 05 '21 at 19:39
63

I know how to create a custom row + custom array adapter to support a custom row for the entire list view. But how can one listview support many different row styles?

You already know the basics. You just need to get your custom adapter to return a different layout/view based on the row/cursor information being provided.

A ListView can support multiple row styles because it derives from AdapterView:

An AdapterView is a view whose children are determined by an Adapter.

If you look at the Adapter, you'll see methods that account for using row-specific views:

abstract int getViewTypeCount()
// Returns the number of types of Views that will be created ...

abstract int getItemViewType(int position)
// Get the type of View that will be created ...

abstract View getView(int position, View convertView, ViewGroup parent)
// Get a View that displays the data ...

The latter two methods provide the position so you can use that to determine the type of view you should use for that row.


Of course, you generally don't use AdapterView and Adapter directly, but rather use or derive from one of their subclasses. The subclasses of Adapter may add additional functionality that change how to get custom layouts for different rows. Since the view used for a given row is driven by the adapter, the trick is to get the adapter to return the desired view for a given row. How to do this differs depending on the specific adapter.

For example, to use ArrayAdapter,

  • override getView() to inflate, populate, and return the desired view for the given position. The getView() method includes an opportunity reuse views via the convertView parameter.

But to use derivatives of CursorAdapter,

  • override newView() to inflate, populate, and return the desired view for the current cursor state (i.e. the current "row") [you also need to override bindView so that widget can reuse views]

However, to use SimpleCursorAdapter,

  • define a SimpleCursorAdapter.ViewBinder with a setViewValue() method to inflate, populate, and return the desired view for a given row (current cursor state) and data "column". The method can define just the "special" views and defer to SimpleCursorAdapter's standard behavior for the "normal" bindings.

Look up the specific examples/tutorials for the kind of adapter you end up using.

Bert F
  • 85,407
  • 12
  • 106
  • 123
  • Any thoughts on which of these adapter types is best for flexible implementation of adapter? I'm adding another question on the board for this. – Androider Feb 13 '11 at 05:36
  • 1
    @Androider - "best for flexible" is very open-ended - there's no end-all, be-all class that will satisfy every need; it's a rich hierarchy - it comes down to whether there's functionality in a subclass that's useful to your purpose. If so, start with that subclass; if not, move up to `BaseAdapter`. Deriving from BaseAdapter would be the most "flexible", but would be the worst at code reuse and maturity since it doesn't take advantage of the knowledge and maturity already put into the other adapters. `BaseAdapter` is there for non-standard contexts where the other adapters don't fit. – Bert F Feb 13 '11 at 08:19
  • 4
    +1 for the fine distinction between `CursorAdapter` and `SimpleCursorAdapter`. – Giulio Piancastelli Nov 02 '11 at 15:06
  • But in multiple layout strategy we can not user view holder properly because setTag can only contain one view holder and whenever row layout switches again we need to call findViewById() . Which makes the listview very low performance. I personal experienced it what is your suggestion on it ? – Piyush Agarwal Apr 18 '14 at 18:41
  • 1
    also note that if you override `ArrayAdapter`, it doesn't matter what layout you give the constructor, as long as `getView()` inflates and returns the right type of layout – woojoo666 Aug 21 '14 at 20:34
  • Do we have any such example? using holders? – Darpan Jun 11 '15 at 12:30
  • 1
    It should be noted that `getViewTypeCount()` is only triggered once everytime you call `ListView.setAdapter()`, not for every `Adapter.notifyDataSetChanged()`. – hidro Jun 21 '15 at 08:04
44

Take a look in the code below.

First, we create custom layouts. In this case, four types.

even.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:background="#ff500000"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text"
        android:textColor="@android:color/white"
        android:layout_width="match_parent"
        android:layout_gravity="center"
        android:textSize="24sp"
        android:layout_height="wrap_content" />

 </LinearLayout>

odd.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:background="#ff001f50"
    android:gravity="right"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text"
        android:textColor="@android:color/white"
        android:layout_width="wrap_content"
        android:layout_gravity="center"
        android:textSize="28sp"
        android:layout_height="wrap_content"  />

 </LinearLayout>

white.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:background="#ffffffff"
    android:gravity="right"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text"
        android:textColor="@android:color/black"
        android:layout_width="wrap_content"
        android:layout_gravity="center"
        android:textSize="28sp"
        android:layout_height="wrap_content"   />

 </LinearLayout>

black.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:background="#ff000000"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text"
        android:textColor="@android:color/white"
        android:layout_width="wrap_content"
        android:layout_gravity="center"
        android:textSize="33sp"
        android:layout_height="wrap_content"   />

 </LinearLayout>

Then, we create the listview item. In our case, with a string and a type.

public class ListViewItem {
        private String text;
        private int type;

        public ListViewItem(String text, int type) {
            this.text = text;
            this.type = type;
        }

        public String getText() {
            return text;
        }

        public void setText(String text) {
            this.text = text;
        }

        public int getType() {
            return type;
        }

        public void setType(int type) {
            this.type = type;
        }

    }

After that, we create a view holder. It's strongly recommended because Android OS keeps the layout reference to reuse your item when it disappears and appears back on the screen. If you don't use this approach, every single time that your item appears on the screen Android OS will create a new one and causing your app to leak memory.

public class ViewHolder {
        TextView text;

        public ViewHolder(TextView text) {
            this.text = text;
        }

        public TextView getText() {
            return text;
        }

        public void setText(TextView text) {
            this.text = text;
        }

    }

Finally, we create our custom adapter overriding getViewTypeCount() and getItemViewType(int position).

public class CustomAdapter extends ArrayAdapter {

        public static final int TYPE_ODD = 0;
        public static final int TYPE_EVEN = 1;
        public static final int TYPE_WHITE = 2;
        public static final int TYPE_BLACK = 3;

        private ListViewItem[] objects;

        @Override
        public int getViewTypeCount() {
            return 4;
        }

        @Override
        public int getItemViewType(int position) {
            return objects[position].getType();
        }

        public CustomAdapter(Context context, int resource, ListViewItem[] objects) {
            super(context, resource, objects);
            this.objects = objects;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {

            ViewHolder viewHolder = null;
            ListViewItem listViewItem = objects[position];
            int listViewItemType = getItemViewType(position);


            if (convertView == null) {

                if (listViewItemType == TYPE_EVEN) {
                    convertView = LayoutInflater.from(getContext()).inflate(R.layout.type_even, null);
                } else if (listViewItemType == TYPE_ODD) {
                    convertView = LayoutInflater.from(getContext()).inflate(R.layout.type_odd, null);
                } else if (listViewItemType == TYPE_WHITE) {
                    convertView = LayoutInflater.from(getContext()).inflate(R.layout.type_white, null);
                } else {
                    convertView = LayoutInflater.from(getContext()).inflate(R.layout.type_black, null);
                }

                TextView textView = (TextView) convertView.findViewById(R.id.text);
                viewHolder = new ViewHolder(textView);

                convertView.setTag(viewHolder);

            } else {
                viewHolder = (ViewHolder) convertView.getTag();
            }

            viewHolder.getText().setText(listViewItem.getText());

            return convertView;
        }

    }

And our activity is something like this:

private ListView listView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main); // here, you can create a single layout with a listview

        listView = (ListView) findViewById(R.id.listview);

        final ListViewItem[] items = new ListViewItem[40];

        for (int i = 0; i < items.length; i++) {
            if (i == 4) {
                items[i] = new ListViewItem("White " + i, CustomAdapter.TYPE_WHITE);
            } else if (i == 9) {
                items[i] = new ListViewItem("Black " + i, CustomAdapter.TYPE_BLACK);
            } else if (i % 2 == 0) {
                items[i] = new ListViewItem("EVEN " + i, CustomAdapter.TYPE_EVEN);
            } else {
                items[i] = new ListViewItem("ODD " + i, CustomAdapter.TYPE_ODD);
            }
        }

        CustomAdapter customAdapter = new CustomAdapter(this, R.id.text, items);
        listView.setAdapter(customAdapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView adapterView, View view, int i, long l) {
                Toast.makeText(getBaseContext(), items[i].getText(), Toast.LENGTH_SHORT).show();
            }
        });

    }
}

now create a listview inside mainactivity.xml like this

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context="com.example.shivnandan.gygy.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

    </android.support.design.widget.AppBarLayout>

    <include layout="@layout/content_main" />

    <ListView
        android:layout_width="match_parent"

        android:layout_height="match_parent"

        android:id="@+id/listView"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true"


        android:layout_marginTop="100dp" />

</android.support.design.widget.CoordinatorLayout>
shiv
  • 459
  • 4
  • 10
14

In your custom array adapter, you override the getView() method, as you presumably familiar with. Then all you have to do is use a switch statement or an if statement to return a certain custom View depending on the position argument passed to the getView method. Android is clever in that it will only give you a convertView of the appropriate type for your position/row; you do not need to check it is of the correct type. You can help Android with this by overriding the getItemViewType() and getViewTypeCount() methods appropriately.

Jems
  • 11,560
  • 1
  • 29
  • 35
4

If we need to show different type of view in list-view then its good to use getViewTypeCount() and getItemViewType() in adapter instead of toggling a view VIEW.GONE and VIEW.VISIBLE can be very expensive task inside getView() which will affect the list scroll.

Please check this one for use of getViewTypeCount() and getItemViewType() in Adapter.

Link : the-use-of-getviewtypecount

kyogs
  • 6,766
  • 1
  • 34
  • 50
1

ListView was intended for simple use cases like the same static view for all row items.
Since you have to create ViewHolders and make significant use of getItemViewType(), and dynamically show different row item layout xml's, you should try doing that using the RecyclerView, which is available in Android API 22. It offers better support and structure for multiple view types.

Check out this tutorial on how to use the RecyclerView to do what you are looking for.

Phileo99
  • 5,581
  • 2
  • 46
  • 54