Header ListView using BaseAdapter – Android

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 NameListViewHeader
Package Namecom.pcsalt.example.listviewheader
Minimum SDK Version16
Activity TypeEmpty Activity
Activity NameMainActivity
Layout Nameactivity_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.
Download source code from GitHub