diff --git a/res/drawable/conversation_item_header_background.xml b/res/drawable/conversation_item_header_background.xml
new file mode 100644
index 0000000000..6cc80221f1
--- /dev/null
+++ b/res/drawable/conversation_item_header_background.xml
@@ -0,0 +1,6 @@
+
+
+    
+    
+
\ No newline at end of file
diff --git a/res/drawable/conversation_item_header_background_dark.xml b/res/drawable/conversation_item_header_background_dark.xml
new file mode 100644
index 0000000000..2cc83dbe26
--- /dev/null
+++ b/res/drawable/conversation_item_header_background_dark.xml
@@ -0,0 +1,6 @@
+
+
+    
+    
+
\ No newline at end of file
diff --git a/res/layout/conversation_item_header.xml b/res/layout/conversation_item_header.xml
new file mode 100644
index 0000000000..ef6fb13203
--- /dev/null
+++ b/res/layout/conversation_item_header.xml
@@ -0,0 +1,21 @@
+
+
+
+    
+
\ No newline at end of file
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index be6834af3d..131369a817 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -71,6 +71,7 @@
     
     
     
+    
 
     
     
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 31496313cc..befdf1b6c0 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -227,6 +227,8 @@
     
     Just now
     %d min
+    Today
+    Yesterday
 
     
     Unlink \'%s\'?
diff --git a/res/values/themes.xml b/res/values/themes.xml
index fe5a50402e..556afe5abc 100644
--- a/res/values/themes.xml
+++ b/res/values/themes.xml
@@ -156,6 +156,7 @@
         - #99000000
- @color/white
- #BFffffff+
- @drawable/conversation_item_header_background
- @drawable/quick_camera_light
- @drawable/ic_mic_grey600_24dp@@ -242,6 +243,7 @@
- @color/white
- #BFffffff
- @drawable/conversation_item_sent_indicator_text_shape_dark+
- @drawable/conversation_item_header_background_dark
- #ff333333diff --git a/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java b/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java
index be3b3083d9..b48f187786 100644
--- a/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java
+++ b/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java
@@ -18,16 +18,12 @@ package org.thoughtcrime.securesms;
 
 
 import android.database.Cursor;
-import android.graphics.Canvas;
-import android.graphics.Rect;
 import android.os.Build;
-import android.os.Build.VERSION;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
 import android.support.v4.app.Fragment;
 import android.support.v4.app.LoaderManager;
 import android.support.v4.content.Loader;
-import android.support.v4.view.ViewCompat;
 import android.support.v4.widget.SwipeRefreshLayout;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
@@ -41,9 +37,9 @@ import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter;
 import org.thoughtcrime.securesms.contacts.ContactSelectionListItem;
 import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
 import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
+import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
 import org.thoughtcrime.securesms.util.ViewUtil;
 
-import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
@@ -205,180 +201,4 @@ public class ContactSelectionListFragment extends    Fragment
     void onContactDeselected(String number);
   }
 
