Android ListView Sticky Header Like iphone | Pinned header listview

android listview sticky header like iphone, android recyclerview sticky header like iphone, android listview with header and footer, android recyclerview with header and footer

We will develop Android ListView Sticky Header Like iphone example in this tutorial.

Sometimes you want to make pinned header listview in android app.

This example will guide you to make sticky or pinned header for listview in android studio.

Pinned header means that a header which have certain number of child rows and this header sticks at the top of the ListView.

Then, again there will be another sticky header which will have certain number of children and it replaces old header.

You may have notice this type of listview in iphone or ios.

Android does not have any built in method or class to achieve this type of user interface for listview.

We need to customize out traditional listview and adapter class for this purpose.

Last Views For Header

Step 1. Adding Colors

I will separate the headers and children with different colors.

Also, different headers may have different colors.

So let us add some colors in res->values->colors.xml file.

Write the following lines in res->values->colors.xml file.

 <color name="holo_blue_light">#ff33b5e5</color>
    <color name="holo_green_light">#ff99cc00</color>
    <color name="holo_red_light">#ffff4444</color>
    <color name="holo_orange_light">#ffffbb33</color>

I have added blue, green, red and orange colors.

You can add many more colors as per your requirements.

Step 2. Adding Image

In this example we need to add one image in the drawable folder.

Download this image by clicking the below link

[sociallocker]Download scrollbarlight Image[/sociallocker]

After downloading the image, add it into res->drawable directory.

We need this image in HeaderListView class which we will create in the next step.

Step 3. Preparing HeaderListView And Section Adapter Class

Create a new JAVA class and give it a name HeaderListView.java

Copy the below code block in HeaderListView.java class

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.ListView;
import android.widget.RelativeLayout;

public class HeaderListView extends RelativeLayout {

    // TODO: Handle listViews with fast scroll
    // TODO: See if there are methods to dispatch to mListView

    private static final int FADE_DELAY    = 1000;
    private static final int FADE_DURATION = 2000;

    private InternalListView mListView;
    private SectionAdapter   mAdapter;
    private RelativeLayout   mHeader;
    private View             mHeaderConvertView;
    private FrameLayout      mScrollView;
    private AbsListView.OnScrollListener mExternalOnScrollListener;

    public HeaderListView(Context context) {
        super(context);
        init(context, null);
    }

