How to make sticky section headers (like iOS) in Android?

sticky header android recyclerview
sticky header expandablelistview android
header recyclerview android library
sticky header recyclerview kotlin
epoxy sticky header
recyclerview header decoration
sticky scrollview android
android expandable recyclerview with sticky header

My specific question is: How I can achieve an effect like this: http://youtu.be/EJm7subFbQI

The bounce effect is not important, but i need the "sticky" effect for the headers. Where do I start?, In what can I base me? I need something that I can implement on API 8 to up.

Thanks.

Android ListView Sticky Header Like iphone, Do you hurry to use your own Sticky Header? If so, go to this address and use my library, but if you want to dig into what is happening behind the  Welcome to Android RecyclerView Sticky Header Like iphone Example. We are going to develop a recyclerview with pinned headers in this tutorial. You may have watched this concept of sticky or pinned header in iphone or ios. Sticky header will stick at the top side of the recyclerview.

EDIT: Had some free time to add the code of fully working example. Edited the answer accordingly.

For those who don't want to use 3rd party code (or cannot use it directly, e.g. in Xamarin), this could be done fairly easily by hand. The idea is to use another ListView for the header. This list view contains only the header items. It will not be scrollable by the user (setEnabled(false)), but will be scrolled from code based on main lists' scrolling. So you will have two lists - headerListview and mainListview, and two corresponding adapters headerAdapter and mainAdapter. headerAdapter only returns section views, while mainAdapter supports two view types (section and item). You will need a method that takes a position in the main list and returns a corresponding position in the sections list.

Main activity

public class MainActivity extends AppCompatActivity {

    public static final int TYPE_SECTION = 0;
    public static final int TYPE_ITEM = 1;

    ListView mainListView;
    ListView headerListView;
    MainAdapter mainAdapter;
    HeaderAdapter headerAdapter;

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

        mainListView = (ListView)findViewById(R.id.list);
        headerListView = (ListView)findViewById(R.id.header);
        mainAdapter = new MainAdapter();
        headerAdapter = new HeaderAdapter();

        headerListView.setEnabled(false);
        headerListView.setAdapter(headerAdapter);
        mainListView.setAdapter(mainAdapter);

        mainListView.setOnScrollListener(new AbsListView.OnScrollListener(){

            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState){

            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                // this should return an index in the headers list, based one the index in the main list. The logic for this is highly dependent on your data.
                int pos = mainAdapter.getSectionIndexForPosition(firstVisibleItem);
                // this makes sure our headerListview shows the proper section (the one on the top of the mainListview)
                headerListView.setSelection(pos);

                // this makes sure that headerListview is scrolled exactly the same amount as the mainListview
                if(mainAdapter.getItemViewType(firstVisibleItem + 1) == TYPE_SECTION){
                    headerListView.setSelectionFromTop(pos, mainListView.getChildAt(0).getTop());
                }
            }
        });
    }

    public class MainAdapter extends BaseAdapter{
        int count = 30;

        @Override
        public int getItemViewType(int position){
            if((float)position / 10 == (int)((float)position/10)){
                return TYPE_SECTION;
            }else{
                return TYPE_ITEM;
            }
        }

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

        @Override
        public int getCount() { return count - 1; }

        @Override
        public Object getItem(int position) { return null; }

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

        public int getSectionIndexForPosition(int position){ return position / 10; }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View v =  getLayoutInflater().inflate(R.layout.item, parent, false);
            position++;
            if(getItemViewType(position) == TYPE_SECTION){
                ((TextView)v.findViewById(R.id.text)).setText("SECTION "+position);

            }else{
                ((TextView)v.findViewById(R.id.text)).setText("Item "+position);
            }
            return v;
        }
    }

    public class HeaderAdapter extends BaseAdapter{
        int count = 5;

        @Override
        public int getCount() { return count; }

        @Override
        public Object getItem(int position) { return null; }

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

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View v =  getLayoutInflater().inflate(R.layout.item, parent, false);
            ((TextView)v.findViewById(R.id.text)).setText("SECTION "+position*10);
            return v;
        }
    }

}

