In the previous post, ListView using BaseAdapter – Android we learned to create a list with custom views. In this post, we will create a Header ListView using BaseAdapter.
Advice
Before proceeding with this tutorial, we would like to share one piece of advice. If it is a problem only a ListView can solve, then only use ListView. Android has provided a new component RecyclerView. It is similar to ListView implementation. But, have many modifications for memory management. In addition to that, RecyclerView also provides animations for transitions like addition, removal and update of the list item. For its implementation, we have provided a tutorial RecyclerView – Android.
Create Header ListView using BaseAdapter
Create Android Application Project
If you are new to Android Studio, then you could learn about creating new projects from our tutorial Create Android Application Project in Android Studio Continued.
Create Android application project with following attributes.
Project Name | ListViewHeader |
---|---|
Package Name | com.pcsalt.example.listviewheader |
Minimum SDK Version | 16 |
Activity Type | Empty Activity |
Activity Name | MainActivity |
Layout Name | activity_name |
Prepare layout screens
activity_main.xml: Layout for MainActivity.java. For this demo application, it would contain only a ListView.
<?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="vertical" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin"> <ListView android:id="@+id/list_view" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout>
layout_list_item.xml: Layout for list items to be displayed in ListView. In this list, each list item will display two TextView.
<?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="vertical" android:padding="5dp"> <TextView android:id="@+id/text_view_message" style="@style/TextAppearance.AppCompat.Body1" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/text_view_message_line_2" style="@style/TextAppearance.AppCompat.Body2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingTop="5dp" /> </LinearLayout>
layout_list_section.xml: Layout for list sections header. The header will display the title in center.
<?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:gravity="center" android:orientation="vertical" android:padding="10dp"> <TextView android:id="@+id/text_view_title" style="@style/TextAppearance.AppCompat.Title" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
Create Model classes
Generally, for a normal list, we had a uniform list of a given type. But, for sectioned list, we have to provide all the section title and corresponding section list items. To achieve this, we would create a parent ListItem which would help us in creating a List of uniform items.
Create parent of List items: ListData
This class would be the parent of all the elements listed in a ListView. It will be used for tagging purpose only. If you can think of any functionality, which would be common for both, headers and list item, then feel free to add in it.
public class ListData { }
Create List Header items: ListSection
This class would be used to populate the Header of List. Currently, this contains only a title in the center, but many other fields could be added to fulfill the requirement of the problem.
public class ListSection extends ListData { String title; public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } }
Create List item: ListItem
To populate the list items, this model class would be used. In case, if list items use a different type of elements create a separate subclass of ListData and use them. Instead of adding all elements in a single class and using if…else to show/hide the View.
public class ListItem extends ListData { String message, messageLine2; public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public String getMessageLine2() { return messageLine2; } public void setMessageLine2(String messageLine2) { this.messageLine2 = messageLine2; } }
For this demo, we have all the model classes we needed. Now, we will customize BaseAdapter to display the list.
Subclass BaseAdapter to customize ListView
Since, we are going to create a list with different types of views. The adapter needs same instance check. Otherwise, it would throw ClassCastException.
Subclass BaseAdapter and implement abstract methods
Subclassing BaseAdapter and implementing its abstract methods are same as described in earlier post ListView using BaseAdapter – Android. The difference is in displaying different View types in the ListView.
Create all view type constants
Displaying list item of different types, BaseAdapter provides a method getItemViewType().
This method gets called every time getView()
gets called. It accepts a position of the view in the list and returns the type of view it is. To get the exact ViewType, match the instance of the list item and return the type of View. For this, create constants for each ViewType including NONE.
private static final int VIEW_TYPE_NONE = 0; private static final int VIEW_TYPE_SECTION = 1; private static final int VIEW_TYPE_ITEM = 2;
Now, overload getItemViewType()
and return expected ViewType after instance match.
@Override public int getItemViewType(int position) { if (getCount() > 0) { ListData listData = getItem(position); if (listData instanceof ListSection) { return VIEW_TYPE_SECTION; } else if (listData instanceof ListItem) { return VIEW_TYPE_ITEM; } else { return VIEW_TYPE_NONE; } } else { return VIEW_TYPE_NONE; } }
Inflate view for Header and List item
In getView()
check the ViewType and inflate the corresponding layout.
@Override public View getView(int position, View convertView, ViewGroup parent) { if (getItemViewType(position) == VIEW_TYPE_SECTION) { return getSectionView(position, convertView, parent); } else if (getItemViewType(position) == VIEW_TYPE_ITEM) { return getItemView(position, convertView, parent); } return null; }
Complete code of BaseAdapter
package com.pcsalt.example.listviewheader.adapter; 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 androidx.annotation.NonNull; import com.pcsalt.example.listviewheader.R; import com.pcsalt.example.listviewheader.model.ListData; import com.pcsalt.example.listviewheader.model.ListItem; import com.pcsalt.example.listviewheader.model.ListSection; import java.util.List; public class SectionedBaseAdapter extends BaseAdapter { private static final int VIEW_TYPE_NONE = 0; private static final int VIEW_TYPE_SECTION = 1; private static final int VIEW_TYPE_ITEM = 2; private LayoutInflater layoutInflater; private List<ListData> dataList; public SectionedBaseAdapter(Context context, List<ListData> dataList) { this.dataList = dataList; this.layoutInflater = LayoutInflater.from(context); } @Override public int getCount() { return dataList != null ? dataList.size() : 0; } @Override public ListData getItem(int position) { if (dataList.isEmpty()) { return null; } else { return dataList.get(position); } } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { if (getItemViewType(position) == VIEW_TYPE_SECTION) { return getSectionView(position, convertView, parent); } else if (getItemViewType(position) == VIEW_TYPE_ITEM) { return getItemView(position, convertView, parent); } return null; } @NonNull private View getItemView(int position, View convertView, ViewGroup parent) { ItemViewHolder itemViewHolder; if (convertView == null) { convertView = layoutInflater.inflate(R.layout.layout_list_item, parent, false); itemViewHolder = new ItemViewHolder(convertView); convertView.setTag(itemViewHolder); } else { itemViewHolder = (ItemViewHolder) convertView.getTag(); } ListItem listItem = (ListItem) getItem(position); itemViewHolder.setMessage(listItem.getMessage()); itemViewHolder.setMessageLine2(listItem.getMessageLine2()); return convertView; } @NonNull private View getSectionView(int position, View convertView, ViewGroup parent) { SectionViewHolder sectionViewHolder; if (convertView == null) { convertView = layoutInflater.inflate(R.layout.layout_list_section, parent, false); sectionViewHolder = new SectionViewHolder(convertView); convertView.setTag(sectionViewHolder); } else { sectionViewHolder = (SectionViewHolder) convertView.getTag(); } sectionViewHolder.setTitle(((ListSection) getItem(position)).getTitle()); return convertView; } @Override public int getItemViewType(int position) { if (getCount() > 0) { ListData listData = getItem(position); if (listData instanceof ListSection) { return VIEW_TYPE_SECTION; } else if (listData instanceof ListItem) { return VIEW_TYPE_ITEM; } else { return VIEW_TYPE_NONE; } } else { return VIEW_TYPE_NONE; } } @Override public int getViewTypeCount() { return 3; } class SectionViewHolder { TextView tvTitle; public SectionViewHolder(View itemView) { tvTitle = (TextView) itemView.findViewById(R.id.text_view_title); } public void setTitle(String title) { tvTitle.setText(title); } } class ItemViewHolder { TextView tvMessage, tvMessageLine2; public ItemViewHolder(View itemView) { tvMessage = (TextView) itemView.findViewById(R.id.text_view_message); tvMessageLine2 = (TextView) itemView.findViewById(R.id.text_view_message_line_2); } public void setMessage(String message) { tvMessage.setText(message); } public void setMessageLine2(String messageLine2) { tvMessageLine2.setText(messageLine2); } } }
Populate data in ListView
For demo purpose, a static list with few headers and list items would be created. This list will be displayed in a ListView
package com.pcsalt.example.listviewheader; import android.os.Bundle; import android.widget.ListView; import androidx.appcompat.app.AppCompatActivity; import com.pcsalt.example.listviewheader.adapter.SectionedBaseAdapter; import com.pcsalt.example.listviewheader.model.ListData; import com.pcsalt.example.listviewheader.model.ListItem; import com.pcsalt.example.listviewheader.model.ListSection; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { ListView listView; List<ListData> dataList = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initViews(); prepareContent(); setAdapter(); } private void initViews() { listView = (ListView) findViewById(R.id.list_view); } private void prepareContent() { for (int i = 0; i < 100; i++) { if (i % 7 == 0) { ListSection listSection = new ListSection(); listSection.setTitle("Title: " + i); dataList.add(listSection); } else { ListItem listItem = new ListItem(); listItem.setMessage(i + " % 7 == 1"); listItem.setMessageLine2("" + (i % 7 == 1)); dataList.add(listItem); } } } private void setAdapter() { SectionedBaseAdapter adapter = new SectionedBaseAdapter(MainActivity.this, dataList); listView.setAdapter(adapter); } }
Download Source Code
The complete source code is pushed in GitHub repo. Browse it by clicking on the octocat icon.