    public HeaderListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        mListView = new InternalListView(getContext(), attrs);
        LayoutParams listParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        listParams.addRule(ALIGN_PARENT_TOP);
        mListView.setLayoutParams(listParams);
        mListView.setOnScrollListener(new HeaderListViewOnScrollListener());
        mListView.setVerticalScrollBarEnabled(false);
        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                if (mAdapter != null) {
                    mAdapter.onItemClick(parent, view, position, id);
                }
            }
        });
        addView(mListView);

        mHeader = new RelativeLayout(getContext());
        LayoutParams headerParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        headerParams.addRule(ALIGN_PARENT_TOP);
        mHeader.setLayoutParams(headerParams);
        mHeader.setGravity(Gravity.BOTTOM);
        addView(mHeader);

        // The list view's scroll bar can be hidden by the header, so we display our own scroll bar instead
        Drawable scrollBarDrawable = getResources().getDrawable(R.drawable.scrollbar_handle_holo_light);
        mScrollView = new FrameLayout(getContext());
        LayoutParams scrollParams = new LayoutParams(scrollBarDrawable.getIntrinsicWidth(), LayoutParams.MATCH_PARENT);
        scrollParams.addRule(ALIGN_PARENT_RIGHT);
        scrollParams.rightMargin = (int) dpToPx(2);
        mScrollView.setLayoutParams(scrollParams);

        ImageView scrollIndicator = new ImageView(context);
        scrollIndicator.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
        scrollIndicator.setImageDrawable(scrollBarDrawable);
        scrollIndicator.setScaleType(ScaleType.FIT_XY);
        mScrollView.addView(scrollIndicator);
        mScrollView.setVisibility(INVISIBLE);

        addView(mScrollView);
    }

    public void setAdapter(SectionAdapter adapter) {
        mAdapter = adapter;
        mListView.setAdapter(adapter);
    }

    public void setOnScrollListener(AbsListView.OnScrollListener l) {
        mExternalOnScrollListener = l;
    }

    private class HeaderListViewOnScrollListener implements AbsListView.OnScrollListener {

        private int            previousFirstVisibleItem = -1;
        private int            direction                = 0;
        private int            actualSection            = 0;
        private boolean        scrollingStart           = false;
        private boolean        doneMeasuring            = false;
        private int            lastResetSection         = -1;
        private int            nextH;
        private int            prevH;
        private View           previous;
        private View           next;
        private AlphaAnimation fadeOut                  = new AlphaAnimation(1f, 0f);
        private boolean        noHeaderUpToHeader       = false;
        private boolean        didScroll = false;

        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            if (mExternalOnScrollListener != null) {
                mExternalOnScrollListener.onScrollStateChanged(view, scrollState);
            }
            didScroll = true;
        }

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
            if (mExternalOnScrollListener != null) {
                mExternalOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
            }

            if (!didScroll) {
                return;
            }

            firstVisibleItem -= mListView.getHeaderViewsCount();
            if (firstVisibleItem < 0) {
                mHeader.removeAllViews();
                return;
            }

            updateScrollBar();
            if (visibleItemCount > 0 && firstVisibleItem == 0 && mHeader.getChildAt(0) == null) {
                addSectionHeader(0);
                lastResetSection = 0;
            }

            int realFirstVisibleItem = getRealFirstVisibleItem(firstVisibleItem, visibleItemCount);
            if (totalItemCount > 0 && previousFirstVisibleItem != realFirstVisibleItem) {
                direction = realFirstVisibleItem - previousFirstVisibleItem;

                actualSection = mAdapter.getSection(realFirstVisibleItem);

                boolean currIsHeader = mAdapter.isSectionHeader(realFirstVisibleItem);
                boolean prevHasHeader = mAdapter.hasSectionHeaderView(actualSection - 1);
                boolean nextHasHeader = mAdapter.hasSectionHeaderView(actualSection + 1);
                boolean currHasHeader = mAdapter.hasSectionHeaderView(actualSection);
                boolean currIsLast = mAdapter.getRowInSection(realFirstVisibleItem) == mAdapter.numberOfRows(actualSection) - 1;
                boolean prevHasRows = mAdapter.numberOfRows(actualSection - 1) > 0;
                boolean currIsFirst = mAdapter.getRowInSection(realFirstVisibleItem) == 0;

                boolean needScrolling = currIsFirst && !currHasHeader && prevHasHeader && realFirstVisibleItem != firstVisibleItem;
                boolean needNoHeaderUpToHeader = currIsLast && currHasHeader && !nextHasHeader && realFirstVisibleItem == firstVisibleItem && Math.abs(mListView.getChildAt(0).getTop()) >= mListView.getChildAt(0).getHeight() / 2;
                
                noHeaderUpToHeader = false;
                if (currIsHeader && !prevHasHeader && firstVisibleItem >= 0) {
                    resetHeader(direction < 0 ? actualSection - 1 : actualSection);
                } else if ((currIsHeader && firstVisibleItem > 0) || needScrolling) {
                    if (!prevHasRows) {
                        resetHeader(actualSection-1);
                    }
                    startScrolling();
                } else if (needNoHeaderUpToHeader) {
                    noHeaderUpToHeader = true;
                } else if (lastResetSection != actualSection) {
                    resetHeader(actualSection);
                }

                previousFirstVisibleItem = realFirstVisibleItem;
            }

            if (scrollingStart) {
                int scrolled = realFirstVisibleItem >= firstVisibleItem ? mListView.getChildAt(realFirstVisibleItem - firstVisibleItem).getTop() : 0;

                if (!doneMeasuring) {
                    setMeasurements(realFirstVisibleItem, firstVisibleItem);
                }

                int headerH = doneMeasuring ? (prevH - nextH) * direction * Math.abs(scrolled) / (direction < 0 ? nextH : prevH) + (direction > 0 ? nextH : prevH) : 0;

                mHeader.scrollTo(0, -Math.min(0, scrolled - headerH));
                if (doneMeasuring && headerH != mHeader.getLayoutParams().height) {
                    LayoutParams p = (LayoutParams) (direction < 0 ? next.getLayoutParams() : previous.getLayoutParams());
                    p.topMargin = headerH - p.height;
                    mHeader.getLayoutParams().height = headerH;
                    mHeader.requestLayout();
                }
            }

            if (noHeaderUpToHeader) {
                if (lastResetSection != actualSection) {
                    addSectionHeader(actualSection);
                    lastResetSection = actualSection + 1;
                }
                mHeader.scrollTo(0, mHeader.getLayoutParams().height - (mListView.getChildAt(0).getHeight() + mListView.getChildAt(0).getTop()));
            }
        }

        private void startScrolling() {
            scrollingStart = true;
            doneMeasuring = false;
            lastResetSection = -1;
        }

        private void resetHeader(int section) {
            scrollingStart = false;
            addSectionHeader(section);
            mHeader.requestLayout();
            lastResetSection = section;
        }

        private void setMeasurements(int realFirstVisibleItem, int firstVisibleItem) {

            if (direction > 0) {
                nextH = realFirstVisibleItem >= firstVisibleItem ? mListView.getChildAt(realFirstVisibleItem - firstVisibleItem).getMeasuredHeight() : 0;
            }

            previous = mHeader.getChildAt(0);
            prevH = previous != null ? previous.getMeasuredHeight() : mHeader.getHeight();

            if (direction < 0) {
                if (lastResetSection != actualSection - 1) {
                    addSectionHeader(Math.max(0, actualSection - 1));
                    next = mHeader.getChildAt(0);
                }
                nextH = mHeader.getChildCount() > 0 ? mHeader.getChildAt(0).getMeasuredHeight() : 0;
                mHeader.scrollTo(0, prevH);
            }
            doneMeasuring = previous != null && prevH > 0 && nextH > 0;
        }

        private void updateScrollBar() {
            if (mHeader != null && mListView != null && mScrollView != null) {
                int offset = mListView.computeVerticalScrollOffset();
                int range = mListView.computeVerticalScrollRange();
                int extent = mListView.computeVerticalScrollExtent();
                mScrollView.setVisibility(extent >= range ? View.INVISIBLE : View.VISIBLE);
                if (extent >= range) {
                    return;
                }
                int top = range == 0 ? mListView.getHeight() : mListView.getHeight() * offset / range;
                int bottom = range == 0 ? 0 : mListView.getHeight() - mListView.getHeight() * (offset + extent) / range;
                mScrollView.setPadding(0, top, 0, bottom);
                fadeOut.reset();
                fadeOut.setFillBefore(true);
                fadeOut.setFillAfter(true);
                fadeOut.setStartOffset(FADE_DELAY);
                fadeOut.setDuration(FADE_DURATION);
                mScrollView.clearAnimation();
                mScrollView.startAnimation(fadeOut);
            }
        }

        private void addSectionHeader(int actualSection) {
            View previousHeader = mHeader.getChildAt(0);
            if (previousHeader != null) {
                mHeader.removeViewAt(0);
            }

            if (mAdapter.hasSectionHeaderView(actualSection)) {
                mHeaderConvertView = mAdapter.getSectionHeaderView(actualSection, mHeaderConvertView, mHeader);
                mHeaderConvertView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));

                mHeaderConvertView.measure(MeasureSpec.makeMeasureSpec(mHeader.getWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));

                mHeader.getLayoutParams().height = mHeaderConvertView.getMeasuredHeight();
                mHeaderConvertView.scrollTo(0, 0);
                mHeader.scrollTo(0, 0);
                mHeader.addView(mHeaderConvertView, 0);
            } else {
                mHeader.getLayoutParams().height = 0;
                mHeader.scrollTo(0, 0);
            }

            mScrollView.bringToFront();
        }

        private int getRealFirstVisibleItem(int firstVisibleItem, int visibleItemCount) {
            if (visibleItemCount == 0) {
                return -1;
            }
            int relativeIndex = 0, totalHeight = mListView.getChildAt(0).getTop();
            for (relativeIndex = 0; relativeIndex < visibleItemCount && totalHeight < mHeader.getHeight(); relativeIndex++) {
                totalHeight += mListView.getChildAt(relativeIndex).getHeight();
            }
            int realFVI = Math.max(firstVisibleItem, firstVisibleItem + relativeIndex - 1);
            return realFVI;
        }
    }

    public ListView getListView() {
        return mListView;
    }
    
    public void addHeaderView(View v) {
        mListView.addHeaderView(v);
    }

    private float dpToPx(float dp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getContext().getResources().getDisplayMetrics());
    }

    protected class InternalListView extends ListView {

        public InternalListView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }

        @Override
        protected int computeVerticalScrollExtent() {
            return super.computeVerticalScrollExtent();
        }

        @Override
        protected int computeVerticalScrollOffset() {
            return super.computeVerticalScrollOffset();
        }

        @Override
        protected int computeVerticalScrollRange() {
            return super.computeVerticalScrollRange();
        }
    }
}

