public abstract class DragAndDropCallback<T> {

  public boolean enableDefaultRaiseEffect() {
    return true;
  }

  public void onCellDragStarted(@NonNull SimpleRecyclerView simpleRecyclerView, @NonNull View itemView, @NonNull T item, int position) {
  }

  public void onCellMoved(@NonNull SimpleRecyclerView simpleRecyclerView, @NonNull View itemView, @NonNull T item, int fromPosition, int toPosition) {
  }

  public void onCellDropped(@NonNull SimpleRecyclerView simpleRecyclerView, @NonNull View itemView, @NonNull T item, int initialPosition, int toPosition) {
  }

  public void onCellDragCancelled(@NonNull SimpleRecyclerView simpleRecyclerView, @NonNull View itemView, @NonNull T item, int currentPosition) {
  }

} public class DragAndDropHelper extends ItemTouchHelper
  implements OnStartDragListener {

  private int dragHandleId;

  private DragAndDropHelper(Callback callback) {
    super(callback);
  }

  public static DragAndDropHelper create(OnItemMoveListener callback,
                                         DragAndDropOptions options) {
    DragAndDropItemCallback simpleCallback = new DragAndDropItemCallback(callback, options);
    DragAndDropHelper helper = new DragAndDropHelper(simpleCallback);
    helper.dragHandleId = options.getDragHandleId();
    return helper;
  }

  @Override
  public void onStartDrag(RecyclerView.ViewHolder viewHolder) {
    startDrag(viewHolder);
  }

  public int getDragHandleId() {
    return dragHandleId;
  }

} public class DragAndDropOptions {

  private boolean canLongPressToDrag = true;
  private int dragHandleId;
  private DragAndDropCallback dragAndDropCallback;
  private boolean enableDefaultEffect = true;

  public void setCanLongPressToDrag(boolean canLongPressToDrag) {
    this.canLongPressToDrag = canLongPressToDrag;
  }

  public boolean canLongPressToDrag() {
    return canLongPressToDrag;
  }

  public int getDragHandleId() {
    return dragHandleId;
  }

  public void setDragHandleId(int dragHandleId) {
    this.dragHandleId = dragHandleId;
  }

  public DragAndDropCallback getDragAndDropCallback() {
    return dragAndDropCallback;
  }

  public void setDragAndDropCallback(DragAndDropCallback dragAndDropCallback) {
    this.dragAndDropCallback = dragAndDropCallback;
  }

  public boolean isDefaultEffectEnabled() {
    return enableDefaultEffect;
  }

  public void setEnableDefaultEffect(boolean enableDefaultEffect) {
    this.enableDefaultEffect = enableDefaultEffect;
  }

} public enum SnapAlignment {
  CENTER, START
} public class StartSnapHelper extends LinearSnapHelper {

  private OrientationHelper verticalHelper;
  private OrientationHelper horizontalHelper;
  private int cellSpacing;

  public StartSnapHelper(int cellSpacing) {
    this.cellSpacing = cellSpacing;
  }

  @Override
  public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager,
                                            @NonNull View targetView) {
    int[] out = new int[2];

    if (layoutManager.canScrollHorizontally()) {
      out[0] = distanceToStart(targetView, getHorizontalHelper(layoutManager));
    } else {
      out[0] = 0;
    }

    if (layoutManager.canScrollVertically()) {
      out[1] = distanceToStart(targetView, getVerticalHelper(layoutManager));
    } else {
      out[1] = 0;
    }

    return out;
  }

  private int distanceToStart(View targetView, OrientationHelper helper) {
    return helper.getDecoratedStart(targetView) - helper.getStartAfterPadding() - cellSpacing / 2;
  }

  @Override
  public View findSnapView(RecyclerView.LayoutManager layoutManager) {
    if (!(layoutManager instanceof LinearLayoutManager)) {
      return super.findSnapView(layoutManager);
    }

    if (layoutManager.canScrollHorizontally()) {
      return getStartView(layoutManager, getHorizontalHelper(layoutManager));
    } else {
      return getStartView(layoutManager, getVerticalHelper(layoutManager));
    }
  }

  private View getStartView(RecyclerView.LayoutManager layoutManager,
                            OrientationHelper helper) {
    if (!(layoutManager instanceof LinearLayoutManager)) {
      return super.findSnapView(layoutManager);
    }

    LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;

    int firstChild = linearLayoutManager.findFirstVisibleItemPosition();

    boolean isLastItem = linearLayoutManager.findLastCompletelyVisibleItemPosition() == layoutManager.getItemCount() - 1;

    if (firstChild == RecyclerView.NO_POSITION || isLastItem) {
      return null;
    }

    View child = layoutManager.findViewByPosition(firstChild);

    if (helper.getDecoratedEnd(child) >= helper.getDecoratedMeasurement(child) / 2
      && helper.getDecoratedEnd(child) > 0) {
      return child;
    } else {
      return layoutManager.findViewByPosition(firstChild + 1);
    }
  }

  private OrientationHelper getVerticalHelper(RecyclerView.LayoutManager layoutManager) {
    if (verticalHelper == null) {
      verticalHelper = OrientationHelper.createVerticalHelper(layoutManager);
    }
    return verticalHelper;
  }

  private OrientationHelper getHorizontalHelper(RecyclerView.LayoutManager layoutManager) {
    if (horizontalHelper == null) {
      horizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager);
    }
    return horizontalHelper;
  }

} public enum SwipeDirection {
  LEFT, RIGHT, UP, DOWN
} public abstract class SwipeToDismissCallback<T> {

  public boolean enableDefaultFadeOutEffect() {
    return true;
  }

  public void onCellSwiping(@NonNull SimpleRecyclerView simpleRecyclerView, @NonNull View itemView, @NonNull T item, int position, @NonNull Canvas canvas, float dX, float dY, boolean isControlledByUser) {
  }

  public void onCellSettled(@NonNull SimpleRecyclerView simpleRecyclerView, @NonNull View itemView, @NonNull T item, int position) {
  }

  public void onCellDismissed(@NonNull SimpleRecyclerView simpleRecyclerView, @NonNull T item, int position) {
  }

} public class SwipeToDismissHelper extends ItemTouchHelper
  implements OnStartDragListener {

  private SwipeToDismissHelper(Callback callback) {
    super(callback);
  }

  public static SwipeToDismissHelper create(OnItemDismissListener callback,
                                            SwipeToDismissOptions options) {
    SwipeToDismissItemCallback simpleCallback = new SwipeToDismissItemCallback(callback, options);
    return new SwipeToDismissHelper(simpleCallback);
  }

  @Override
  public void onStartDrag(RecyclerView.ViewHolder viewHolder) {
    startDrag(viewHolder);
  }

} public class SwipeToDismissOptions {

  private Set<SwipeDirection> swipeDirections = new HashSet<>();
  private boolean enableDefaultFadeOutEffect = true;
  private SwipeToDismissCallback swipeToDismissCallback;

  public boolean isDefaultFadeOutEffectEnabled() {
    return enableDefaultFadeOutEffect;
  }

  public void setEnableDefaultFadeOutEffect(boolean enableDefaultFadeOutEffect) {
    this.enableDefaultFadeOutEffect = enableDefaultFadeOutEffect;
  }

  public SwipeToDismissCallback getSwipeToDismissCallback() {
    return swipeToDismissCallback;
  }

  public void setSwipeToDismissCallback(SwipeToDismissCallback swipeToDismissCallback) {
    this.swipeToDismissCallback = swipeToDismissCallback;
  }

  public void setSwipeDirections(Set<SwipeDirection> swipeDirections) {
    this.swipeDirections = new HashSet<>(swipeDirections);
  }

  public boolean canSwipeLeft() {
    return swipeDirections.contains(SwipeDirection.LEFT);
  }


  public boolean canSwipeRight() {
    return swipeDirections.contains(SwipeDirection.RIGHT);
  }

  public boolean canSwipeUp() {
    return swipeDirections.contains(SwipeDirection.UP);
  }

  public boolean canSwipeDown() {
    return swipeDirections.contains(SwipeDirection.DOWN);
  }

} public class DividerItemDecoration extends RecyclerView.ItemDecoration {
  public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
  public static final int VERTICAL = LinearLayout.VERTICAL;

  private static final int[] ATTRS = new int[]{android.R.attr.listDivider};

  private Drawable mDivider;

  private int mOrientation;

  private boolean isShowLastDivider;

  private final Rect mBounds = new Rect();

  private List<String> noDividerCellTypes;
  private RecyclerView.LayoutManager layoutManager;
  private LinearLayoutManager linearLayoutManager;
  private GridLayoutManager gridLayoutManager;

  public DividerItemDecoration(Context context, int orientation) {
    final TypedArray a = context.obtainStyledAttributes(ATTRS);
    mDivider = a.getDrawable(0);
    a.recycle();
    setOrientation(orientation);
  }

  public void setOrientation(int orientation) {
    if (orientation != HORIZONTAL && orientation != VERTICAL) {
      throw new IllegalArgumentException(
        "Invalid orientation. It should be either HORIZONTAL or VERTICAL");
    }
    mOrientation = orientation;
  }

  public void setShowLastDivider(boolean isShowLastDivider) {
    this.isShowLastDivider = isShowLastDivider;
  }

  public void setDrawable(@NonNull Drawable drawable) {
    if (drawable == null) {
      throw new IllegalArgumentException("Drawable cannot be null.");
    }
    mDivider = drawable;
  }

  @Override
  public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
    if (parent.getLayoutManager() == null) {
      return;
    }

    if (mOrientation == VERTICAL) {
      drawHorizontalDivider(c, parent);
    } else {
      drawVerticalDivider(c, parent);
    }
  }

  @SuppressLint("NewApi")
  private void drawHorizontalDivider(Canvas canvas, RecyclerView parent) {
    canvas.save();
    final int left;
    final int right;
    if (parent.getClipToPadding()) {
      left = parent.getPaddingLeft();
      right = parent.getWidth() - parent.getPaddingRight();
      canvas.clipRect(left, parent.getPaddingTop(), right,
        parent.getHeight() - parent.getPaddingBottom());
    } else {
      left = 0;
      right = parent.getWidth();
    }

    final int childCount = parent.getChildCount();
    for (int i = 0; i < childCount; i++) {
      final View child = parent.getChildAt(i);

      if (ignoreDrawDividerForCellTypes(parent, i)) {
        continue;
      }

      if (isLastRow(parent, child) && !isShowLastDivider) {
        continue;
      }

      parent.getDecoratedBoundsWithMargins(child, mBounds);
      final int bottom = mBounds.bottom;
      final int top = bottom - mDivider.getIntrinsicHeight();
      mDivider.setBounds(left, top, right, bottom);
      mDivider.draw(canvas);
    }
    canvas.restore();
  }

  @SuppressLint("NewApi")
  private void drawVerticalDivider(Canvas canvas, RecyclerView parent) {
    canvas.save();
    final int childCount = parent.getChildCount();
    for (int i = 0; i < childCount; i++) {
      final View child = parent.getChildAt(i);

      if (ignoreDrawDividerForCellTypes(parent, i)) {
        continue;
      }

      if (isLastColumn(parent, child) && !isShowLastDivider) {
        continue;
      }

      if (isLastColumn(parent, child) && isGridMode(parent)) {
        continue;
      }

      parent.getLayoutManager().getDecoratedBoundsWithMargins(child, mBounds);
      final int right = mBounds.right;
      final int left = right - mDivider.getIntrinsicWidth();
      final int bottom = child.getBottom();
      final int top = child.getTop();
      mDivider.setBounds(left, top, right, bottom);
      mDivider.draw(canvas);
    }
    canvas.restore();
  }

  @Override
  public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
                             RecyclerView.State state) {
    if (mOrientation == VERTICAL) {
      if (isLastRow(parent, view) && !isShowLastDivider) {
        outRect.set(0, 0, 0, 0);
      } else {
        outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
      }
    } else {
      if ((isLastColumn(parent, view) && isGridMode(parent)) ||
        (isLastColumn(parent, view) && !isShowLastDivider)) {
        outRect.set(0, 0, 0, 0);
      } else {
        outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
      }
    }
  }

  private boolean ignoreDrawDividerForCellTypes(RecyclerView parent, int position) {
    if (noDividerCellTypes == null) {
      noDividerCellTypes = ((SimpleRecyclerView) parent).getNoDividerCellTypes();
    }

    if (noDividerCellTypes.size() <= 0) {
      return false;
    }

    if (isLinearMode(parent)) {
      int pos = getLinearLayoutManager().findFirstVisibleItemPosition();

      if (pos == -1) {
        return false;
      }

      String type = ((SimpleAdapter) parent.getAdapter()).getCell(pos + position).getClass().getSimpleName();
      return noDividerCellTypes.contains(type);
    }

    return false;
  }

  private boolean isLastColumn(RecyclerView parent, View view) {
    int position = parent.getChildAdapterPosition(view);
    int totalChildCount = parent.getAdapter().getItemCount();

    boolean isLastColumn = false;
    if (isGridMode(parent)) {
      int spanCount = getGridLayoutManager().getSpanCount();
      int spanIndex = getGridLayoutManager().getSpanSizeLookup().getSpanIndex(position, spanCount);
      int spanSize = getGridLayoutManager().getSpanSizeLookup().getSpanSize(position);
      isLastColumn = spanIndex == spanCount - spanSize;
    } else if (isLinearMode(parent)) {
      isLastColumn = position == totalChildCount - 1;
    }

    return isLastColumn;
  }

  private boolean isLastRow(RecyclerView parent, View view) {
    int position = parent.getChildAdapterPosition(view);
    int totalChildCount = parent.getAdapter().getItemCount();

    boolean isLastRow = false;
    if (isGridMode(parent)) {
      int spanCount = getGridLayoutManager().getSpanCount();
      int spanIndex = getGridLayoutManager().getSpanSizeLookup().getSpanIndex(position, spanCount);
      int spanSize = getGridLayoutManager().getSpanSizeLookup().getSpanSize(position);
      int column = (spanIndex + spanSize) / spanSize - 1;
      // check if next row first item's index is the last index
      if (spanSize == 1) {
        isLastRow = position + spanCount - column > totalChildCount - 1;
      } else {
        int maxColumns = totalChildCount - position + column;
        int columns = spanCount / spanSize > maxColumns ? maxColumns : spanCount / spanSize;
        isLastRow = position + columns - column > totalChildCount - 1;
      }
    } else if (isLinearMode(parent)) {
      isLastRow = position == totalChildCount - 1;
    }

    return isLastRow;
  }

  private boolean isGridMode(RecyclerView parent) {
    if (layoutManager == null) {
      layoutManager = parent.getLayoutManager();
    }

    return layoutManager instanceof GridLayoutManager;
  }

  private boolean isLinearMode(RecyclerView parent) {
    if (layoutManager == null) {
      layoutManager = parent.getLayoutManager();
    }

    return layoutManager instanceof LinearLayoutManager;
  }

  public GridLayoutManager getGridLayoutManager() {
    if (gridLayoutManager == null) {
      gridLayoutManager = (GridLayoutManager) layoutManager;
    }

    return gridLayoutManager;
  }

  public LinearLayoutManager getLinearLayoutManager() {
    if (linearLayoutManager == null) {
      linearLayoutManager = (LinearLayoutManager) layoutManager;
    }

    return linearLayoutManager;
  }
} public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {

  private boolean includeEdge;
  private int horizontalSpacing;
  private int verticalSpacing;
  private GridLayoutManager gridLayoutManager;

  private GridSpacingItemDecoration(Builder builder) {
    includeEdge = builder.includeEdge;
    int spacing = builder.spacing;
    if (spacing != 0) {
      horizontalSpacing = spacing;
      verticalSpacing = spacing;
    } else {
      horizontalSpacing = builder.horizontalSpacing;
      verticalSpacing = builder.verticalSpacing;
    }
  }

  public static Builder newBuilder() {
    return new Builder();
  }

  @Override
  public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
    if (gridLayoutManager == null) {
      gridLayoutManager = (GridLayoutManager) parent.getLayoutManager();
    }

    int spanCount = gridLayoutManager.getSpanCount();
    int position = parent.getChildAdapterPosition(view);
    int spanSize = gridLayoutManager.getSpanSizeLookup().getSpanSize(position);
    int column = gridLayoutManager.getSpanSizeLookup().getSpanIndex(position, spanCount);
    int totalChildCount = parent.getAdapter().getItemCount();
    boolean isLastRow = spanSize == 1 ?
      position + spanCount - column > totalChildCount - 1 :
      position - column / spanSize > totalChildCount - 1;
    boolean isFirstRow = gridLayoutManager.getSpanSizeLookup().getSpanGroupIndex(position, spanCount) == 0;

    if (includeEdge) {
      outRect.left = horizontalSpacing - column * horizontalSpacing / spanCount;
      outRect.right = (column + spanSize) * horizontalSpacing / spanCount;
      outRect.top = verticalSpacing;
      outRect.bottom = isLastRow ? verticalSpacing : 0;
    } else {
      outRect.left = column * horizontalSpacing / spanCount;
      outRect.right = horizontalSpacing - (column + spanSize) * horizontalSpacing / spanCount;
      outRect.top = isFirstRow ? 0 : verticalSpacing;
    }
  }

  public static final class Builder {
    private boolean includeEdge;
    private int spacing = 0;
    private int verticalSpacing;
    private int horizontalSpacing;

    private Builder() {
    }

    public Builder includeEdge(boolean includeEdge) {
      this.includeEdge = includeEdge;
      return this;
    }

    public Builder spacing(int spacing) {
      this.spacing = spacing;
      return this;
    }

    public Builder verticalSpacing(int verticalSpacing) {
      this.verticalSpacing = verticalSpacing;
      return this;
    }

    public Builder horizontalSpacing(int horizontalSpacing) {
      this.horizontalSpacing = horizontalSpacing;
      return this;
    }

    public GridSpacingItemDecoration build() {
      return new GridSpacingItemDecoration(this);
    }
  }

} public class LinearSpacingItemDecoration extends RecyclerView.ItemDecoration {

  private int spacing;
  private boolean includeEdge;
  private boolean isVertical;

  private LinearSpacingItemDecoration(Builder builder) {
    spacing = builder.spacing;
    includeEdge = builder.includeEdge;
    isVertical = builder.orientation == LinearLayoutManager.VERTICAL;
  }

  public static Builder newBuilder() {
    return new Builder();
  }

  @Override
  public void getItemOffsets(Rect outRect, View view,
                             RecyclerView parent, RecyclerView.State state) {
    int position = parent.getChildAdapterPosition(view);
    boolean isFirstCell = position == 0;
    boolean isLastCell = position == parent.getAdapter().getItemCount() - 1;

    if (isFirstCell) {
      if (isVertical) {
        outRect.top = includeEdge ? spacing : 0;
        outRect.bottom = isLastCell ? (includeEdge ? spacing : 0) : 0;
      } else {
        outRect.left = includeEdge ? spacing : 0;
        outRect.right = spacing / 2;
      }
    } else if (isLastCell) {
      if (isVertical) {
        outRect.top = spacing;
        outRect.bottom = includeEdge ? spacing : 0;
      } else {
        outRect.left = spacing / 2;
        outRect.right = includeEdge ? spacing : 0;
      }
    } else {
      if (isVertical) {
        outRect.top = spacing;
        outRect.bottom = 0;
      } else {
        outRect.left = spacing / 2;
        outRect.right = spacing / 2;
      }
    }
  }

  public static final class Builder {
    private int spacing;
    private boolean includeEdge;
    private int orientation;

    private Builder() {
    }

    public Builder spacing(int val) {
      spacing = val;
      return this;
    }

    public Builder includeEdge(boolean includeEdge) {
      this.includeEdge = includeEdge;
      return this;
    }

    public Builder orientation(int orientation) {
      this.orientation = orientation;
      return this;
    }

    public LinearSpacingItemDecoration build() {
      return new LinearSpacingItemDecoration(this);
    }
  }

} @SuppressWarnings("unchecked")
public class SectionHeaderItemDecoration extends RecyclerView.ItemDecoration {

