In this tutorial we’ll use a CustomAdapter that populates the custom rows of the Android ListView with an ArrayList
. Also to enhance the user experience, we’ll animate the ListView while scrolling.
Table of Contents
Android ListView Custom Adapter Overview
The simplest Adapter to populate a view from an ArrayList is the ArrayAdapter
. That’s what we’ll implement in this tutorial. There are other adapters as well, such as the CursorAdapter
which binds directly to a result set from a Local SQLite Database and it uses a Cursor as it’s data source.
Recycling Rows
As a ListView is instantiated and the rows are populated such that the full height of the list is filled. After that no new row items are created in the memory. As the user scrolls through the list, items that leave the screen are kept in memory for later use and then every new row that enters the screen reuses an older row kept in the memory.
Creating a View template
Let’s create a xml layout that presents the items in a row in a customised way.
row_item.xml
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp">
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:text="Marshmallow"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="@android:color/black" />
<TextView
android:id="@+id/type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/name"
android:layout_marginTop="5dp"
android:text="Android 6.0"
android:textColor="@android:color/black" />
<ImageView
android:id="@+id/item_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:src="@android:drawable/ic_dialog_info" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true">
<TextView
android:id="@+id/version_heading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="API: "
android:textColor="@android:color/black"
android:textStyle="bold" />
<TextView
android:id="@+id/version_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="23"
android:textAppearance="?android:attr/textAppearanceButton"
android:textColor="@android:color/black"
android:textStyle="bold" />
</LinearLayout>
</RelativeLayout>
In this tutorial we’ll build an application that consists of list of rows displaying text descriptions and an info icon. Clicking the row would display the SnackBar with the text elements of that row. Clicking the info will display a SnackBar with information specific to that row.
Project Structure
Code
We are creating a custom ListView of by subclassing ArrayAdapter with the DataModel as the object.
getView() is the method that returns the actual view used as a row within the ListView at a particular position.
The content_main.xml
contains the ListView as shown below.
content_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="https://schemas.android.com/apk/res-auto"
tools:context="com.journaldev.customlistview.MainActivity"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:showIn="@layout/activity_main">
<ListView
android:id="@+id/list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</RelativeLayout>
The data model that is contained in the ArrayList is shown below.
DataModel.java
public class DataModel {
String name;
String type;
String version_number;
String feature;
public DataModel(String name, String type, String version_number, String feature ) {
this.name=name;
this.type=type;
this.version_number=version_number;
this.feature=feature;
}
public String getName() {
return name;
}
public String getType() {
return type;
}
public String getVersion_number() {
return version_number;
}
public String getFeature() {
return feature;
}
}
The CustomAdapter that populates the DataModel into the ListView is shown below.
CustomAdapter.java
public class CustomAdapter extends ArrayAdapter<DataModel> implements View.OnClickListener{
private ArrayList<DataModel> dataSet;
Context mContext;
// View lookup cache
private static class ViewHolder {
TextView txtName;
TextView txtType;
TextView txtVersion;
ImageView info;
}
public CustomAdapter(ArrayList<DataModel> data, Context context) {
super(context, R.layout.row_item, data);
this.dataSet = data;
this.mContext=context;
}
@Override
public void onClick(View v) {
int position=(Integer) v.getTag();
Object object= getItem(position);
DataModel dataModel=(DataModel)object;
switch (v.getId())
{
case R.id.item_info:
Snackbar.make(v, "Release date " +dataModel.getFeature(), Snackbar.LENGTH_LONG)
.setAction("No action", null).show();
break;
}
}
private int lastPosition = -1;
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// Get the data item for this position
DataModel dataModel = getItem(position);
// Check if an existing view is being reused, otherwise inflate the view
ViewHolder viewHolder; // view lookup cache stored in tag
final View result;
if (convertView == null) {
viewHolder = new ViewHolder();
LayoutInflater inflater = LayoutInflater.from(getContext());
convertView = inflater.inflate(R.layout.row_item, parent, false);
viewHolder.txtName = (TextView) convertView.findViewById(R.id.name);
viewHolder.txtType = (TextView) convertView.findViewById(R.id.type);
viewHolder.txtVersion = (TextView) convertView.findViewById(R.id.version_number);
viewHolder.info = (ImageView) convertView.findViewById(R.id.item_info);
result=convertView;
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
result=convertView;
}
Animation animation = AnimationUtils.loadAnimation(mContext, (position > lastPosition) ? R.anim.up_from_bottom : R.anim.down_from_top);
result.startAnimation(animation);
lastPosition = position;
viewHolder.txtName.setText(dataModel.getName());
viewHolder.txtType.setText(dataModel.getType());
viewHolder.txtVersion.setText(dataModel.getVersion_number());
viewHolder.info.setOnClickListener(this);
viewHolder.info.setTag(position);
// Return the completed view to render on screen
return convertView;
}
}
In the above code we’ve added a onClickListener
to the ImageView that displays a SnackBar when clicked with a description for the respective row.
Also the list rows are animated when scrolled. The two animation xml resource files are given below.
down_from_top.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="https://schemas.android.com/apk/res/android"
android:shareInterpolator="@android:anim/decelerate_interpolator">
<translate
android:fromXDelta="0%" android:toXDelta="0%"
android:fromYDelta="-100%" android:toYDelta="0%"
android:duration="400" />
</set>
up_from_bottom.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="https://schemas.android.com/apk/res/android"
android:shareInterpolator="@android:anim/decelerate_interpolator">
<translate
android:fromXDelta="0%" android:toXDelta="0%"
android:fromYDelta="100%" android:toYDelta="0%"
android:duration="400" />
</set>
The MainActivity.java
where the CustomAdapter is set to the ListView is defined below. Along with that a random ArrayList of DataModel objects is populated.
MainActivity.java
public class MainActivity extends AppCompatActivity {
ArrayList<DataModel> dataModels;
ListView listView;
private static CustomAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
listView=(ListView)findViewById(R.id.list);
dataModels= new ArrayList<>();
dataModels.add(new DataModel("Apple Pie", "Android 1.0", "1","September 23, 2008"));
dataModels.add(new DataModel("Banana Bread", "Android 1.1", "2","February 9, 2009"));
dataModels.add(new DataModel("Cupcake", "Android 1.5", "3","April 27, 2009"));
dataModels.add(new DataModel("Donut","Android 1.6","4","September 15, 2009"));
dataModels.add(new DataModel("Eclair", "Android 2.0", "5","October 26, 2009"));
dataModels.add(new DataModel("Froyo", "Android 2.2", "8","May 20, 2010"));
dataModels.add(new DataModel("Gingerbread", "Android 2.3", "9","December 6, 2010"));
dataModels.add(new DataModel("Honeycomb","Android 3.0","11","February 22, 2011"));
dataModels.add(new DataModel("Ice Cream Sandwich", "Android 4.0", "14","October 18, 2011"));
dataModels.add(new DataModel("Jelly Bean", "Android 4.2", "16","July 9, 2012"));
dataModels.add(new DataModel("Kitkat", "Android 4.4", "19","October 31, 2013"));
dataModels.add(new DataModel("Lollipop","Android 5.0","21","November 12, 2014"));
dataModels.add(new DataModel("Marshmallow", "Android 6.0", "23","October 5, 2015"));
adapter= new CustomAdapter(dataModels,getApplicationContext());
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
DataModel dataModel= dataModels.get(position);
Snackbar.make(view, dataModel.getName()+"\n"+dataModel.getType()+" API: "+dataModel.getVersion_number(), Snackbar.LENGTH_LONG)
.setAction("No action", null).show();
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
The output of the application in action is shown below.
This brings an end to this tutorial. You can download the final Android ListView Custom Adapter Project from the link below.
Reference: API Guide List View
I like this post, but missing my own template to show ListView XML file.
How to do in the correct way this inserted code as PHP uses simple include(file)…
Should be created template inside ListView file like XML or we add own template/acticity and insert just ListView. Need help.
Thank you very much.
I have a problem with disappearing rows. When I scroll too far and go back there’s nothing left, only empty rows – i see their borders when I turn on “show layout borders” (or sth like this – I use other language in smartphone). What could go wrong? I use Android 9 on Nokia 6.1.
Ok, I know, that when I try to hide TextViews in my custom rows by setting textSize or height to 0dp, there are some errors. After scrolling down and up textViews that should be there disappear by setting height or textSize to 0. How should I correctly hide them to remove empty spaces between text lines (row contains a few TextViews placed vertically)?
Very helpful article indeed.
Please I need help I’m stuck in how I can make the spinner on each row on lthe istvew. I already create the custom arrayadapter class and custom data type class, but I don’t know how I can add the spinner to each row.
Any I’m really really appreciate it
Are you still stuck?
Thank you! Very helpfull code.
Very Helpful.. Thanks for the code
How can I pass the data from the custom adapter to the main activity when the imageview is clicked? Thank you
public ObjectListAdapter(@NonNull Context context, int resource, int textViewResourceId, @NonNull ArrayList arrayList)
I’m prompted that I have to use the above constructor, and when I do the app crashes with the following error
java.lang.IllegalStateException: ArrayAdapter requires the resource ID to be a TextView
The code is clear but I didn’t understood why you stored ” result=convertView; ” because it is never according to the code. An explanation would be appreciated.
Very educational. Code example works great. Thanks!
can u repost the above code with database connectivity?.It would be helpful.
Visit this: https://www.journaldev.com/9438/android-sqlite-database-example-tutorial
hi this good work same work am doing
Thank you . Very nice article.
Thank you
I like this post, but missing my own template to show ListView XML file.
How to do in the correct way this inserted code as PHP uses simple include(file)…
Should be created template inside ListView file like XML or we add own template/acticity and insert just ListView. Need help.
hello, the code didn’t work for me the app crashes immediately
Thanks for the valuable information I am also the android user and the developer that’s why I always visit your site here I grave more knowledge about the android it helps to improve my skills and knowledge about the android keep posting like this.
Glad it’s helped you!
Short and clear article. Thank you.
Appreciate your gesture. Thanks!
what should i do if i wanna replace that button with a spinner
how do i implement setOnSelectedItem method without confusing with the listview setonitemclick
This was extremely useful to me and my project, thanks very much!
The problem about all custom listview tutorials are this :
What will happen if i want to add more than 1 Strings to get ?
I cant use toString() method for more then one String.
For example i got 4 Stringss.
@Override
public String toString(){
return String1;
}
Will not work for more then one string.What can i do in this situation ?
How to load image in custom list view with array of url using picasso library ?
In android example – https://www.journaldev.com/10416/android-listview-with-custom-adapter-example-tutorial
Got the answer.
Answer :Picasso.with(mContext).load(dataModel.getImageURL()).resize(100, 100).into(viewHolder.info);
Thank you.
viewHolder.txtName.setText(dataModel.getName());
viewHolder.txtType.setText(dataModel.getType());
viewHolder.txtVersion.setText(dataModel.getVersion_number());
viewHolder.info.setOnClickListener(this);
viewHolder.info.setTag(position);
This snippet flags Null pointer exception when i scroll down.
package com.login;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import java.util.ArrayList;
public class list extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list);
sqlite sqliteObj= new sqlite(list.this);
ArrayList arrColle = sqliteObj.getall();
ListView liView = (ListView)findViewById(R.id.lisv);
CustonmAdapter cusObj = new CustonmAdapter(list.this,R.layout.adpater_view,arrColle);
liView.setAdapter(cusObj);
}
public class CustonmAdapter extends ArrayAdapter
{
Context _con;
int _layout;
ArrayList arrObj;
public CustonmAdapter(Context con , int layout, ArrayList arr)
{
super(con,layout,arr);
_con = con;
_layout = layout;
arrObj = arr;
}
@Override
public View getView(int position,View convertView,
ViewGroup parent) {
View v =null;
LayoutInflater layou =(LayoutInflater)(list.this).getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = layou.inflate(_layout,null);
Pojo poObj =arrObj.get(position);
TextView tx = (TextView) v.findViewById(R.id.uName);
TextView tx1 = (TextView)v.findViewById(R.id.pName);
tx.setText(poObj.getUsernam());
tx1.setText(poObj.getPass());
return v;
}
}
}
Thanks a lot!!
package com.login;
import java.io.Serializable;
/**
* Created by Jai on 6/10/2017.
*/
public class Pojo implements Serializable {
String name;
String loc;
String usernam;
String pass;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLoc() {
return loc;
}
public void setLoc(String loc) {
this.loc = loc;
}
public String getUsernam() {
return usernam;
}
public void setUsernam(String usernam) {
this.usernam = usernam;
}
public String getPass() {
return pass;
}
public void setPass(String pass) {
this.pass = pass;
}
}
Very useful example, thanks for sharing. Best regards,
sir i had a problem related to snackbar class it doesnot import it goes to error help me
Add the following dependency in your build.gradle
compile ‘com.android.support:design:25.3.1’ (or whichever is the latest SDK version you’re using)
Thanks
Thank you , simple and helpful , prof way 🙂
Thanks, Somaia.
Hy
In ‘CustomAdapter.java’ it should return variable ‘result’
else the animation will not be seen
Great ! It works for me.
I integrated a SQlite db to populate listview but I am not able to refresh ListV when db is updated. Any hints ???
Nice simple example thanks
Thanks, Brianoh.
i learn basics ….tankhu
Thanks, Aiman.
Hi,I’ve been looking for a decent listview adapter and this seems to be the one.would you mind if I use it in my app?could you please give me a hint on how to saverify it to sharedpreferences?