Sticky Header RecyclerView in Android

In my last article we have leaned about to Fast scrolling in recyclerView. If you have not seen Please check this link Fast Scroll RecyclerView.

In this article I am focusing the sticky header in recyclerView. Now days people are using very complex layout and design. In one place they want every things to show up. I mean, I am talking about the Section recyclerView.  In one sections having many items in recyclerView. Now the user wants to section header would be sticky at the time of recyclerView Scrolling.

This article will  helps to you implement this type of requirement. There are many numbers of library available to build this. I really appreciate to those author who make our life easy. So full credit goes to them. In one of them I am picking one library that implements is very simple and easy to help, Here is the detail Sticky Header.
Here is the video uploaded that help to you to understand the sticky header implements.


Lets create an android project for Sticky header in recyclerView.

build.gradle app level

apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.0"
    defaultConfig {
        applicationId "com.sunil.stickyheaderrecyclerview"
        minSdkVersion 15
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.0.0'
    testCompile 'junit:junit:4.12'

    compile 'com.android.support:recyclerview-v7:25.0.0'
    compile 'com.android.support:cardview-v7:25.0.0'
    compile 'com.android.support:design:25.0.0'

    compile 'com.jakewharton:butterknife:8.4.0'
    apt 'com.jakewharton:butterknife-compiler:8.4.0'

    compile 'com.github.bumptech.glide:glide:3.7.0'
    compile 'de.hdodenhof:circleimageview:2.0.0'
    compile 'com.android.support:percent:25.0.0'
    compile 'com.timehop.stickyheadersrecyclerview:library:0.4.3'
}

ItemModel.java

public class ItemModel {

    private String name;
    private String imagePath;

    public ItemModel(){

    }

    public ItemModel(String name, String imagePath) {
        this.name = name;
        this.imagePath = imagePath;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getImagePath() {
        return imagePath;
    }

    public void setImagePath(String imagePath) {
        this.imagePath = imagePath;
    }
}

MainActivity.java

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;

import com.timehop.stickyheadersrecyclerview.StickyRecyclerHeadersDecoration;
import com.timehop.stickyheadersrecyclerview.StickyRecyclerHeadersTouchListener;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import butterknife.BindView;
import butterknife.ButterKnife;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = MainActivity.class.getSimpleName();

    private String[] names = Constant.name;
    private  String[] images = Constant.image;

    @BindView(R.id.recyclerView)
    RecyclerView recyclerView;

    RecyclerHeaderItemAdapter mAdapter ;

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

        initView();
    }

    private void initView() {
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        List listItems = getList();
        mAdapter = new RecyclerHeaderItemAdapter(this, listItems);
        recyclerView.setAdapter(mAdapter);


        // Add the sticky headers decoration
        final StickyRecyclerHeadersDecoration headersDecor = new StickyRecyclerHeadersDecoration(mAdapter);
        recyclerView.addItemDecoration(headersDecor);

        // Add decoration for dividers between list items
        recyclerView.addItemDecoration(new DividerItemDecoration(this, LinearLayoutManager.VERTICAL));

        StickyRecyclerHeadersTouchListener touchListener =
                new StickyRecyclerHeadersTouchListener(recyclerView, headersDecor);
        recyclerView.addOnItemTouchListener(touchListener);

    }

    private List getList(){
        List list = new ArrayList<>();
        for (int index =0; index < names.length; index++){
            ItemModel itemModel = new ItemModel();
            itemModel.setName(names[index]);
            itemModel.setImagePath(images[index]);
            list.add(itemModel);
        }
        if (list.size() > 0) {
            Collections.sort(list, new Comparator() {
                @Override
                public int compare(final ItemModel object1, final ItemModel object2) {
                    return object1.getName().compareTo(object2.getName());
                }
            });
        }
        return list;
    }
}

RecyclerItemAdapter.java

public abstract class RecyclerItemAdapter extends RecyclerView.Adapter {

    List items = new ArrayList<>();
    RecyclerItemAdapter(){
        setHasStableIds(true);
    }


    public void add(ItemModel object) {
        items.add(object);
        notifyDataSetChanged();
    }