-  /**
-   * A sticky header decoration for android's RecyclerView.
-   */
-  public static class StickyHeaderDecoration extends RecyclerView.ItemDecoration {
-
-    private Map mHeaderCache;
-
-    private StickyHeaderAdapter mAdapter;
-
-    private boolean mRenderInline;
-
-    /**
-     * @param adapter the sticky header adapter to use
-     */
-    public StickyHeaderDecoration(StickyHeaderAdapter adapter, boolean renderInline) {
-      mAdapter = adapter;
-      mHeaderCache = new HashMap<>();
-      mRenderInline = renderInline;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
-                               RecyclerView.State state)
-    {
-      int position = parent.getChildAdapterPosition(view);
-
-      int headerHeight = 0;
-      if (position != RecyclerView.NO_POSITION && hasHeader(position)) {
-        View header = getHeader(parent, position).itemView;
-        headerHeight = getHeaderHeightForLayout(header);
-      }
-
-      outRect.set(0, headerHeight, 0, 0);
-    }
-
-    private boolean hasHeader(int position) {
-      if (position == 0) {
-        return true;
-      }
-
-      int previous = position - 1;
-      return mAdapter.getHeaderId(position) != mAdapter.getHeaderId(previous);
-    }
-
-    private RecyclerView.ViewHolder getHeader(RecyclerView parent, int position) {
-      final long key = mAdapter.getHeaderId(position);
-
-      if (mHeaderCache.containsKey(key)) {
-        return mHeaderCache.get(key);
-      } else {
-        final RecyclerView.ViewHolder holder = mAdapter.onCreateHeaderViewHolder(parent);
-        final View header = holder.itemView;
-
-        //noinspection unchecked
-        mAdapter.onBindHeaderViewHolder(holder, position);
-
-        int widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY);
-        int heightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.UNSPECIFIED);
-
-        int childWidth = ViewGroup.getChildMeasureSpec(widthSpec,
-                                                       parent.getPaddingLeft() + parent.getPaddingRight(), header.getLayoutParams().width);
-        int childHeight = ViewGroup.getChildMeasureSpec(heightSpec,
-                                                        parent.getPaddingTop() + parent.getPaddingBottom(), header.getLayoutParams().height);
-
-        header.measure(childWidth, childHeight);
-        header.layout(0, 0, header.getMeasuredWidth(), header.getMeasuredHeight());
-
-        mHeaderCache.put(key, holder);
-
-        return holder;
-      }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
-      final int count = parent.getChildCount();
-
-      for (int layoutPos = 0; layoutPos < count; layoutPos++) {
-        final View child = parent.getChildAt(layoutPos);
-
-        final int adapterPos = parent.getChildAdapterPosition(child);
-
-        if (adapterPos != RecyclerView.NO_POSITION && (layoutPos == 0 || hasHeader(adapterPos))) {
-          View header = getHeader(parent, adapterPos).itemView;
-          c.save();
-          final int left = child.getLeft();
-          final int top = getHeaderTop(parent, child, header, adapterPos, layoutPos);
-          c.translate(left, top);
-          header.draw(c);
-          c.restore();
-        }
-      }
-    }
-
-    private int getHeaderTop(RecyclerView parent, View child, View header, int adapterPos,
-                             int layoutPos)
-    {
-      int headerHeight = getHeaderHeightForLayout(header);
-      int top = getChildY(parent, child) - headerHeight;
-      if (layoutPos == 0) {
-        final int count = parent.getChildCount();
-        final long currentId = mAdapter.getHeaderId(adapterPos);
-        // find next view with header and compute the offscreen push if needed
-        for (int i = 1; i < count; i++) {
-          int adapterPosHere = parent.getChildAdapterPosition(parent.getChildAt(i));
-          if (adapterPosHere != RecyclerView.NO_POSITION) {
-            long nextId = mAdapter.getHeaderId(adapterPosHere);
-            if (nextId != currentId) {
-              final View next = parent.getChildAt(i);
-              final int offset = getChildY(parent, next) - (headerHeight + getHeader(parent, adapterPosHere).itemView.getHeight());
-              if (offset < 0) {
-                return offset;
-              } else {
-                break;
-              }
-            }
-          }
-        }
-
-        top = Math.max(0, top);
-      }
-
-      return top;
-    }
-
-    private int getChildY(RecyclerView parent, View child) {
-      if (VERSION.SDK_INT < 11) {
-        Rect rect = new Rect();
-        parent.getChildVisibleRect(child, rect, null);
-        return rect.top;
-      } else {
-        return (int)ViewCompat.getY(child);
-      }
-    }
-
-    private int getHeaderHeightForLayout(View header) {
-      return mRenderInline ? 0 : header.getHeight();
-    }
-  }
-
-  /**
-   * The adapter to assist the {@link StickyHeaderDecoration} in creating and binding the header views.
-   *
-   * @param  the header view holder
-   */
-  public interface StickyHeaderAdapter {
-
-    /**
-     * Returns the header id for the item at the given position.
-     *
-     * @param position the item position
-     * @return the header id
-     */
-    long getHeaderId(int position);
-
-    /**
-     * Creates a new header ViewHolder.
-     *
-     * @param parent the header's view parent
-     * @return a view holder for the created view
-     */
-    T onCreateHeaderViewHolder(ViewGroup parent);
-
-    /**
-     * Updates the header view to reflect the header data for the given position
-     * @param viewHolder the header view holder
-     * @param position the header's item position
-     */
-    void onBindHeaderViewHolder(T viewHolder, int position);
-  }
 }
