Android Basic Training Course: Getting Fancy with ListView

    A normal ListView is one of the most important widgets in Android, simply because it is used frequently. No matter when you select a contact to make a call, an e-mail to forward, or an electronic book reader, the ListView widget are built to cater for all purposes above. Definitely it will more cool if they are not merely simple texts.

The first basic knowledge

    A classic ListView in Android is a list of simple text - dense but not attractive. Basically, we passed to ListView a string of words in an array and Android uses a simple layout is available to turn them into a list.
    However, we can have a list that is created by rows of icons, symbols and texts, check boxes and text, or anything that we want. It should be noted here is to provide enough data for the adapter and adapter help create a set of View objects fuller for each line.
    For example, suppose we wanted to have a ListView with items created by a symbol, followed by a text line. We can create a layout for a line that looks like this:
    This layout uses a LinearLayout to set the contents of a row, with a logo on the left and the content (with a custom font) on the right.
    However, by default, Android does not know we want to use this layout to your ListView. To create a link between them, we need to give your Adapter ID of layout custom resources mentioned above, so put this code in your Activity:
package devexchanges.info.basiclistview;

import android.annotation.SuppressLint;
import android.app.ListActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;

public class ListViewDemo extends ListActivity {

    private TextView selection;
    private static final String[] items = { "lorem", "ipsum", "dolor", "sit",
            "amet", "consectetuer", "adipiscing", "elit", "morbi", "vel",
            "ligula", "vitae", "arcu", "aliquet", "mollis", "etiam", "vel",
            "erat", "placerat", "ante", "porttitor", "sodales", "pellentesque",
            "augue", "purus" };

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setContentView(R.layout.activity_list_demo);
        setListAdapter(new ArrayAdapter<>(this, R.layout.row, R.id.label, items));
        selection = (TextView) findViewById(R.id.selection);
    }

    @SuppressLint("SetTextI18n")
    public void onListItemClick(ListView parent, View v, int position, long id) {
        selection.setText(items[position] + " Clicked!");
    }
}
    Note: Reference to a layout (row.xml), we use R.layout as a prefix before the name of the XML file layout (R.layout.row). Code for this file in this sample project:
    Output when running this project:

Dynamic ListView

    As seen in the previous section, how to provide an alternative layout to use for the treatment of lines in the simplest case is very beautiful. However, what happens if we want to change icon based on the row data? For example, suppose we want to use a symbol for short words and other symbols for words long. In this case, we will need to extend our ListView adapter class from ArrayAdapter to customizing our logic. Specifically, we need to override the method getView():
package devexchanges.info.basiclistview;

import android.app.ListActivity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;

public class DynamicDemo extends ListActivity {
    TextView selection;
    private static final String[] items = {"lorem", "ipsum", "dolor", "sit",
            "amet", "consectetuer", "adipiscing", "elit", "morbi", "vel",
            "ligula", "vitae", "arcu", "aliquet", "mollis", "etiam", "vel",
            "erat", "placerat", "ante", "porttitor", "sodales", "pellentesque",
            "augue", "purus"};

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setContentView(R.layout.activity_list_demo);
        setListAdapter(new IconicAdapter());
        selection = (TextView) findViewById(R.id.selection);
    }

    public void onListItemClick(ListView parent, View v, int position, long id) {
        selection.setText(items[position]);
    }

    private class IconicAdapter extends ArrayAdapter<String> {
        public IconicAdapter() {
            super(DynamicDemo.this, R.layout.row, R.id.label, items);
        }

        public View getView(int position, View convertView, ViewGroup parent) {
            View row = super.getView(position, convertView, parent);
            ImageView icon = (ImageView) row.findViewById(R.id.icon);
            if (items[position].length() > 4) {
                icon.setImageResource(R.mipmap.ic_delete);
            } else {
                icon.setImageResource(R.mipmap.ic_check);
            }
            return (row);
        }
    }
}
    Our IconicAdapter - a nested class in the Activity within 2 methods. Firstly, it has a constructor with a parameter passed to ArrayAdapter similar to what we did in the previous example. Secondly, it has methods getView() is executed - do 2 works:
- It uses the method overrided from getView() super class, returns to us an object View of row - fitted by ArrayAdapter. Specifically, our words are put into the TextView by ArrayAdapter that do it in a normal way.
- It finds the ImageView object and apply to it the logic made to know which symbols should be used, reference to one of the two resources are R.mipmap.ic_check and R.mipmap.ic_delete) .
    With above row.xml file, this is output:

Recycling Views

    The execution getView() method in the previous example worked fine, but was not effective. Each time a user scrolls down or roll up, we had to create a new heap objects to contain the row view newly appear. It's a bad idea, wasteful memory and battery power and has some more risks. So, we will think about reuse the view.
    As you see in the code above, getView() has an important View parameter called convertView. Sometimes, convertView may be null. In such cases, we need to create an object for each of the first View (through convert XML structure file to View) - like we do in the previous example. However, if convertView not null, then obviously it is one of the objects View was we created earlier! It would appear primarily when users scroll ListView. When the new line appears, Android will recycle lines have been rolled over to prevent from the create this line. See this example:
