There are times when you want to show a collection of related data (e.g., books, movies) in an easy-to-read format. A ListView is a great candidate for this this of task. A ListView will create a scrollable list of items that can be displayed.
In this tutorial, we will create an app that will use a Custom Adapter for the ListView:
As in many objects that are created, we will:
In this third version of the Adobe Apps, we will drag-and-drop a ListView component onto the screen and then use an Adapter and an array to populate a CUSTOM ListView.
android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin"
android:background="#5151fe">
<ListView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/ListView"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true" android:divider="#cccccc"
android:dividerHeight="1dp"/>
While you could have done this at the SAME time you created the ListView component above, we wanted to do it in a systematic approach. However, in real production, it would be best to create the components and give them names at the same time they are created.
IMPORTANT CONCEPT TO REMEMBER: It is important to name every component that will be used by Java code with an id. The id is used to store a reference of the element in a Java object as you will see later.
<ListView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true" android:divider="#cccccc"
android:dividerHeight="1dp"
android:id="@android:id/list" />
public class Book {
public String title;
public String author;
}
package com.example.adobecustomlistviewappv2;
public class Book {
public String title;
public String author;
public Book(String title, String author) {
this.title = title;
this.author = author;
}
}
package com.example.adobecustomlistviewappv2; public class Books {
public String title;
public String author;
public Books(String title, String author) {
this.title = title;
this.author = author;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
package com.example.adobecustomlistviewappv2;
import java.util.ArrayList;
public class BookStore {
public static List<Book> getBooks(){
List<Book> books = new ArrayList<Book>();
books.add(new Book("Book1","BookSubTitle1"));
books.add(new Book("Book2","BookSubTitle2"));
books.add(new Book("Book3","BookSubTitle3"));
books.add(new Book("Book4","BookSubTitle4"));
books.add(new Book("Book5","BookSubTitle5"));
books.add(new Book("Book6","BookSubTitle6"));
books.add(new Book("Book7","BookSubTitle7"));
books.add(new Book("Book8","BookSubTitle8"));
books.add(new Book("Book9","BookSubTitle9"));
books.add(new Book("Book10","BookSubTitle10"));
books.add(new Book("Book11","BookSubTitle11"));
books.add(new Book("Book12","BookSubTitle12"));
books.add(new Book("Book13","BookSubTitle13"));
books.add(new Book("Book14","BookSubTitle14"));
books.add(new Book("Book15","BookSubTitle15"));
return books;
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Large Text"
android:id="@+id/title" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Medium Text"
android:id="@+id/author" />
</LinearLayout>
public class MainActivity extends ListActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
class BookAdapter extends ArrayAdapter<Book>{
}
}
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
class BookAdapter extends ArrayAdapter<com.example.adobecustomlistviewappv2.Book>{
public BookAdapter(Context context, int textViewResourceId, List<Book> books) {
super(context,textViewResourceId, books);
}
}
}
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
class BookAdapter extends ArrayAdapter<com.example.adobecustomlistviewappv2.Book>{
private LayoutInflater layoutInflater;
public BookAdapter(Context context, int textViewResourceId, List<com.example.adobecustomlistviewappv2.Book> books) {
super(context,textViewResourceId, books); layoutInflater = LayoutInflater.from(context);
}
}
}
}
public BookAdapter(Context context, int textViewResourceId, List<com.example.adobecustomlistviewappv2.Book> books) {
super(context,textViewResourceId, books);
layoutInflater = LayoutInflater.from(context);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return super.getView(position, convertView, parent);
}
public View getView(int position, View convertView, ViewGroup parent) {
View view = layoutInflater.inflate(R.layout.book_item, null);
Book book = getItem(position);
// Get book properties and assigned them to the view object that is inflated
// The findViewById is called on the view instance because the title and author TextViews are INSIDE of the layout View object
TextView title = (TextView) view.findViewById(R.id.title);
TextView author = (TextView) view.findViewById(R.id.author);
title.setText(book.getTitle());
author.setText(book.getAuthor());
return view;
// return super.getView(position, convertView, parent);
}
If you have a small list of items (one or two dozen) then you can skip these steps. However, if you have a LARGE list of items, then the app may displays sluggish. If you examine the logcat WHILE scrolling the list, you will see:
A Choreographer is a component that is responsive for coordinating input events and screen drawing. It is responsible for keeping the input events in sync with the UI. Like a video if it cannot keep up the sync, it will “drop frames” to compensate. There are two things that can cause an app to run sluggish when you attempt to scroll a list view forward and backward:
The Garbage Collector monitors views that are visible. If you are scrolling up or down, once a view is not visible it no longer has a reference and can be “pick up” by the garbage collector. Moreover, new views will have to be created as you scroll up or down.
Hence, it is not efficient to create a lot of views. To solve this problem, views can be RECYCLED. The goal is to create an initial “set” of views that can be seen on the first screen and then recycle those views as needed. For example, when one view is moved OFF of the screen because the list was scrolled, another existing view can be recycled to show a view that is going to be moved ONTO the screen.
Adapters come with a recycler called convertView that can be used to speed up an app and make is more efficient by modifying the custom adapter code to:
Currently, the convertView parameter is not being used in the getView() method.
// View view = layoutInflater.inflate(R.layout.book_item, null);
View view = convertView;
View view = convertView; Book book = getItem(position);
if (view == null) {
view = layoutInflater.inflate(R.layout.book_item, null);
}
To avoid using the findViewById more than needed, we will use a Holder class which “holds” a reference to the views (e.g., title and author) that we are interested in.
static class Holder {
public TextView title;
public TextView author;
}
}
static class Holder {
public TextView title;
public TextView author;
public Holder(TextView title, TextView author) {
this.title = title;
this author = author;
}
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView; Book book = getItems(position);
Holder holder = null;
if (view == null) {
view = layoutInflater.inflate(R.layout.book_item, null);
TextView title = (TextView) view.findViewById(R.id.title);
TextView author = (TextView) view.findViewById(R.id.author);
// Create a new holder object
holder = new Holder(title, author);
view.setTag(holder);
}
else {
// Get holder from inflated view
holder = (Holder) view.getTag();
}
holder.title.setText(book.getTitle());
holder.author.setText(book.getAuthor());
[ADD SCREEN SHOT HERE...]
// ArrayAdapter<String> appsAdapter = new ArrayAdapter<String>(getApplicationContext(), // android.R.layout.simple_list_item_1,android.R.id.text1, appsStringArray);
public class CustomListViewAdapter extends BaseAdapter {
}
public class CustomListViewAdapter extends BaseAdapter {
@Override
public int getCount() {
return 0;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return null;
}
}
public class CustomListViewAdapter extends BaseAdapter {
private Context mContext;
private ArrayList<HashMap<String,String>> topics;
private static LayoutInflater inflater = null;
public CustomListViewAdapter(Context context, ArrayList<HashMap<String, String>> data){
mContext = context;
topics = data;
inflater = (LayoutInflater)context.getSystemService(context.LAYOUT_INFLATER_SERVICE);
}
@Override
public int getCount() {
return topics.size();
}
@Override
public Object getItem(int position) {
return position;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if (convertView == null){
view = inflater.inflate(R.layout.custom_listview_row, null);
}
TextView title = (TextView) view.findViewById(R.id.title);
TextView sub_title = (TextView) view.findViewById(R.id.sub_title);
ImageView image = (ImageView) view.findViewById(R.id.list_icon);
HashMap<String, String> mTopic = new HashMap<>();
mTopic = topics.get(position);
title.setText(mTopic.get("title"));
sub_title.setText(mTopic.get("sub_title"));
return view;
}
}
public class MainActivity extends AppCompatActivity {
private ListView myListView;
private CustomListViewAdapter customListViewAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myListView = (ListView) findViewById(R.id.appsListView);
final ArrayList<HashMap<String,String>> titleList = new ArrayList<>();
final String[] tittleArray = getResources().getStringArray(R.array.adobe_apps);
final String[] subtitleArray = getResources().getStringArray(R.array.adobe_apps_subtitle);
for (int i=0; i<subtitleArray.length; i++){
HashMap<String,String> data = new HashMap<>();
data.put("title", tittleArray[i].toString());
data.put("sub_title",subtitleArray[i].toString());
titleList.add(data);
}
// Create new adapter and assign it to the ListView
customListViewAdapter = new CustomListViewAdapter(getApplicationContext(),titleList);
myListView.setAdapter(customListViewAdapter);
// Add Event Listener
myListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String myItem = tittleArray[position];
Toast.makeText(getApplicationContext(), myItem, Toast.LENGTH_SHORT).show();
}
});
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if (convertView == null){
view = inflater.inflate(R.layout.custom_listview_row, null);
}
TextView title = (TextView) view.findViewById(R.id.title);
TextView sub_title = (TextView) view.findViewById(R.id.sub_title);
ImageView image = (ImageView) view.findViewById(R.id.list_icon);
HashMap<String, String> mTopic = new HashMap<>();
mTopic = topics.get(position);
title.setText(mTopic.get("title"));
sub_title.setText(mTopic.get("sub_title"));
if(position==0) {
image.setImageResource(R.drawable.dreamweaver);
}
if(position==1) {
image.setImageResource(R.drawable.photoshop);
}
if(position==2) {
image.setImageResource(R.drawable.illustrator);
}
if(position==3) {
image.setImageResource(R.drawable.indesign);
}
if(position==4) {
image.setImageResource(R.drawable.animate);
}
if(position==5) {
image.setImageResource(R.drawable.after_effects);
}
if(position==6) {
image.setImageResource(R.drawable.premiere_pro);
}
if(position==7) {
image.setImageResource(R.drawable.lightroom);
}
if(position==8) {
image.setImageResource(R.drawable.muse);
}
if(position==9) {
image.setImageResource(R.drawable.fuse);
}
if(position==10) {
image.setImageResource(R.drawable.acrobat_pro_dc);
}
if(position==11) {
image.setImageResource(R.drawable.audition);
}
if(position==12) {
image.setImageResource(R.drawable.phonegap);
}
return view;
}
}
It is important to note how these components will be knitted together. The listview state xml files are nested inside of the selector xml file which is inside of the row xml which is pulled inside (inflated) in the java file (e.g., CustomListViewAdapter.java):
Not only do you have to create a custom layout, you have to create a custom adaptor as well. In order to show the custom ListView, we will have to inflate it with Java.
<?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:startColor="#f1f1f2"
android:centerColor="#7878fa"
android:endColor="#cfcfcf"> </gradient>
</shape>
<?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:centerColor="#5454f5"
android:endColor="#cfcfcf"
android:startColor="#f1f1f2"> </gradient>
</shape>
<?xml version="1.0" encoding="utf-8"?>NOTE: The first <item> element is used if the list item has not been selected and have not been pressed--show the default background (e.g., custom_listview. The second <item> element is used if the list item is presses but not selected. The third <item> element is used if the list item is pressed and selected (user has press and then removed finger from selection).
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/custom_listview_normal"
android:state_pressed="false"
android:state_selected="false">
</item>
<item android:drawable="@drawable/custom_listview_hover"
android:state_pressed="true">
</item>
<item android:drawable="@drawable/custom_listview_hover"
android:state_pressed="true"
android:state_selected="true">
</item>
</selector>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="5dp"
android:background="@drawable/custom_listview_selector">