diff --git a/src/org/thoughtcrime/securesms/ConversationAdapter.java b/src/org/thoughtcrime/securesms/ConversationAdapter.java
index de7bcac90f..c08f4fcd58 100644
--- a/src/org/thoughtcrime/securesms/ConversationAdapter.java
+++ b/src/org/thoughtcrime/securesms/ConversationAdapter.java
@@ -29,6 +29,7 @@ import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
+import android.widget.TextView;
 
 import org.thoughtcrime.securesms.crypto.MasterSecret;
 import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
@@ -39,11 +40,17 @@ import org.thoughtcrime.securesms.database.SmsDatabase;
 import org.thoughtcrime.securesms.database.model.MessageRecord;
 import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
 import org.thoughtcrime.securesms.recipients.Recipients;
+import org.thoughtcrime.securesms.util.DateUtils;
 import org.thoughtcrime.securesms.util.LRUCache;
+import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
+import org.thoughtcrime.securesms.util.Util;
 import org.thoughtcrime.securesms.util.ViewUtil;
+import org.thoughtcrime.securesms.ConversationAdapter.HeaderViewHolder;
 
 import java.lang.ref.SoftReference;
+import java.util.Calendar;
 import java.util.Collections;
+import java.util.Date;
 import java.util.HashSet;
 import java.util.Locale;
 import java.util.Map;
@@ -59,6 +66,7 @@ import java.util.Set;
  */
 public class ConversationAdapter 
     extends CursorRecyclerViewAdapter
+  implements StickyHeaderDecoration.StickyHeaderAdapter
 {
 
   private static final int MAX_CACHE_SIZE = 40;
@@ -82,6 +90,7 @@ public class ConversationAdapter 
   private final @NonNull  Recipients        recipients;
   private final @NonNull  MmsSmsDatabase    db;
   private final @NonNull  LayoutInflater    inflater;
+  private final @NonNull  Calendar          calendar;
 
   protected static class ViewHolder extends RecyclerView.ViewHolder {
     public  ViewHolder(final @NonNull V itemView) {
@@ -94,6 +103,21 @@ public class ConversationAdapter 
     }
   }
 
+
+  protected static class HeaderViewHolder extends RecyclerView.ViewHolder {
+    private TextView textView;
+
+    public HeaderViewHolder(View itemView) {
+      super(itemView);
+      textView = ViewUtil.findById(itemView, R.id.text);
+    }
+
+    public void setText(CharSequence text) {
+      textView.setText(text);
+    }
+  }
+
+
   public interface ItemClickListener {
     void onItemClick(MessageRecord item);
     void onItemLongClick(MessageRecord item);
@@ -109,6 +133,7 @@ public class ConversationAdapter 
     this.recipients    = null;
     this.inflater      = null;
     this.db            = null;
+    this.calendar      = null;
   }
 
   public ConversationAdapter(@NonNull Context context,
@@ -125,6 +150,7 @@ public class ConversationAdapter 
     this.recipients    = recipients;
     this.inflater      = LayoutInflater.from(context);
     this.db            = DatabaseFactory.getMmsSmsDatabase(context);
+    this.calendar      = Calendar.getInstance();
 
     setHasStableIds(true);
   }
@@ -137,10 +163,8 @@ public class ConversationAdapter 
 
   @Override
   public void onBindItemViewHolder(ViewHolder viewHolder, @NonNull Cursor cursor) {
-    long start = System.currentTimeMillis();
-    long          id            = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.ID));
-    String        type          = cursor.getString(cursor.getColumnIndexOrThrow(MmsSmsDatabase.TRANSPORT));
-    MessageRecord messageRecord = getMessageRecord(id, cursor, type);
+    long          start         = System.currentTimeMillis();
+    MessageRecord messageRecord = getMessageRecord(cursor);
 
     viewHolder.getView().bind(masterSecret, messageRecord, locale, batchSelected, recipients);
     Log.w(TAG, "Bind time: " + (System.currentTimeMillis() - start));
@@ -191,11 +215,9 @@ public class ConversationAdapter 
 
   @Override
   public int getItemViewType(@NonNull Cursor cursor) {
-    long          id            = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.ID));
-    String        type          = cursor.getString(cursor.getColumnIndexOrThrow(MmsSmsDatabase.TRANSPORT));
-    MessageRecord messageRecord = getMessageRecord(id, cursor, type);
+    MessageRecord messageRecord = getMessageRecord(cursor);
 
