Tutorial: Optimized listview

29 November 2014 Written by 
Published in Android

Basic tutorial to create an optimized custom listview using Android.

English

To create our custom listview we need a custom adapter too. An adapter manage the list data and control how it is displayed.

Our first step is to create de row layout with 3 elements, one icon, one name and one numeric value, we will name it listview_row.xml

listview row

 

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="fill_parent"
                android:layout_height="?android:attr/listPreferredItemHeight"
                android:padding="6dip">

    <ImageView
    android:id="@+id/icon"
    android:layout_width="wrap_content"
    android:layout_height="fill_parent"
    android:layout_alignParentBottom="true"
    android:layout_alignParentTop="true"
    android:layout_marginRight="6dip"
    android:contentDescription="iconrow"
    android:src="@drawable/ic_launcher" />

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_alignParentBottom="true"
        android:layout_alignParentTop="true"
        android:gravity="center_vertical"
        android:layout_toRightOf="@id/icon">
        <TextView
            android:id="@+id/firstLine"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:gravity="center_vertical"
            android:text="Item name"
            android:layout_weight="1"
            android:textSize="16sp" />

        <TextView
            android:id="@+id/secondLine"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:ellipsize="marquee"
            android:singleLine="true"
            android:text="Amount"
            android:gravity="end"
            android:textSize="12sp" />
    </LinearLayout>
</RelativeLayout>

We create a simple class to bind our list data and our adapter could use it

public class MyListViewRow
{
    private long _id;
    private String _name;
    private int _amount;

    public MyListViewRow(long id, String name,int amount)
    {
        _id = id;
        _name = name;
        _amount = amount;
    }

    public long get_id(){ return _id;}
    public String get_name(){return _name;}
    public int get_amount(){return _amount;}
}

Now we coding the adapter class. We use the ViewHolder design pattern to avoid frequent calls of findViewById() during ListView scrolling improving the performance.

public class MyListViewAdapter extends BaseAdapter
{
    static class ViewHolder
    {
        ImageView iconIV;
        TextView nameTV;
        TextView amountTV;
    }

    private LayoutInflater _inflater;
    private Context _context;
    private ArrayList<MyListViewRow> _values;

    public MyListViewAdapter(Context context, ArrayList<MyListViewRow> values)
    {
        _context = context;
        _values = values;

        _inflater = LayoutInflater.from(_context);
    }

    @Override
    public int getCount() {
        return _values.size();
    }

    @Override
    public Object getItem(int position) {
        return _values.get(position);
    }

    @Override
    public long getItemId(int position) {
        return _values.get(position).get_id();
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent)
    {
        ViewHolder holder;
        if (convertView == null)
        {
            convertView = _inflater.inflate(R.layout.listview_row, null);
            holder = new ViewHolder();
            holder.nameTV = (TextView) convertView.findViewById(R.id.firstLine);
            holder.amountTV = (TextView) convertView.findViewById(R.id.secondLine);
            holder.iconIV = (ImageView) convertView.findViewById(R.id.icon);
            convertView.setTag(holder);
        }
        else
        {
            holder = (ViewHolder)convertView.getTag();
        }
        holder.nameTV.setText(_values.get(position).get_name());
        holder.amountTV.setText(Integer.toString(_values.get(position).get_amount()));

        return convertView;
    }
}

The first time that row is loaded, converView is null. We will have to inflate our listview row layout, instantiate the viewholder and find the components using findViewById() to assign it to hte viewholder, and set as tag of converView.

The next times it was loaded, converView is not null and we dont have to inflate the view row and, the best important thing to the performance, we dont have to call findViewById() again, we can now access to the components of the row using the viewholder of converView.

Now we can modify the main layout adding a listview component

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">

    <ListView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/mainLV"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"/>

</RelativeLayout>

And we add some come in the main class to use the listview

private ListView _mainListView;
    private MyListViewAdapter _mainListViewAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //Input list data
        ArrayList<MyListViewRow> _inputdata = new ArrayList<MyListViewRow>();
        MyListViewRow rowData;
        for (int i = 0; i < 20; ++i)
        {
            rowData = new MyListViewRow(i*3,"Element "+ Integer.toString(i), i*2);
            _inputdata.add(rowData);
        }

        //find list view
        _mainListView = (ListView) findViewById(R.id.mainLV);

        //create an adapter to listview and assign it
        _mainListViewAdapter = new MyListViewAdapter(_mainListView.getContext(), _inputdata);
        _mainListView.setAdapter(_mainListViewAdapter);

        //click listener example
        _mainListView.setOnItemClickListener(new AdapterView.OnItemClickListener()
        {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int position, long id)
            {
                MyListViewRow clickRowData = (MyListViewRow) _mainListView.getItemAtPosition(position);
                Toast.makeText(getApplicationContext(), Long.toString(clickRowData.get_id()) + "   " + clickRowData.get_name() + "   " + Integer.toString(clickRowData.get_amount()) , Toast.LENGTH_LONG).show();
            }
        });
    }

Finally it will show our optimized list

listview final

 

 

Final workspace tree:

listview workspace

download
Download

Spanish

Para crear nuestra propia listview optimizada necesitamos crear un apadter primero. Un adapter gestiona los datos que van a rellenar la lista y controla como se muestran en pantalla.

Nuestro primer paso será crear el layout que usara el adapter para representar graficamente la fila de cada elemento de la lista, lo llamaremos listview_row.xml. Creamos una fila sencilla con 3 elmentos: un icono, un nombre y un valor numérico

 

