A most useful and common View in Android is ListView. In this tutorial, wWe are going to show that how to develop an android custom ListView to suit your needs.
This tutorial will also show you how to load images in ListView from server and cache them so they are instantly available for download. We are using third party library which provides an efficient way to get data and images from the server.
In this tutorial we will get JSON from web service contains world’s wealthiest guys list and show in ListView.
JSON
Our JSON consists of Array of an object where each object contains name, wealth, and source of a billionaire.
[
{
"name": "Bill Gates",
"image": " https://raw.githubusercontent.com/mobilesiri/Android-Custom-Listview-Using-Volley/master/images/Bill_Gates.jpg
",
"worth": "$79.2 billion",
"InYear": 2015,
"source": "Microsoft"
}
]
Android Custom ListView Row Layout
We use the relative layout as the parent. And use Alignment like below, right and parent bottom in children views to design a layout.
Creating Android Project
1. Open Android Studio and create a new project.
2. Once the project created, open build.Gradle and add the dependencies of Volley.
dependencies {
compile ‘com.mcxiaoke.volley:library-aar:1.0.0’
}
3. Just for organizing we create the separate packages adapter, model, util and volleycustomlistview (already present).
4. In res->values open colors.xml (if not available create it) and add these colors to it.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="wealthsource">#757575</color>
<color name="year">#949494</color>
<color name="list_divider">#d9d9d9</color>
<color name="list_row_start_color">#FCFCFC</color>
<color name="row_list_end_color">#FCFCFC</color>
<color name="row_list_hover_start_color">#EDF0F2</color>
<color name="row_list_hover_end_color">#EDF0F2</color>
</resources>
5. In dimens.xml allocated at res-> values add below dimensions
<resources>
<dimen name="name">17dp</dimen>
<dimen name="worth">15dip</dimen>
<dimen name="source">13dip</dimen>
<dimen name="InYear">12dip</dimen>
</resources>
6. Now, start to create the UI part first. In res-> drawable (create if not exist) add list_row_bg.xml, list_row_bg_hover.xml and row_list_selector.xml.
row_list_bg.xml Default view for row
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:startColor="@color/list_row_start_color"
android:endColor="@color/row_list_end_color"
android:angle="270" />
</shape>
row_list_bg_hover.xml When row selected
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<gradient
android:angle=”270″
android:endColor=”@color/row_list_hover_end_color”
android:startColor=”@color/row_list_hover_start_color” />
</shape>
list_row_selector.xml Switch design file when state change
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable=”@drawable/list_row_bg” android:state_pressed=”false” android:state_selected=”false”/>
<item android:drawable=”@drawable/list_row_bg_hover” android:state_pressed=”true”/>
<item android:drawable=”@drawable/list_row_bg_hover” android:state_pressed=”false” android:state_selected=”true”/>
</selector>
7. Now, add ListView xml is your main activity’s layout.
<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"
tools:context=".MainActivity">
<ListView
android:id=”@+id/list”
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:divider=”@color/list_divider”
android:dividerHeight=”1dp”
android:listSelector=”@drawable/row_list_selector” />
</RelativeLayout>
8. Now, add the design for list row, this is the main design of custom listview.
List_row.xml
<?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="wrap_content"
android:background="@drawable/row_list_selector"
android:padding="8dp">
<!– Thumbnail Image –>
<com.android.volley.toolbox.NetworkImageView
android:id=”@+id/imgBillionaire”
android:layout_width=”80dp”
android:layout_height=”80dp”
android:layout_alignParentLeft=”true”
android:layout_marginRight=”8dp” />
<!– Name of billionaire –>
<TextView
android:id=”@+id/billionairename”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_alignTop=”@+id/imgBillionaire”
android:layout_toRightOf=”@+id/imgBillionaire”
android:textSize=”@dimen/bname”
android:textStyle=”bold” />
<!– Worth –>
<TextView
android:id=”@+id/worth”
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:layout_below=”@id/billionairename”
android:layout_marginTop=”1dip”
android:layout_toRightOf=”@+id/imgBillionaire”
android:textSize=”@dimen/worth” />
<!– Source –>
<TextView
android:id=”@+id/source”
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:layout_below=”@id/worth”
android:layout_marginTop=”5dp”
android:layout_toRightOf=”@+id/imgBillionaire”
android:textColor=”@color/wealthsource”
android:textSize=”@dimen/source” />
<!– Year –>
<TextView
android:id=”@+id/inYear”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_alignParentBottom=”true”
android:layout_alignParentRight=”true”
android:textColor=”@color/year”
android:textSize=”@dimen/InYear” />
</RelativeLayout>
We done with design part now move to write java
9. Create LruBitmapImgCache.java in util package. This file takes care of caching the image on disk.
LruBitmapImgCache.java
package com.mobilesiri.util;
import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
import com.android.volley.toolbox.ImageLoader;
public class LruBitmapImgCache extends LruCache<String, Bitmap> implements
ImageLoader.ImageCache {
public static int getDefaultLruCacheSize() {
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
final int cacheSize = maxMemory / 8;
return cacheSize;
}
public LruBitmapImgCache() {
this(getDefaultLruCacheSize());
}
public LruBitmapImgCache(int sizeInKiloBytes) {
super(sizeInKiloBytes);
}
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
@Override
public Bitmap getBitmap(String url) {
return get(url);
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
put(url, bitmap);
}
}
10. Now, add internet permission in your AndroidManifest.xml, and to execute AppController.java class on application start add it in AndroidManifest.xml of your tag using name property.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mobilesiri.volleycustomlistview">
<application
android:name=”.AppController”
android:allowBackup=”true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=”.MainActivity”
android:label=”@string/app_name”>
<intent-filter>
<action android:name=”android.intent.action.MAIN” />
<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
</activity>
</application>
<uses-permission android:name=”android.permission.INTERNET” />
<uses-permission android:name=”android.permission.WRITE_EXTERNAL_STORAGE” />
</manifest>
11. Now create WorldsBillionaires.java under model package. This class hold object after parsing to provide data to ListView.
package com.mobilesiri.model;
public class WorldsBillionaires {
private String billionairesname, billionairesImgUrl;
private int year;
private String source;
private String worth;
public String getBillionairesname() {
return billionairesname;
}
public void setBillionairesname(String billionairesname) {
this.billionairesname = billionairesname;
}
public String getBillionairesImgUrl() {
return billionairesImgUrl;
}
public void setBillionairesImgUrl(String billionairesImgUrl) {
this.billionairesImgUrl = billionairesImgUrl;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public String getSource() {
return source;
}
public void setSource(String source) {
this.source = source;
}
public String getWorth() {
return worth;
}
public void setWorth(String worth) {
this.worth = worth;
}
}
12. Now, add the custom list adapter with name CustomListAdapter.java and add below code. It is custom list adapter which renders each row of ListView, getView method is called for each row, which uses list_row.xml as view and fills it with the appropriate data.
CustomListAdapter.java
package com.mobilesiri.adapter;
import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.NetworkImageView;
import com.mobilesiri.model.WorldsBillionaires;
import com.mobilesiri.volleycustomlistview.AppController;
import com.mobilesiri.volleycustomlistview.R;
import java.util.List;
public class CustomListAdapter extends BaseAdapter {
private Activity activity;
private LayoutInflater inflater;
private List<WorldsBillionaires> billionairesItems;
ImageLoader imageLoader = AppController.getInstance().getImageLoader();
public CustomListAdapter(Activity activity, List<WorldsBillionaires> billionairesItems) {
this.activity = activity;
this.billionairesItems = billionairesItems;
}
@Override
public int getCount() {
return billionairesItems.size();
}
@Override
public Object getItem(int location) {
return billionairesItems.get(location);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (inflater == null)
inflater = (LayoutInflater) activity
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (convertView == null)
convertView = inflater.inflate(R.layout.list_row, null);
if (imageLoader == null)
imageLoader = AppController.getInstance().getImageLoader();
NetworkImageView imgBillionaire = (NetworkImageView) convertView
.findViewById(R.id.imgBillionaire);
TextView name = (TextView) convertView.findViewById(R.id.billionairename);
TextView worth = (TextView) convertView.findViewById(R.id.worth);
TextView source = (TextView) convertView.findViewById(R.id.source);
TextView year = (TextView) convertView.findViewById(R.id.inYear);
// getting billionaires data for the row
WorldsBillionaires m = billionairesItems.get(position);
// Billionaire image
imgBillionaire.setImageUrl(m.getThumbnailUrl(), imageLoader);
// name
name.setText(m.getName());
// Wealth Source
source.setText(“Wealth Source: ” + String.valueOf(m.getSource()));
worth.setText(String.valueOf(m.getWorth()));
// release year
year.setText(String.valueOf(m.getYear()));
return convertView;
}
}
Read More: JSON Parsing in Android using Android Studio
Read More: Android SQLite Database Tutorial using Android Studio
13. Finally add below code in MainActiviy.java and we use Volley’s JsonArrayRequest to get JSON from URL and in onResponse, called on successful HTTP request complete, parse the JSON and put in ArrayList of WorldsBillionaires objects. Then call notifyDataSetChanged of CustomListAdapter instance to show List with update data.
package com.mobilesiri.volleycustomlistview;
import android.app.Activity;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.util.Log;
import android.widget.ListView;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.VolleyLog;
import com.android.volley.toolbox.JsonArrayRequest;
import com.mobilesiri.adapter.CustomListAdapter;
import com.mobilesiri.model.WorldsBillionaires;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends Activity {
// Log tag
private static final String TAG = MainActivity.class.getSimpleName();
// Billionaires json url
private static final String url = “https://raw.githubusercontent.com/mobilesiri/Android-Custom-Listview-Using-Volley/master/richman.json”;
private ProgressDialog pDialog;
private List<WorldsBillionaires> worldsBillionairesList = new ArrayList<WorldsBillionaires>();
private ListView listView;
private CustomListAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.list);
adapter = new CustomListAdapter(this, worldsBillionairesList);
listView.setAdapter(adapter);
pDialog = new ProgressDialog(this);
// Showing progress dialog before making http request
pDialog.setMessage(“Loading…”);
pDialog.show();
// Creating volley request obj
JsonArrayRequest billionaireReq = new JsonArrayRequest(url,
new Response.Listener<JSONArray>() {
@Override
public void onResponse(JSONArray response) {
Log.d(TAG, response.toString());
hidePDialog();
// Parsing json
for (int i = 0; i < response.length(); i++) {
try {
JSONObject obj = response.getJSONObject(i);
WorldsBillionaires worldsBillionaires = new WorldsBillionaires();
worldsBillionaires.setName(obj.getString(“name”));
worldsBillionaires.setThumbnailUrl(obj.getString(“image”));
worldsBillionaires.setWorth(obj.getString(“worth”));
worldsBillionaires.setYear(obj.getInt(“InYear”));
worldsBillionaires.setSource(obj.getString(“source”));
// adding Billionaire to worldsBillionaires array
worldsBillionairesList.add(worldsBillionaires);
} catch (JSONException e) {
e.printStackTrace();
}
}
// notifying list adapter about data changes
// so that it shows updated data in ListView
adapter.notifyDataSetChanged();
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
VolleyLog.d(TAG, “Error: ” + error.getMessage());
hidePDialog();
}
});
// Adding request to request queue
AppController.getInstance().addToRequestQueue(billionaireReq);
}
@Override
public void onDestroy() {
super.onDestroy();
hidePDialog();
}
private void hidePDialog() {
if (pDialog != null) {
pDialog.dismiss();
pDialog = null;
}
}
}