-    if (messageRecord.isGroupAction() || messageRecord.isCallLog() || messageRecord.isJoined() || 
+    if (messageRecord.isGroupAction() || messageRecord.isCallLog() || messageRecord.isJoined() ||
         messageRecord.isExpirationTimerUpdate() || messageRecord.isEndSession() || messageRecord.isIdentityUpdate()) {
       return MESSAGE_TYPE_UPDATE;
     } else if (hasAudio(messageRecord)) {
@@ -216,7 +238,10 @@ public class ConversationAdapter 
     return cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.UNIQUE_ROW_ID));
   }
 
-  private MessageRecord getMessageRecord(long messageId, Cursor cursor, String type) {
+  private MessageRecord getMessageRecord(Cursor cursor) {
+    long   messageId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.ID));
+    String type      = cursor.getString(cursor.getColumnIndexOrThrow(MmsSmsDatabase.TRANSPORT));
+
     final SoftReference reference = messageRecordCache.get(type + messageId);
     if (reference != null) {
       final MessageRecord record = reference.get();
@@ -254,4 +279,26 @@ public class ConversationAdapter 
   private boolean hasThumbnail(MessageRecord messageRecord) {
     return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getThumbnailSlide() != null;
   }
+
+
+  @Override
+  public long getHeaderId(int position) {
+    Cursor        cursor = getCursorAtPositionOrThrow(position);
+    MessageRecord record = getMessageRecord(cursor);
+
+    calendar.setTime(new Date(record.getDateSent()));
+    return Util.hashCode(calendar.get(Calendar.YEAR), calendar.get(Calendar.DAY_OF_YEAR));
+  }
+
+  @Override
+  public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent) {
+    return new HeaderViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.conversation_item_header, parent, false));
+  }
+
+  @Override
+  public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position) {
+    Cursor cursor = getCursorAtPositionOrThrow(position);
+    viewHolder.setText(DateUtils.getRelativeDate(getContext(), locale, getMessageRecord(cursor).getDateReceived()));
+  }
 }
+
diff --git a/src/org/thoughtcrime/securesms/ConversationFragment.java b/src/org/thoughtcrime/securesms/ConversationFragment.java
index 90b155a0d7..5db12fd418 100644
--- a/src/org/thoughtcrime/securesms/ConversationFragment.java
+++ b/src/org/thoughtcrime/securesms/ConversationFragment.java
@@ -32,7 +32,6 @@ import android.support.v7.app.AppCompatActivity;
 import android.support.v7.view.ActionMode;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.RecyclerView.ItemAnimator.ItemAnimatorFinishedListener;
 import android.support.v7.widget.RecyclerView.OnScrollListener;
 import android.text.ClipboardManager;
 import android.text.TextUtils;
@@ -60,6 +59,7 @@ import org.thoughtcrime.securesms.recipients.Recipients;
 import org.thoughtcrime.securesms.sms.MessageSender;
 import org.thoughtcrime.securesms.util.SaveAttachmentTask;
 import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment;
+import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
 import org.thoughtcrime.securesms.util.ViewUtil;
 import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
 
@@ -170,7 +170,10 @@ public class ConversationFragment extends Fragment
 
   private void initializeListAdapter() {
     if (this.recipients != null && this.threadId != -1) {
-      list.setAdapter(new ConversationAdapter(getActivity(), masterSecret, locale, selectionClickListener, null, this.recipients));
+      ConversationAdapter adapter = new ConversationAdapter(getActivity(), masterSecret, locale, selectionClickListener, null, this.recipients);
+      list.setAdapter(adapter);
+      list.addItemDecoration(new StickyHeaderDecoration(adapter, false));
+
       getLoaderManager().restartLoader(0, Bundle.EMPTY, this);
     }
   }
diff --git a/src/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java b/src/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java
index c889fde7b3..c723f1f9f2 100644
--- a/src/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java
+++ b/src/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java
@@ -35,7 +35,7 @@ import android.widget.TextView;
 
 import org.thoughtcrime.securesms.R;
 import org.thoughtcrime.securesms.components.RecyclerViewFastScroller.FastScrollAdapter;
-import org.thoughtcrime.securesms.ContactSelectionListFragment.StickyHeaderAdapter;
+import org.thoughtcrime.securesms.util.StickyHeaderDecoration.StickyHeaderAdapter;
 import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter.HeaderViewHolder;
 import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter.ViewHolder;
 import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