listview row

 

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="fill_parent"
                android:layout_height="?android:attr/listPreferredItemHeight"
                android:padding="6dip">

    <ImageView
    android:id="@+id/icon"
    android:layout_width="wrap_content"
    android:layout_height="fill_parent"
    android:layout_alignParentBottom="true"
    android:layout_alignParentTop="true"
    android:layout_marginRight="6dip"
    android:contentDescription="iconrow"
    android:src="@drawable/ic_launcher" />

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_alignParentBottom="true"
        android:layout_alignParentTop="true"
        android:gravity="center_vertical"
        android:layout_toRightOf="@id/icon">
        <TextView
            android:id="@+id/firstLine"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:gravity="center_vertical"
            android:text="Item name"
            android:layout_weight="1"
            android:textSize="16sp" />

        <TextView
            android:id="@+id/secondLine"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:ellipsize="marquee"
            android:singleLine="true"
            android:text="Amount"
            android:gravity="end"
            android:textSize="12sp" />
    </LinearLayout>
</RelativeLayout>

 

Ahora creamos una clase para almacenar los datos de la lista y para que también nuestro apadter sea capaz de usarla para contener los valores y acceder a los atributos de cada fila.

public class MyListViewRow
{
    private long _id;
    private String _name;
    private int _amount;

    public MyListViewRow(long id, String name,int amount)
    {
        _id = id;
        _name = name;
        _amount = amount;
    }

    public long get_id(){ return _id;}
    public String get_name(){return _name;}
    public int get_amount(){return _amount;}
}

 

Ya podemos empezar con nuestra clase adapter. Usaremos el patron de diseño ViewHolder para evitar las numerosas llamadas a findViewById() que se producirian durante el scrolling y mejorar asi el rendimiento. El resto del adapter es bastante facil de implementar ya que podemos aprovechar el contenedor de datos de entrada para hacer los override necesarios. 

public class MyListViewAdapter extends BaseAdapter
{
    static class ViewHolder
    {
        ImageView iconIV;
        TextView nameTV;
        TextView amountTV;
    }

    private LayoutInflater _inflater;
    private Context _context;
    private ArrayList<MyListViewRow> _values;

    public MyListViewAdapter(Context context, ArrayList<MyListViewRow> values)
    {
        _context = context;
        _values = values;

        _inflater = LayoutInflater.from(_context);
    }

    @Override
    public int getCount() {
        return _values.size();
    }

    @Override
    public Object getItem(int position) {
        return _values.get(position);
    }

    @Override
    public long getItemId(int position) {
        return _values.get(position).get_id();
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent)
    {
        ViewHolder holder;
        if (convertView == null)
        {
            convertView = _inflater.inflate(R.layout.listview_row, null);
            holder = new ViewHolder();
            holder.nameTV = (TextView) convertView.findViewById(R.id.firstLine);
            holder.amountTV = (TextView) convertView.findViewById(R.id.secondLine);
            holder.iconIV = (ImageView) convertView.findViewById(R.id.icon);
            convertView.setTag(holder);
        }
        else
        {
            holder = (ViewHolder)convertView.getTag();
        }
        holder.nameTV.setText(_values.get(position).get_name());
        holder.amountTV.setText(Integer.toString(_values.get(position).get_amount()));

        return convertView;
    }
}

 

La primera vez que la fila es cargada, converView es nulo. Tendremos que cargar(inflate) nuestro layout de la fila, instanciar el ViewHolder,buscar las componentes de nuestro layout de la fila usando findViewById(), asignarselas, y finalmente añadir el ViewHolder al converView.

Las proximas veces que la fila es cargada (cuando vuelve a entrar en la parte visible de la lista al hacer scroll) converView no es null y no necesitamos hacer el inflate del layout de la fila y lo mas importante para el rendimiento, no necesitamos volver a llamar a findViewById(),  podemos acceder a las componentes de la View de la fila usando el ViewHolder de convertView.

Ahora podemos modificar el layout principal y añadir un ListView al que luego añadiremos nuestro Adapter por código

 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">

    <ListView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/mainLV"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"/>

</RelativeLayout>

 

Y añadimos algo de codigo a la clase principal para manejar el ListView

private ListView _mainListView;
    private MyListViewAdapter _mainListViewAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //Input list data
        ArrayList<MyListViewRow> _inputdata = new ArrayList<MyListViewRow>();
        MyListViewRow rowData;
        for (int i = 0; i < 20; ++i)
        {
            rowData = new MyListViewRow(i*3,"Element "+ Integer.toString(i), i*2);
            _inputdata.add(rowData);
        }

        //find list view
        _mainListView = (ListView) findViewById(R.id.mainLV);

        //create an adapter to listview and assign it
        _mainListViewAdapter = new MyListViewAdapter(_mainListView.getContext(), _inputdata);
        _mainListView.setAdapter(_mainListViewAdapter);

        //click listener example
        _mainListView.setOnItemClickListener(new AdapterView.OnItemClickListener()
        {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int position, long id)
            {
                MyListViewRow clickRowData = (MyListViewRow) _mainListView.getItemAtPosition(position);
                Toast.makeText(getApplicationContext(), Long.toString(clickRowData.get_id()) + "   " + clickRowData.get_name() + "   " + Integer.toString(clickRowData.get_amount()) , Toast.LENGTH_LONG).show();
            }
        });
    }

 

Finalmente nuestra lista optimizada se mostraria asi:

listview final

 

El workspace final deberia tener esta pinta:

listview workspace

 

download
Download

 

Read 1447 times Last modified on Sunday, 30 November 2014 19:45
More in this category: Tutorial: Push to exit »
Login to post comments

We use cookies to ensure that we give you the best experience on our website. If you continue without changing your settings, we'll assume that you are happy to receive all cookies. View policy