en Android, Tutoriales

Lista personalizada y optimizada

La clase ListView es un componente muy usado en aplicaciones Android para mostrar listas de datos con cierto formato. Hoy en dia los móviles de gama media-alta pueden hacer funcionar código sin optimizar a una velocidad tolerable. Sin embargo, un gran porcentaje de dispositivos Android aun utilizan procesadores de un solo núcleo y con una memoria bastante limitada. Este post explica una manera sencilla para el desarrollador de optimizar este componente.

Para crear nuestro listview personalizado necesitamos un adapter personalizado tambien. Un Adapter gestiona los datos de la lista y controla como se van mostrando.

Nuestro primer paso es crear la plantilla de la fila de la lista con 3 elementos, un icono, un nombre y un valor numérico, lo llamaremos listview_row.xml

list_view
<?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>

Crearemos una clase muy sencilla para enlazar con los datos de la lista y que nuestro adapter pueda usarla

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

Ahora añadimos el código de nuestro Adapter. Usamos el patrón de diseño ViewHolder para evitar realizar llamadas con demasiada frecuencia a findViewById() durante el desplazamiento por el ListView, mejorando así su rendimiento

public class MyListViewAdapter extends BaseAdapter
{
    static class ViewHolder
    {
        ImageView iconIV;
        TextView nameTV;
        TextView amountTV;
    }
 
    private LayoutInflater _inflater;
    private Context _context;
    private ArrayList _values;
 
    public MyListViewAdapter(Context context, ArrayList 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 un elemento de la lista es cargado convertView es null. Necesitamos hacer un inflate de nuestra plantilla del elemento de la lista, instanciar el viewholder y encontrar el componente visual de la interfaz usando findViewById() para asignarlo al viewholder, y añadirlo como tag del convertView.

Las próximas veces que se cargue, convertView no es nulo y no necesitaremos realizar el inflate de la plantilla y, lo más importante para el rendimiento, no tenemos que volver a realizar otra llamada a findViewById(), podemos acceder a los componentes del elemento de la lista usando el viewholder del convertView.

Ahora podemos modificar la plantilla principal añadiendo un componente listview.

<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 el siguiente código en la clase principal para usar nuestro 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 _inputdata = new ArrayList();
        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 se mostrará nuestra lista optimizada

list_view_full
folder_treeview
Workspace final

Tutorial files

Ayudanos con este blog!

El último año he estado dedicando cada vez más tiempo a la creación de tutoriales, en su mayoria sobre desarrollo de videojuegos. Si crees que estos posts te han ayudado de alguna manera o incluso inspirado, por favor considera ayudarnos a mantener este blog con alguna de estas opciones. Gracias por hacerlo posible!

Escribe un comentario

Comentario