diff --git a/src/org/thoughtcrime/securesms/util/DateUtils.java b/src/org/thoughtcrime/securesms/util/DateUtils.java
index 6d12ad4eea..c53039c132 100644
--- a/src/org/thoughtcrime/securesms/util/DateUtils.java
+++ b/src/org/thoughtcrime/securesms/util/DateUtils.java
@@ -18,6 +18,7 @@ package org.thoughtcrime.securesms.util;
 
 import android.content.Context;
 import android.os.Build;
+import android.support.annotation.NonNull;
 import android.text.format.DateFormat;
 
 import java.text.SimpleDateFormat;
@@ -37,6 +38,10 @@ public class DateUtils extends android.text.format.DateUtils {
     return System.currentTimeMillis() - millis <= unit.toMillis(span);
   }
 
+  private static boolean isYesterday(final long when) {
+    return DateUtils.isToday(when + TimeUnit.DAYS.toMillis(1));
+  }
+
   private static int convertDelta(final long millis, TimeUnit to) {
     return (int) to.convert(System.currentTimeMillis() - millis, TimeUnit.MILLISECONDS);
   }
@@ -111,6 +116,19 @@ public class DateUtils extends android.text.format.DateUtils {
     return new SimpleDateFormat(dateFormatPattern, locale);
   }
 