    public void add(int index, ItemModel object) {
        items.add(index, object);
        notifyDataSetChanged();
    }

    public void addAll(Collection collection) {
        if (collection != null) {
            items.addAll(collection);
            notifyDataSetChanged();
        }
    }

    public void addAll(ItemModel... items) {
        addAll(Arrays.asList(items));
    }

    public void clear() {
        items.clear();
        notifyDataSetChanged();
    }

    public void remove(ItemModel object) {
        items.remove(object);
        notifyDataSetChanged();
    }

}

RecyclerHeaderItemAdapter.java

public class RecyclerHeaderItemAdapter extends RecyclerItemAdapter implements StickyRecyclerHeadersAdapter {

    private List mList ;
    private Context context;

    RecyclerHeaderItemAdapter(Context context, List list ){
        this.mList = list;
        this.context = context;
    }


    @Override
    public long getHeaderId(int position) {
        if (position == 0) {
            return -1;
        } else {
            return getItemId(position);
        }
    }

    @Override
    public RecyclerView.ViewHolder onCreateHeaderViewHolder(ViewGroup parent) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_header, parent, false);
        return new ItemHeaderViewHolder(view);
    }

    @Override
    public void onBindHeaderViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof ItemHeaderViewHolder) {
            if (getItem(position).getName() != null) {
                String header = String.valueOf(getItem(position).getName().charAt(0));
                ((ItemHeaderViewHolder) holder).header.setText(header);
            }
        }

    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item, parent, false);
        return new ItemViewHolder(view);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        ItemModel itemModel = mList.get(position);
        ((ItemViewHolder)holder).name.setText(itemModel.getName());
        String imageUrl = itemModel.getImagePath();
        if (imageUrl != null){
            Glide.with(context)
                    .load(imageUrl)
                    .into(((ItemViewHolder)holder).imageView);
        }
    }

    @Override
    public int getItemCount() {
        return mList.size();
    }

    public ItemModel getItem(int position) {
        return mList.get(position);
    }

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


    public static class ItemViewHolder extends RecyclerView.ViewHolder {

        @BindView(R.id.name)
        TextView name;

        @BindView(R.id.imageView)
        ImageView imageView;

        public ItemViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }
    }

    public static class ItemHeaderViewHolder extends RecyclerView.ViewHolder {

        @BindView(R.id.header)
        TextView header;

        public ItemHeaderViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }
    }

}

activity_main.xml

<?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"
    tools:context="com.sunil.stickyheaderrecyclerview.MainActivity">

   <android.support.v7.widget.RecyclerView
       android:id="@+id/recyclerView"
       android:layout_width="match_parent"
       android:layout_height="wrap_content">
   </android.support.v7.widget.RecyclerView>
</RelativeLayout>

list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <de.hdodenhof.circleimageview.CircleImageView
        android:src="@mipmap/ic_launcher"
        android:id="@+id/imageView"
        android:layout_width="70dp"
        android:layout_height="70dp" />

    <TextView
        android:id="@+id/name"
        android:text="name"
        android:textStyle="bold"
        android:layout_alignBaseline="@id/imageView"
        android:layout_margin="10dp"
        android:gravity="center_vertical"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:layout_toRightOf="@+id/imageView"/>

</RelativeLayout>



Thanks for reading this post. I hope it will helps to understand.

6 comments:

  1. Nice tutorial, can you please workaround for gridLayoutManager. I failed to make the header take full parent width.

    ReplyDelete
    Replies
    1. Thanks for the comment. I will look into this.

      Delete
  2. I am unable to find list_item_header Layout ??

    ReplyDelete
  3. what is Constant.java ?? I am getting error

    ReplyDelete
    Replies
    1. Hi Raj, You can get the source reference Here https://github.com/sunil676/StickyHeaderRecyclerView

      Delete
  4. I have a question, the sticky header used in here, is it focusable in talkback mode?

    ReplyDelete

Debug Database in Android

In my last tutorial, we have learned how we can use persistence database in android. We also understood which database wrapper we can use...

Contact Me

Name

Email *

Message *