A couple of things to note here. We do not want to show the very first section in the main view list, because it would produce a duplicate (it's already shown in the header). To avoid that, in your mainAdapter.getCount():

return actualCount - 1;

and make sure the first line in your getView() method is

position++;

This way your main list will be rendering all cells but the first one.

Another thing is that you want to make sure your headerListview's height matches the height of the list item. In this example the height is fixed, but it could be tricky if your items height is not set to an exact value in dp. Please refer to this answer for how to address this: https://stackoverflow.com/a/41577017/291688

Main layout

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    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/header"
        android:layout_width="match_parent"
        android:layout_height="48dp"/>

    <ListView
        android:id="@+id/list"
        android:layout_below="@+id/header"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</RelativeLayout>

Item / header layout

<?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="48dp">
    <TextView
        android:id="@+id/text"
        android:gravity="center_vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />

</LinearLayout>

Sticky Header For RecyclerView - Saber Solooki, StickyListHeaders is an Android library that makes it easy to integrate section headers in These section headers stick to the top like in the new People app of Android 4.0 Ice This behavior is also found in lists with sections on iOS devices​. Use a LinearLayout with vertical orientation. Add the header layout first. Add a ScrollView as the second child view/row. Set its lauout_weight to 1 so that the ScrollView occupies the remaining available height.

Add this in your app.gradle file

compile 'se.emilsjolander:StickyScrollViewItems:1.1.0'

then my layout, where I have added android:tag ="sticky" to specific views like textview or edittext not LinearLayout, looks like this. It also uses databinding, ignore that.

    <?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="temp"
            type="com.lendingkart.prakhar.lendingkartdemo.databindingmodel.BusinessDetailFragmentModel" />

        <variable
            name="presenter"
            type="com.lendingkart.prakhar.lendingkartdemo.presenters.BusinessDetailsPresenter" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">


        <com.lendingkart.prakhar.lendingkartdemo.customview.StickyScrollView
            android:id="@+id/sticky_scroll"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <!-- scroll view child goes here -->
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical">


                <android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    card_view:cardCornerRadius="5dp"
                    card_view:cardUseCompatPadding="true">

                    <LinearLayout
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:orientation="vertical">


                        <TextView
                            style="@style/group_view_text"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:background="@drawable/businessdetailtitletextviewbackground"
                            android:padding="@dimen/activity_horizontal_margin"
                            android:tag="sticky"
                            android:text="@string/business_contact_detail" />

                        <android.support.design.widget.TextInputLayout
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:layout_margin="7dp">

                            <android.support.design.widget.TextInputEditText
                                android:layout_width="match_parent"
                                android:layout_height="wrap_content"
                                android:hint="@string/comapnyLabel"
                                android:textSize="16sp" />

                        </android.support.design.widget.TextInputLayout>

                        <android.support.design.widget.TextInputLayout
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:layout_margin="5dp">

                            <android.support.design.widget.TextInputEditText
                                android:layout_width="match_parent"
                                android:layout_height="wrap_content"
                                android:hint="@string/contactLabel"
                                android:textSize="16sp" />

                        </android.support.design.widget.TextInputLayout>

                        <android.support.design.widget.TextInputLayout
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:layout_margin="5dp">

                            <android.support.design.widget.TextInputEditText
                                android:layout_width="match_parent"
                                android:layout_height="wrap_content"
                                android:hint="@string/emailLabel"
                                android:textSize="16sp" />

                        </android.support.design.widget.TextInputLayout>

                        <android.support.design.widget.TextInputLayout
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:layout_margin="5dp">

                            <android.support.design.widget.TextInputEditText
                                android:layout_width="match_parent"
                                android:layout_height="wrap_content"
                                android:hint="@string/NumberOfEmployee"
                                android:textSize="16sp" />

                        </android.support.design.widget.TextInputLayout>


                    </LinearLayout>
                </android.support.v7.widget.CardView>

                <android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    card_view:cardCornerRadius="5dp"
                    card_view:cardUseCompatPadding="true">

                    <TextView
                        style="@style/group_view_text"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:background="@drawable/businessdetailtitletextviewbackground"
                        android:padding="@dimen/activity_horizontal_margin"
                        android:tag="sticky"
                        android:text="@string/nature_of_business" />


                </android.support.v7.widget.CardView>

                <android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    card_view:cardCornerRadius="5dp"
                    card_view:cardUseCompatPadding="true">

                    <TextView
                        style="@style/group_view_text"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:background="@drawable/businessdetailtitletextviewbackground"
                        android:padding="@dimen/activity_horizontal_margin"
                        android:tag="sticky"
                        android:text="@string/taxation" />


                </android.support.v7.widget.CardView>



            </LinearLayout>
        </com.lendingkart.prakhar.lendingkartdemo.customview.StickyScrollView>


    </LinearLayout>
</layout>

style group for the textview looks this

 <style name="group_view_text" parent="@android:style/TextAppearance.Medium">
        <item name="android:layout_width">wrap_content</item>
        <item name="android:layout_height">wrap_content</item>
        <item name="android:textColor">@color/edit_text_color</item>
        <item name="android:textSize">16dp</item>
        <item name="android:layout_centerVertical">true</item>
        <item name="android:textStyle">bold</item>
    </style>

and the background for the textview goes like this:(@drawable/businessdetailtitletextviewbackground)

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape android:shape="rectangle">
            <solid android:color="@color/edit_text_color" />
        </shape>
    </item>
    <item android:bottom="2dp">
        <shape android:shape="rectangle">
            <solid android:color="@color/White" />
        </shape>
    </item>
</layer-list>

emilsjolander/StickyListHeaders: An android library for , just good for UX. Since the iOS RCTScrollView implements sticky headers (​instead of using something provided by the system) I imagine it's doable on Android as well. Would it make sense to incorporate something like  I think the cases for sticky headers on android are probably more narrow: e.g. the design of many lists that might have sticky headers on iOS wouldn't on Android in keeping consistent with the platform (e.g. compare the contacts app on the two platforms), but I can see apps on Android (the schedule view in the F8 app comes to mind) where this

You can reach this effect using SuperSLiM library. It provides you a LayoutManager for RecyclerView with interchangeable linear, grid, and staggered displays of views.

A good demo is located in github repository

It is simply to get such result

app:slm_headerDisplay="inline|sticky"
or
app:slm_headerDisplay="sticky"

[Android][ScrollView] Support sticky section headers on Android , I made a quick mockup of how I'd like it to look: http://i.imgur.com/jlQZ7YT.gifv sorry about the If I were to use single header lib and make separate Views for each section, of your app and I will create a beautiful mock-up for Android or iOS. Sticky header in recyclerview android sunil gupta. Android ListView Sticky Header Like iphone Pinned header Android App Development: Create Android List View with section headers

I have used one special class to achieve listview like iPhone. You can find example with source code here. https://demonuts.com/android-recyclerview-sticky-header-like-iphone/

This class which has updated listview is as

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();
        }
    }
}