This class helps us to make a special listview which can support the sticky header.

Above class is the transformation from traditional listview to our desired listview.

Now make another new JAVA class named SectionAdapter.java

Write down the following Coding lines in SectionAdapter.java

import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;

public abstract class SectionAdapter extends BaseAdapter implements OnItemClickListener {

    private int mCount = -1;

    public abstract int numberOfSections();

    public abstract int numberOfRows(int section);

    public abstract View getRowView(int section, int row, View convertView, ViewGroup parent);

    public abstract Object getRowItem(int section, int row);

    public boolean hasSectionHeaderView(int section) {
        return false;
    }

    public View getSectionHeaderView(int section, View convertView, ViewGroup parent) {
        return null;
    }

    public Object getSectionHeaderItem(int section) {
        return null;
    }

    public int getRowViewTypeCount() {
        return 1;
    }

    public int getSectionHeaderViewTypeCount() {
        return 1;
    }

    /**
     * Must return a value between 0 and getRowViewTypeCount() (excluded)
     */
    public int getRowItemViewType(int section, int row) {
        return 0;
    }

    /**
     * Must return a value between 0 and getSectionHeaderViewTypeCount() (excluded, if > 0)
     */
    public int getSectionHeaderItemViewType(int section) {
        return 0;
    }

    @Override
    /**
     * Dispatched to call onRowItemClick
     */
    public final void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        onRowItemClick(parent, view, getSection(position), getRowInSection(position), id);
    }

    public void onRowItemClick(AdapterView<?> parent, View view, int section, int row, long id) {

    }

    @Override
    /**
     * Counts the amount of cells = headers + rows
     */
    public final int getCount() {
        if (mCount < 0) {
            mCount = numberOfCellsBeforeSection(numberOfSections());
        }
        return mCount;
    }

    @Override
    public boolean isEmpty() {
        return getCount() == 0;
    }

    @Override
    /**
     * Dispatched to call getRowItem or getSectionHeaderItem
     */
    public final Object getItem(int position) {
        int section = getSection(position);
        if (isSectionHeader(position)) {
            if (hasSectionHeaderView(section)) {
                return getSectionHeaderItem(section);
            }
            return null;
        }
        return getRowItem(section, getRowInSection(position));
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    /**
     * Dispatched to call getRowView or getSectionHeaderView
     */
    public final View getView(int position, View convertView, ViewGroup parent) {
        int section = getSection(position);
        if (isSectionHeader(position)) {
            if (hasSectionHeaderView(section)) {
                return getSectionHeaderView(section, convertView, parent);
            }
            return null;
        }
        return getRowView(section, getRowInSection(position), convertView, parent);
    }

    /**
     * Returns the section number of the indicated cell
     */
    protected int getSection(int position) {
        int section = 0;
        int cellCounter = 0;
        while (cellCounter <= position && section <= numberOfSections()) {
            cellCounter += numberOfCellsInSection(section);
            section++;
        }
        return section - 1;
    }

    /**
     * Returns the row index of the indicated cell Should not be call with
     * positions directing to section headers
     */
    protected int getRowInSection(int position) {
        int section = getSection(position);
        int row = position - numberOfCellsBeforeSection(section);
        if (hasSectionHeaderView(section)) {
            return row - 1;
        } else {
            return row;
        }
    }

    /**
     * Returns true if the cell at this index is a section header
     */
    protected boolean isSectionHeader(int position) {
        int section = getSection(position);
        return hasSectionHeaderView(section) && numberOfCellsBeforeSection(section) == position;
    }

    /**
     * Returns the number of cells (= headers + rows) before the indicated
     * section
     */
    protected int numberOfCellsBeforeSection(int section) {
        int count = 0;
        for (int i = 0; i < Math.min(numberOfSections(), section); i++) {
            count += numberOfCellsInSection(i);
        }
        return count;
    }

    private int numberOfCellsInSection(int section) {
        return numberOfRows(section) + (hasSectionHeaderView(section) ? 1 : 0);
    }

    @Override
    public void notifyDataSetChanged() {
        mCount = numberOfCellsBeforeSection(numberOfSections());
        super.notifyDataSetChanged();
    }

    @Override
    public void notifyDataSetInvalidated() {
        mCount = numberOfCellsBeforeSection(numberOfSections());
        super.notifyDataSetInvalidated();
    }

    @Override
    /**
     * Dispatched to call getRowItemViewType or getSectionHeaderItemViewType
     */
    public final int getItemViewType(int position) {
        int section = getSection(position);
        if (isSectionHeader(position)) {
            return getRowViewTypeCount() + getSectionHeaderItemViewType(section);
        } else {
            return getRowItemViewType(section, getRowInSection(position));
        }
    }

    @Override
    /**
     * Dispatched to call getRowViewTypeCount and getSectionHeaderViewTypeCount
     */
    public final int getViewTypeCount() {
        return getRowViewTypeCount() + getSectionHeaderViewTypeCount();
    }

    @Override
    /**
     * By default, disables section headers
     */
    public boolean isEnabled(int position) {
        return (disableHeaders() || !isSectionHeader(position)) && isRowEnabled(getSection(position), getRowInSection(position));
    }

    public boolean disableHeaders() {
        return false;
    }

    public boolean isRowEnabled(int section, int row) {
        return true;
    }
}

