Learn Android — Custom Lists and Adapters
The world’s most popular mobile OS — from phones and watches to cars and TVs.
Welcome to the Learn Android series. I’m going to share with you, why and how to start with Android and build your portfolio. I’ll share explanations and code snippets on how to implement the most important and basic things in Android.
In the previous article, we learned about all the theoretical part of the workings about List and Adapters. But we used most of the inbuilt methods. So in this article, we’ll learn about creating and using Custom Lists and Custom Adapters.
For our learning purpose, we’ll create a ListView row item similar to how contacts are displayed on our mobile phones. Let us do this step-wise.
Initially, you should create a ‘new’ project and go through all the fundamental details, including choosing the API level and upto choosing color schemes for your Android application. Please do choose an empty activity for learning purposes.
Next up, we’ll already have one XML and one Java file created in our project. Let’s see what we’ll do next to create perfectly, custom Adapters and Lists.
Using the already created activity_main.xml file, we’ll define the ListView in the XML like we did in the previous article. Also, we need to give an ID to the ListView so we can reference the view into the Java file.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
tools:context=".TargetActivity">
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
</LinearLayout>
Now, we’ll create a new XML layout file. Let’s name it “list_item.xml” and then we can create a layout similar to how a single contact is displayed.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<ImageView
android:id="@+id/contact_image"
android:layout_width="0dp"
android:layout_height="128dp"
android:layout_weight="2"
android:contentDescription="Contact Image" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="128dp"
android:layout_weight="3"
android:orientation="vertical">
<TextView
android:id="@+id/contact_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:textColor="@android:color/black"
android:textSize="18sp" />
<TextView
android:id="@+id/contact_number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:textColor="@android:color/darker_gray"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>
This will give us a horizontal list item with a contact image and 2 text’s for contact name and number.
Furthermore, we’ll now need to define an object class. An object is a collection of variables. To create an object class, we create a new Java class. Let’s name it “ContactItem.java”.
We will then define 3 variables — for our 3 fields in the contact list item — and create constructor and getter & setter methods for the same. For the image, we will have an integer variable as for the “dummy data” that we’ll be including in the application will be of the integer data type.
If we access image through APIs, most of the time we get image URL’s, which are of the String form and we need to parse the URL’s to get the image.
We just need to declare the variable, the Android Studio IDE is capable of generating the constructors and methods for us. Let’s see how to do that. First of all, we’ll create the variables we need.
package com.example.android.myapplication;
public class ContactItem {
private int contact_image;
private String contact_name;
private String contact_number;
}
After this, press Alt + Insert on the keyboard and a drop down will let us choose constructor, getter, and setter methods and we’ll add them one by one. This reduces the amount of code that we need to type. While adding these, we’ll need to select all the variables.
Here’s the final Object class. This is also called as a POJO class or Plain Old Java Object.
package com.example.android.myapplication;
public class ContactItem {
private int contact_image;
private String contact_name;
private String contact_number;
public ContactItem(int contact_image, String contact_name, String contact_number) {
this.contact_image = contact_image;
this.contact_name = contact_name;
this.contact_number = contact_number;
}
public int getContact_image() {
return contact_image;
}
public void setContact_image(int contact_image) {
this.contact_image = contact_image;
}
public String getContact_name() {
return contact_name;
}
public void setContact_name(String contact_name) {
this.contact_name = contact_name;
}
public String getContact_number() {
return contact_number;
}
public void setContact_number(String contact_number) {
this.contact_number = contact_number;
}
}
After creating the POJO class, we’ll create the ArrayAdapter to link the data with the views. For more information about Adapters, please refer to the previous article.
But instead of our ArrayAdapters consisting of Strings like in the previous article, our “ContactAdapter” class will have ArrayAdapter consisting of our ContactItem class.
Next, the ArrayList that we will create will also contain ContactItem instead of the String since each element now contains the complete ContactItem details — including image, name, and number. Let us see how to implement the ArrayAdapter.
package com.example.android.myapplication;
import android.content.Context;
import android.support.v4.content.ContextCompat;
import android.widget.ArrayAdapter;
import java.lang.reflect.Array;
import java.util.ArrayList;
public class ContactAdapter extends ArrayAdapter<ContactItem> {
private Context context;
private ArrayList<ContactItem> contactItemArrayList;
public ContactAdapter(Context context, ArrayList<ContactItem> contactItemArrayList) {
super(context, 0, contactItemArrayList);
this.context = context;
this.contactItemArrayList = contactItemArrayList;
}
}
After creating the variables and the constructor, we will override some methods. The shortcut for browsing available methods that we can “override” is Ctrl + O.
The must-override methods while creating ArrayAdapters are —
- getView()
- getCount()
We will add these methods dynamically (Ctrl + O) so that we do not need to worry about the boilerplate but rather just the implementation of what we need to achieve. This is how our adapter will look after adding the methods —
package com.example.android.myapplication;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.ArrayList;
public class ContactAdapter extends ArrayAdapter<ContactItem> {
private Context context;
private ArrayList<ContactItem> contactItemArrayList;
public ContactAdapter(Context context, ArrayList<ContactItem> contactItemArrayList) {
super(context, 0, contactItemArrayList);
this.context = context;
this.contactItemArrayList = contactItemArrayList;
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
View view = convertView;
if (convertView == null) {
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item, parent, false);
}
ImageView imageTv = view.findViewById(R.id.contact_image);
TextView nameTv = view.findViewById(R.id.contact_name);
TextView numberTv = view.findViewById(R.id.contact_number);
ContactItem item = contactItemArrayList.get(position);
imageTv.setImageResource(item.getContact_image());
nameTv.setText(item.getContact_name());
numberTv.setText(item.getContact_number());
return view;
}
@Override
public int getCount() {
return contactItemArrayList.size();
}
}
In the getView() method, firstly we “inflate” the layout resource. Inflating a resource means we take the XML layout and parse it to create a hierarchy of views. This is used to render the layout when setContentView() is called.
After inflating the layout, we initialize the view components from the XML by using the findViewById() method. But since we’ve inflated the layout in the “view” object we use view.findViewById() and pass in the ID parameter.
After initializing the views, we get the item from the ArrayList using the “position” parameter and then set the data into the views using the getter methods that we generated in the object file. And then we return the view. Furthermore, in the getCount() method, we return the size of the ArrayList that contains the data.
Now, we need to instantiate all the required adapters and lists in the Java file of the activity we want to show the list into. Let’s see how we do that —
package com.example.android.myapplication;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import java.util.ArrayList;
public class TargetActivity extends AppCompatActivity {
private ArrayList<ContactItem> contactItemArrayList;
private ListView listView;
private ContactAdapter contactAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_target);
//Setting up the ListView from the XML.
listView = findViewById(R.id.list_view);
contactItemArrayList = new ArrayList<>();
contactAdapter = new ContactAdapter(this, contactItemArrayList);
//Populating the array list with the required data to be shown in our list view.
populateList();
//Setting the adapter to the list view.
listView.setAdapter(contactAdapter);
}
private void populateList() {
//Replicating the same item a few number of times so that we can observe the list.
contactItemArrayList.add(new ContactItem(R.drawable.ic_launcher_background, "Myself", "987654321"));
contactItemArrayList.add(new ContactItem(R.drawable.ic_launcher_background, "Myself", "987654321"));
contactItemArrayList.add(new ContactItem(R.drawable.ic_launcher_background, "Myself", "987654321"));
contactItemArrayList.add(new ContactItem(R.drawable.ic_launcher_background, "Myself", "987654321"));
contactItemArrayList.add(new ContactItem(R.drawable.ic_launcher_background, "Myself", "987654321"));
contactItemArrayList.add(new ContactItem(R.drawable.ic_launcher_background, "Myself", "987654321"));
contactItemArrayList.add(new ContactItem(R.drawable.ic_launcher_background, "Myself", "987654321"));
contactItemArrayList.add(new ContactItem(R.drawable.ic_launcher_background, "Myself", "987654321"));
}
}
In our Activity class, we’ve initialized the list and the adapter along with our ListView. Then we populate the ArrayList with dummy data by replicating the same data a few number of times for the sake of observing the ListView. We use the .add() method and create a new ContactItem object and pass in the required parameters to add an item to the list.
You can add custom data to the list if you wish to observe it with a random data set.
Finally, we set the Adapter on the ListView and when we run the code, we’ll be able to see the ListView in action.
THAT’S IT! We’ve successfully implemented a custom data holding ListView and we are now able to create endless custom views.