+  public static String getRelativeDate(@NonNull Context context,
+                                       @NonNull Locale locale,
+                                       long timestamp)
+  {
+    if (isToday(timestamp)) {
+      return context.getString(R.string.DateUtils_today);
+    } else if (isYesterday(timestamp)) {
+      return context.getString(R.string.DateUtils_yesterday);
+    } else {
+      return getFormattedDateTime(timestamp, "EEE, MMM d, yyyy", locale);
+    }
+  }
+
   private static String getLocalizedPattern(String template, Locale locale) {
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
       return DateFormat.getBestDateTimePattern(locale, template);
diff --git a/src/org/thoughtcrime/securesms/util/StickyHeaderDecoration.java b/src/org/thoughtcrime/securesms/util/StickyHeaderDecoration.java
new file mode 100644
index 0000000000..c4bc881d7a
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/util/StickyHeaderDecoration.java
@@ -0,0 +1,201 @@
+package org.thoughtcrime.securesms.util;
+
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.os.Build.VERSION;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A sticky header decoration for android's RecyclerView.
+ * Currently only supports LinearLayoutManager in VERTICAL orientation.
+ */
+public class StickyHeaderDecoration extends RecyclerView.ItemDecoration {
+
+  private final Map headerCache;
+  private final StickyHeaderAdapter   adapter;
+  private final boolean               renderInline;
+
+  /**
+   * @param adapter the sticky header adapter to use
+   */
+  public StickyHeaderDecoration(StickyHeaderAdapter adapter, boolean renderInline) {
+    this.adapter      = adapter;
+    this.headerCache  = new HashMap<>();
+    this.renderInline = renderInline;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+                             RecyclerView.State state)
+  {
+    int position = parent.getChildAdapterPosition(view);
+
+    int headerHeight = 0;
+    if (position != RecyclerView.NO_POSITION && hasHeader(parent, position)) {
+      View header = getHeader(parent, position).itemView;
+      headerHeight = getHeaderHeightForLayout(header);
+    }
+
+    outRect.set(0, headerHeight, 0, 0);
+  }
+
+  private boolean hasHeader(RecyclerView parent, int adapterPos) {
+    boolean isReverse = isReverseLayout(parent);
+    if (isReverse && adapterPos == parent.getAdapter().getItemCount() - 1 ||
+        !isReverse && adapterPos == 0) {
+      return true;
+    }
+
+    int previous = adapterPos + (isReverse ? 1 : -1);
+    return adapter.getHeaderId(adapterPos) != adapter.getHeaderId(previous);
+  }
+
+  private ViewHolder getHeader(RecyclerView parent, int position) {
+    final long key = adapter.getHeaderId(position);
+
+    if (headerCache.containsKey(key)) {
+      return headerCache.get(key);
+    } else {
+      final ViewHolder holder = adapter.onCreateHeaderViewHolder(parent);
+      final View header = holder.itemView;
+
+      //noinspection unchecked
+      adapter.onBindHeaderViewHolder(holder, position);
+
+      int widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY);
+      int heightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.UNSPECIFIED);
+
+      int childWidth = ViewGroup.getChildMeasureSpec(widthSpec,
+                                                     parent.getPaddingLeft() + parent.getPaddingRight(), header.getLayoutParams().width);
+      int childHeight = ViewGroup.getChildMeasureSpec(heightSpec,
+                                                      parent.getPaddingTop() + parent.getPaddingBottom(), header.getLayoutParams().height);
+
+      header.measure(childWidth, childHeight);
+      header.layout(0, 0, header.getMeasuredWidth(), header.getMeasuredHeight());
+
+      headerCache.put(key, holder);
+
+      return holder;
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
+    final int count = parent.getChildCount();
+
+    for (int layoutPos = 0; layoutPos < count; layoutPos++) {
+      final View child = parent.getChildAt(translatedChildPosition(parent, layoutPos));
+
+      final int adapterPos = parent.getChildAdapterPosition(child);
+
+      if (adapterPos != RecyclerView.NO_POSITION && (layoutPos == 0 || hasHeader(parent, adapterPos))) {
+        View header = getHeader(parent, adapterPos).itemView;
+        c.save();
+        final int left = child.getLeft();
+        final int top = getHeaderTop(parent, child, header, adapterPos, layoutPos);
+        c.translate(left, top);
+        header.draw(c);
+        c.restore();
+      }
+    }
+  }
+
+  private int getHeaderTop(RecyclerView parent, View child, View header, int adapterPos,
+                           int layoutPos)
+  {
+    int headerHeight = getHeaderHeightForLayout(header);
+    int top = getChildY(parent, child) - headerHeight;
+    if (layoutPos == 0) {
+      final int count = parent.getChildCount();
+      final long currentId = adapter.getHeaderId(adapterPos);
+      // find next view with header and compute the offscreen push if needed
+      for (int i = 1; i < count; i++) {
+        int adapterPosHere = parent.getChildAdapterPosition(parent.getChildAt(translatedChildPosition(parent, i)));
+        if (adapterPosHere != RecyclerView.NO_POSITION) {
+          long nextId = adapter.getHeaderId(adapterPosHere);
+          if (nextId != currentId) {
+            final View next = parent.getChildAt(translatedChildPosition(parent, i));
+            final int offset = getChildY(parent, next) - (headerHeight + getHeader(parent, adapterPosHere).itemView.getHeight());
+            if (offset < 0) {
+              return offset;
+            } else {
+              break;
+            }
+          }
+        }
+      }
+
+      top = Math.max(0, top);
+    }
+
+    return top;
+  }
+
+  private int translatedChildPosition(RecyclerView parent, int position) {
+    return isReverseLayout(parent) ? parent.getChildCount() - 1 - position : position;
+  }
+
+  private int getChildY(RecyclerView parent, View child) {
+    if (VERSION.SDK_INT < 11) {
+      Rect rect = new Rect();
+      parent.getChildVisibleRect(child, rect, null);
+      return rect.top;
+    } else {
+      return (int)ViewCompat.getY(child);
+    }
+  }
+
+  private int getHeaderHeightForLayout(View header) {
+    return renderInline ? 0 : header.getHeight();
+  }
+
+  private boolean isReverseLayout(final RecyclerView parent) {
+    return (parent.getLayoutManager() instanceof LinearLayoutManager) &&
+        ((LinearLayoutManager)parent.getLayoutManager()).getReverseLayout();
+  }
+
+  /**
+   * The adapter to assist the {@link StickyHeaderDecoration} in creating and binding the header views.
+   *
+   * @param  the header view holder
+   */
+  public interface StickyHeaderAdapter {
+
+    /**
+     * Returns the header id for the item at the given position.
+     *
+     * @param position the item position
+     * @return the header id
+     */
+    long getHeaderId(int position);
+
+    /**
+     * Creates a new header ViewHolder.
+     *
+     * @param parent the header's view parent
+     * @return a view holder for the created view
+     */
+    T onCreateHeaderViewHolder(ViewGroup parent);
+
+    /**
+     * Updates the header view to reflect the header data for the given position
+     * @param viewHolder the header view holder
+     * @param position the header's item position
+     */
+    void onBindHeaderViewHolder(T viewHolder, int position);
+  }
+}
\ No newline at end of file