For special listview, we also need to have special adapter with it.

Above class is making that special adapter for our special listview.

We will extend this adapter while creating the actual adapter for our listview.

Step 4. Ios Adapter

Let us create an adapter what will populate out listview with pinned header.

Make a new class and give it a name IosAdapter.java

Following is the code structure for IosAdapter.java class

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.TextView;
import android.widget.Toast;

public class IosAdapter extends SectionAdapter {

    private Context context;

    public IosAdapter(Context context) {

        this.context = context;
    }

    @Override
    public int numberOfSections() {
        return 4; //return the total number of headers
    }

    @Override
    public int numberOfRows(int section) {
        return 5; //return the number of children for particular header
    }

    @Override
    public Object getRowItem(int section, int row) {
        return null;
    }

    @Override
    public boolean hasSectionHeaderView(int section) {
        return true;
    }

    @Override
    public View getRowView(int section, int row, View convertView, ViewGroup parent) {

        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        if (convertView == null) {
            convertView = (TextView) inflater.inflate(context.getResources().getLayout(android.R.layout.simple_list_item_1), null);
        }
        ((TextView) convertView).setText("Section " + section + " Row " + row);
        return convertView;
    }

    @Override
    public int getSectionHeaderViewTypeCount() {
        return 2;
    }

    @Override
    public int getSectionHeaderItemViewType(int section) {
        return section % 2;
    }