public class ListViewDemo extends ListActivity {
    private TextView selection;
    private static final String[] items = {"lorem", "ipsum", "dolor", "sit",
            "amet", "consectetuer", "adipiscing", "elit", "morbi", "vel",
            "ligula", "vitae", "arcu", "aliquet", "mollis", "etiam", "vel",
            "erat", "placerat", "ante", "porttitor", "sodales", "pellentesque",
            "augue", "purus"};

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setContentView(R.layout.main);
        setListAdapter(new IconicAdapter());
        selection = (TextView) findViewById(R.id.selection);
    }

    public void onListItemClick(ListView parent, View v, int position, long id) {
        selection.setText(items[position]);
    }

    class IconicAdapter extends ArrayAdapter<String> {
        IconicAdapter() {
            super(ListViewDemo.this, R.layout.row, items);
        }

        public View getView(int position, View convertView, ViewGroup parent) {
            View row = convertView;
            if (row == null) {
                LayoutInflater inflater = getLayoutInflater();
                row = inflater.inflate(R.layout.row, parent, false);
            }
            TextView label = (TextView) row.findViewById(R.id.label);
            label.setText(items[position]);
            ImageView icon = (ImageView) row.findViewById(R.id.icon);
            if (items[position].length() > 4) {
                icon.setImageResource(R.drawable.delete);
            } else {
                icon.setImageResource(R.drawable.ok);
            }
            return row;
        }
    }
}
    Here we check to see if the convertView is null and, if so we then inflate our row—but if it is not null, we just reuse it. The work to fill in the contents (icon image, text) is the same in either case. The advantage is that if the convertView is not null, we avoid the potentially expensive inflation step.

Use ViewHolder Pattern

    The most optimized design for ListView is using a ViewHolder class to recycling the views. All View objects have getTag() and setTag() methods. These allow you to associate an arbitrary object with the widget. That holder pattern uses that “tag” to hold an object that, in turn, holds each of the child widgets of interest. By attaching that holder to the row View, every time we use the row, we already have access to the child widgets we care about, without having to call findViewById() again.
    And you can see most of my post related ListView use this feature, a ViewHolder class can be very simple with a constructor which declared items through call findViewbyId() in it:
class ViewHolder {
 private class ViewHolder {

        private ImageView icon;
        private TextView text;

        public ViewHolder(View v) {
            icon = (ImageView)v.findViewById(R.id.icon);
            text = (TextView)v.findViewById(R.id.label);
        }
    }
     By it, in getView(), when convertView is null, we inflate the XML file and convert to View, after that,  linked it with the ViewHolder instance through call setTag(). Moreover, if convertView is not null, recycling it with the exist ViewHolder object with getTag():
 
            ViewHolder holder; //initialize a null ViewHolder object
            LayoutInflater inflater = (LayoutInflater) getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
            // If holder not exist then locate all view from UI file.
            if (convertView == null) {
                // inflate UI from XML file
                convertView = inflater.inflate(R.layout.row, parent, false);
                // get all UI view
                holder = new ViewHolder(convertView);
                // set tag for holder
                convertView.setTag(holder);
            } else {
                // if holder created, get tag from view
                holder = (ViewHolder) convertView.getTag();
            }
    With this design pattern, our ListView will be scrolled very smoothly, it's performance is optimized. You can realize it effect when the ListView data is much complicated. And this is full code for this Activity (extends from AppCompatActivity, not ListActivity like the examples above):
package info.devexchanges.listviewsample;

import android.app.Activity;
import android.app.ListActivity;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private TextView selection;
    private ListView listView;
    private static final String[] items = {"lorem", "ipsum", "dolor", "sit",
            "amet", "consectetuer", "adipiscing", "elit", "morbi", "vel",
            "ligula", "vitae", "arcu", "aliquet", "mollis", "etiam", "vel",
            "erat", "placerat", "ante", "porttitor", "sodales", "pellentesque",
            "augue", "purus"};

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setContentView(R.layout.activity_main);
        selection = (TextView) findViewById(R.id.selection);
        listView = (ListView) findViewById(R.id.list);

        //set on click listener for each List Item
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
                selection.setText(items[position]);
            }
        });

        //set listview adapter
        ArrayAdapter<String> adapter = new IconicAdapter();
        listView.setAdapter(adapter);
    }

    private class IconicAdapter extends ArrayAdapter<String> {

        public IconicAdapter() {
            super(MainActivity.this, R.layout.row, items);
        }

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

            ViewHolder holder; //initialize a null ViewHolder object
            LayoutInflater inflater = (LayoutInflater) getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
            // If holder not exist then locate all view from UI file.
            if (convertView == null) {
                // inflate UI from XML file
                convertView = inflater.inflate(R.layout.row, parent, false);
                // get all UI view
                holder = new ViewHolder(convertView);
                // set tag for holder
                convertView.setTag(holder);
            } else {
                // if holder created, get tag from view
                holder = (ViewHolder) convertView.getTag();
            }

            if (items[position].length() > 4) {
                holder.icon.setImageResource(R.mipmap.ic_delete);
            } else {
                holder.icon.setImageResource(R.mipmap.ic_check);
            }
            holder.text.setText(items[position]);

            return convertView;
        }
    }

    private class ViewHolder {

        private ImageView icon;
        private TextView text;

        public ViewHolder(View v) {
            icon = (ImageView)v.findViewById(R.id.icon);
            text = (TextView)v.findViewById(R.id.label);
        }
    }
}

Conclusions

    Once again, I would like to emphasize that ListView is the most important widget in Android, it is a fast way to organize data in a list format on the screen. From now on, you can visit my ListView tag link to read more useful tips about it. Up to the next chapter, I will guide about embedded the WebKit Browser to Android app. Finally, you can get full project code about ListView using ViewHolder pattern from +GitHub by click the button below.




Share


Previous post
« Prev Post
Next post
Next Post »