  private SectionHeaderProvider provider;
  private SimpleRecyclerView simpleRecyclerView;
  private LinearLayoutManager layoutManager;
  private int sectionHeight;
  private boolean isHeaderOverlapped;
  private int firstHeaderTop;
  private int secondHeaderTop;
  private boolean isClipToPadding;
  private Class clazz;

  public SectionHeaderItemDecoration(Class clazz, SectionHeaderProvider provider) {
    this.clazz = clazz;
    this.provider = provider;
  }

  @SuppressLint("NewApi")
  @Override
  public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
    // init
    if (simpleRecyclerView == null) {
      simpleRecyclerView = ((SimpleRecyclerView) parent);
    }
    if (layoutManager == null) {
      layoutManager = (LinearLayoutManager) parent.getLayoutManager();
    }
    isClipToPadding = parent.getClipToPadding();

    int position = parent.getChildAdapterPosition(view);

    if (position == NO_POSITION || !isSectionType(position)) {
      return;
    }

    if (sectionHeight == 0) {
      View sectionHeader = getAndMeasureSectionHeader(parent, position);
      sectionHeight = sectionHeader.getMeasuredHeight();
    }

    if (!isSameSection(position)) {
      outRect.top = sectionHeight + provider.getSectionHeaderMarginTop(getItem(position), position);
    } else {
      outRect.top = 0;
    }
  }

  // draw section header
  @SuppressLint("NewApi")
  @Override
  public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
    int topPadding = isClipToPadding ? parent.getPaddingTop() : 0;
    int left = parent.getPaddingLeft();
    int right = parent.getWidth() - parent.getPaddingRight();

    boolean isFirst = false;
    for (int i = 0; i < parent.getChildCount(); i++) {
      View view = parent.getChildAt(i);
      int position = parent.getChildAdapterPosition(view);
      int top = view.getTop() - sectionHeight;

      if (position != NO_POSITION
              && !isSameSection(position)
              && !isItemCoveredBySection(view.getHeight(), top)) {
        if (!isSectionType(position)) {
          continue;
        }
        View sectionHeader = getAndMeasureSectionHeader(parent, position);
        int bottom = view.getTop();
        boolean isHeaderExit = top <= topPadding;

        if (!isFirst) {
          firstHeaderTop = top;
        }
        if (!isFirst && position != 0) {
          secondHeaderTop = top;
          if (isHeaderExit) {
            isHeaderOverlapped = false;
          } else {
            isHeaderOverlapped = secondHeaderTop <= sectionHeight + topPadding;
          }
        }
        isFirst = true;

        sectionHeader.layout(left, top, right, bottom);
        canvas.save();
        if (isClipToPadding && isHeaderExit) {
          canvas.clipRect(left, topPadding, right, bottom);
        }
        canvas.translate(left, top);
        sectionHeader.draw(canvas);
        canvas.restore();
      }
    }
  }

  // draw sticky section header
  @SuppressLint("NewApi")
  @Override
  public void onDrawOver(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
    if (!provider.isSticky()) {
      return;
    }

    if (layoutManager == null) {
      layoutManager = (LinearLayoutManager) parent.getLayoutManager();
    }

    int position = layoutManager.findFirstVisibleItemPosition();

    if (position == NO_POSITION) {
      return;
    }

    if (!isSectionType(position)) {
      return;
    }

    int topPadding = isClipToPadding ? parent.getPaddingTop() : 0;
    int left = parent.getPaddingLeft();
    int right = parent.getWidth() - parent.getPaddingRight();
    int top = topPadding;
    int bottom = top + sectionHeight;

    // if android:isClipToPadding="false", first header can be scroll up till reaching top.
    if (!isClipToPadding && position == 0) {
      top = firstHeaderTop > 0 ? firstHeaderTop : 0;
      bottom = top + sectionHeight;
    }

    if (isHeaderOverlapped) {
      top = top - topPadding - (sectionHeight - secondHeaderTop);
      bottom = top + sectionHeight;
    }

    boolean isHeaderExit = top <= topPadding;

    if (isHeaderExit) {
      isHeaderOverlapped = false;
    }

    View sectionHeader = getAndMeasureSectionHeader(parent, position);
    sectionHeader.layout(left, top, right, bottom);
    canvas.save();
    if (isClipToPadding && isHeaderExit) {
      canvas.clipRect(left, topPadding, right, bottom);
    }
    canvas.translate(left, top);
    sectionHeader.draw(canvas);
    canvas.restore();
  }

  private boolean isItemCoveredBySection(int itemHeight, int sectionTop) {
    return itemHeight + sectionTop <= 0;
  }
  
  private View getAndMeasureSectionHeader(RecyclerView parent, int position) {
    View sectionHeader = provider.getSectionHeaderView(getItem(position), position);
    int widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY);
    int heightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.AT_MOST);
    sectionHeader.measure(widthSpec, heightSpec);
    return sectionHeader;
  }

  private boolean isSameSection(int position) {
    if (position == 0) {
      return false;
    }

    return isSectionType(position) && isSectionType(position - 1) &&
      provider.isSameSection(getItem(position), getItem(position - 1));
  }

  private boolean isSectionType(int position) {
    Class<?> aClass = getItem(position).getClass();

    // handle realm proxy class
    if (aClass.getName().endsWith("Proxy")) {
      aClass = aClass.getSuperclass();
    }

    return clazz.getCanonicalName().equals(aClass.getCanonicalName());
  }

  private Object getItem(int position) {
    return simpleRecyclerView.getCell(position).getItem();
  }

} public interface SectionHeaderProvider<T> {
  @NonNull View getSectionHeaderView(@NonNull T item, int position);
  boolean isSameSection(@NonNull T item, @NonNull T nextItem);
  boolean isSticky();
  int getSectionHeaderMarginTop(@NonNull T item, int position);
}
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.InsetDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RectShape;
import android.support.annotation.ColorInt;
import android.support.annotation.ColorRes;
import android.support.annotation.IdRes;
import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.LinearSnapHelper;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SimpleItemAnimator;
import android.support.v7.widget.SnapHelper;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import com.jaychang.srv.behavior.DragAndDropCallback;
import com.jaychang.srv.behavior.DragAndDropHelper;
import com.jaychang.srv.behavior.DragAndDropOptions;
import com.jaychang.srv.behavior.SnapAlignment;
import com.jaychang.srv.behavior.StartSnapHelper;
import com.jaychang.srv.behavior.SwipeDirection;
import com.jaychang.srv.behavior.SwipeToDismissCallback;
import com.jaychang.srv.behavior.SwipeToDismissHelper;
import com.jaychang.srv.behavior.SwipeToDismissOptions;
import com.jaychang.srv.decoration.DividerItemDecoration;
import com.jaychang.srv.decoration.GridSpacingItemDecoration;
import com.jaychang.srv.decoration.LinearSpacingItemDecoration;
import com.jaychang.srv.decoration.SectionHeaderItemDecoration;
import com.jaychang.srv.decoration.SectionHeaderProvider;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
    addCell(emptyStateViewCell);

    isEmptyViewShown = true;
  }

  public void hideEmptyStateView() {
    if (!isEmptyViewShown || emptyStateViewCell == null) {
      return;
    }

    removeCell(emptyStateViewCell);

    isEmptyViewShown = false;
  }

  public void setEmptyStateView(@LayoutRes int emptyStateView) {
    View view = LayoutInflater.from(getContext()).inflate(emptyStateView, this, false);
    setEmptyStateView(view);
  }

  public void setEmptyStateView(@NonNull View emptyStateView) {
    this.emptyStateViewCell = new InternalEmptyStateViewCell(emptyStateView);
    emptyStateViewCell.setSpanSize(gridSpanCount);
  }

  /**
   * load more
   */
  public void setLoadMoreView(@LayoutRes int loadMoreView) {
    View view = LayoutInflater.from(getContext()).inflate(loadMoreView, this, false);
    setLoadMoreView(view);
  }

  public void setLoadMoreView(@NonNull View loadMoreView) {
    this.loadMoreViewCell = new InternalLoadMoreViewCell(loadMoreView);
    loadMoreViewCell.setSpanSize(gridSpanCount);
  }

  public void showLoadMoreView() {
    if (loadMoreViewCell == null || isLoadMoreViewShown) {
      isLoadingMore = true;
      return;
    }

    if (isLoadMoreToTop) {
      addCell(0, loadMoreViewCell);
    } else {
      addCell(loadMoreViewCell);
    }

    isLoadMoreViewShown = true;
    isLoadingMore = true;
  }

  public void hideLoadMoreView() {
    if (loadMoreViewCell == null || !isLoadMoreViewShown) {
      isLoadingMore = false;
      return;
    }

    removeCell(loadMoreViewCell);

    isLoadMoreViewShown = false;
    isLoadingMore = false;
  }

  public void setAutoLoadMoreThreshold(int hiddenCellCount) {
    if (hiddenCellCount < 0) {
      throw new IllegalArgumentException("hiddenCellCount must >= 0");
    }
    this.autoLoadMoreThreshold = hiddenCellCount;
  }

  public int getAutoLoadMoreThreshold() {
    return autoLoadMoreThreshold;
  }

  public void setLoadMoreToTop(boolean isLoadMoreForTop) {
    this.isLoadMoreToTop = isLoadMoreForTop;
  }

  public boolean isLoadMoreToTop() {
    return isLoadMoreToTop;
  }

  public void setOnLoadMoreListener(@NonNull OnLoadMoreListener listener) {
    this.onLoadMoreListener = listener;
  }

  @Deprecated
  public void setLoadMoreCompleted() {
    this.isLoadingMore = false;
  }

  /**
   * drag & drop
   */
  public void enableDragAndDrop(@NonNull DragAndDropCallback dragAndDropCallback) {
    enableDragAndDrop(0, dragAndDropCallback);
  }

  public void enableDragAndDrop(@IdRes int dragHandleId, @NonNull DragAndDropCallback dragAndDropCallback) {
    DragAndDropOptions options = new DragAndDropOptions();
    options.setDragHandleId(dragHandleId);
    options.setCanLongPressToDrag(dragHandleId == 0);
    options.setDragAndDropCallback(dragAndDropCallback);
    options.setEnableDefaultEffect(dragAndDropCallback.enableDefaultRaiseEffect());
    DragAndDropHelper dragAndDropHelper = DragAndDropHelper.create(adapter, options);
    adapter.setDragAndDropHelper(dragAndDropHelper);
    dragAndDropHelper.attachToRecyclerView(this);
  }

  /**
   * swipe to dismiss
   */
  public void enableSwipeToDismiss(@NonNull SwipeToDismissCallback swipeToDismissCallback, @NonNull SwipeDirection... directions) {
    enableSwipeToDismiss(swipeToDismissCallback, new HashSet<>(Arrays.asList(directions)));
  }

  public void enableSwipeToDismiss(@NonNull SwipeToDismissCallback swipeToDismissCallback, @NonNull Set<SwipeDirection> directions) {
    SwipeToDismissOptions options = new SwipeToDismissOptions();