    @Override
    public View getSectionHeaderView(int section, View convertView, ViewGroup parent) {

        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (convertView == null) {
            if (getSectionHeaderItemViewType(section) == 0) {
                convertView = (TextView) inflater.inflate(context.getResources().getLayout(android.R.layout.simple_list_item_1), null);
            } else {
                convertView = inflater.inflate(context.getResources().getLayout(android.R.layout.simple_list_item_2), null);
            }
        }

        if (getSectionHeaderItemViewType(section) == 0) {
            ((TextView) convertView).setText("Header for section " + section);
        } else {
            ((TextView) convertView.findViewById(android.R.id.text1)).setText("Header for section " + section);
            //  ((TextView) convertView.findViewById(android.R.id.text2)).setText("Has a detail text field");
        }

        switch (section) {
            case 0:
                convertView.setBackgroundColor(context.getResources().getColor(R.color.holo_red_light));
                break;
            case 1:
                convertView.setBackgroundColor(context.getResources().getColor(R.color.holo_orange_light));
                break;
            case 2:
                convertView.setBackgroundColor(context.getResources().getColor(R.color.holo_green_light));
                break;
            case 3:
                convertView.setBackgroundColor(context.getResources().getColor(R.color.holo_blue_light));
                break;
        }
        return convertView;
    }