XML usage

<?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>

How to make sectioned Listview with sticky headers? : androiddev, May 09, 2017 · Create Android Recyclerview adapters like a boss with is a list view with sections and with a cool iOS-like "sticky" section headers. Currently  In this example, we will create a recyclerview where all the section header have different layout structure than the normal child row items. Read to make section header in Listview. Let us go through all the steps. Step 1. Build.gradle(Module: app) We have to add gradle lines to import the required classes of recyclerview and cardview.

Sticky header recyclerview android, You can also make sticky ListView sections using this tutorial. stickyHeaderIndices={[0]} : This would make the header as sticky because its  Also note that the header won't show when a section has 0 items, while the space for it still shows. You could opt not showing the space for it, but alternatively you can just show the header even when the section has 0 items.

React Native Add Sticky Header to ListView Android iOS Example, My specific question is: How I can achieve an effect like this: are section headers and have come to be referred to as sticky section headers in Android. These section headers stick to the top like in the new People app of Android 4.0 Ice Cream Sandwich. This behavior is also found in lists with sections on iOS devices. This library can also be used without the sticky functionality if you just want section headers.

Listview header android, StickyListHeaders is an Android library that makes it easy to integrate section is a list view with sections and with a cool iOS-like "sticky" section headers. EDIT: Had some free time to add the code of fully working example. Edited the answer accordingly. For those who don't want to use 3rd party code (or cannot use it directly, e.g. in Xamarin), this could be done fairly easily by hand.

Comments
  • any luck for iOS ??
  • After trying the options I got here, the HeaderListView seems to be the best suited to my needs, and is more compatible than others. Thanks
  • I'm not able to access HeaderListView at github.io right now. Here is direct link to github.com: github.com/applidium/HeaderListView
  • Which ones support fast scroller?
  • It seems that HeaderListView doesn't support fast scrolling.
  • not even slow scrolling, it crashes as soon as section 2 gets started :(
  • added an example
  • @DennisK. You are like God to me. Thank you ssoooooooooo much man for this wonderful code. It really made my day. I was like 4 to 5 days searching for a solution and your answer worked in 5 minutes :)
  • This was very helpful, thanks! I had an issue with getting lots of these warnings: requestLayout() improperly called by android.widget.ListView. Turns out they were coming from the onScroll listener. For anyone else having the issue, moving the onScroll UI updates to a UI thread resolved the issue to me. More here: stackoverflow.com/questions/5065339/…
  • @DennisK Thanks you for your answer, but when i tried to set headerListView.addHeaderView(view); its replacing first item from headerListView. How can i achieve headerView above sectionHeader?