    @Override
    public void onRowItemClick(AdapterView<?> parent, View view, int section, int row, long id) {
        super.onRowItemClick(parent, view, section, row, id);
        Toast.makeText(context, "Section " + section + " Row " + row, Toast.LENGTH_SHORT).show();
    }

}

Looking Deeply

Let us look what the above class IosAdapter will do actually.

Consider below code

 @Override
    public int numberOfSections() {
        return 4; //return the total number of headers
    }
  • This method will return the total number of sticky or pinned headers.

Look the following code

 @Override
    public int numberOfRows(int section) {
        return 5; //return the number of children for particular header
    }

Above method is returning the total number of child for ant specific header.

Number of header is at the parameter (int section)

Above method will return 5 as the number of children for header at the position (int section)

getRowView()

code for above method

 @Override
    public View getRowView(int section, int row, View convertView, ViewGroup parent) {

        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        if (convertView == null) {
            convertView = (TextView) inflater.inflate(context.getResources().getLayout(android.R.layout.simple_list_item_1), null);
        }
        ((TextView) convertView).setText("Section " + section + " Row " + row);
        return convertView;
    }

Above method will generate the view for the children items.

We will also set the text for children in this method.

Address of child can be like a child of the header at -> position section and a child position for that header is -> int row

Following method getSectionHeaderView() is making a layout for header

  @Override
    public View getSectionHeaderView(int section, View convertView, ViewGroup parent) {

        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (convertView == null) {
            if (getSectionHeaderItemViewType(section) == 0) {
                convertView = (TextView) inflater.inflate(context.getResources().getLayout(android.R.layout.simple_list_item_1), null);
            } else {
                convertView = inflater.inflate(context.getResources().getLayout(android.R.layout.simple_list_item_2), null);
            }
        }

        if (getSectionHeaderItemViewType(section) == 0) {
            ((TextView) convertView).setText("Header for section " + section);
        } else {
            ((TextView) convertView.findViewById(android.R.id.text1)).setText("Header for section " + section);
            //  ((TextView) convertView.findViewById(android.R.id.text2)).setText("Has a detail text field");
        }

        switch (section) {
            case 0:
                convertView.setBackgroundColor(context.getResources().getColor(R.color.holo_red_light));
                break;
            case 1:
                convertView.setBackgroundColor(context.getResources().getColor(R.color.holo_orange_light));
                break;
            case 2:
                convertView.setBackgroundColor(context.getResources().getColor(R.color.holo_green_light));
                break;
            case 3:
                convertView.setBackgroundColor(context.getResources().getColor(R.color.holo_blue_light));
                break;
        }
        return convertView;
    }

First parameter of getSectionHeaderView() method is the position (int section) for header.

We will set the name of the header in this method.

I have also set the background colors in this method using switch statement.

Handling Click Events

To handle the click events of the listview, review the below method

 @Override
    public void onRowItemClick(AdapterView<?> parent, View view, int section, int row, long id) {
        super.onRowItemClick(parent, view, section, row, id);
        Toast.makeText(context, "Section " + section + " Row " + row, Toast.LENGTH_SHORT).show();
    }

You can have both header and row position in this click event.

Step 5. Main Updates

Now last thing is to update activity_main.xml and MainActivity.java files.

Write the following coding lines in activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.example.parsaniahardik.listview_stickyheader_ios.HeaderListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/lv">

    </com.example.parsaniahardik.listview_stickyheader_ios.HeaderListView>

</android.support.constraint.ConstraintLayout>

As you can see in the above code, I have used HeaderListView class to define listview.

It is referencing that HeaderListView which we have created in the step 3.

Here, in this line <com.example.parsa………ios>, you need to replace it with the package name of your android studio code.

You can find your package name in AndroidManifest.xml file.

Now make following source code in MainActivity.java

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    private IosAdapter iosAdapter;

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        HeaderListView headerListView = findViewById(R.id.lv);

        iosAdapter = new IosAdapter(this);
        headerListView.setAdapter(iosAdapter);

    }
}

In this main class, we just need to set up the listview with proper adapter.

We will set the object of IosAdapter class to the listview.

Download Source Code For Android ListView Sticky Header Like iphone

[sociallocker]Download Source Code For ListView_StickyHeader_IOS[/sociallocker]