content: package io.virtualapp.widgets;
  
  import android.os.Bundle;
  import android.support.annotation.CallSuper;
  import android.support.v7.widget.RecyclerView;
  
  import java.util.ArrayList;
  
  /**
   * @author Aidan Follestad (afollestad)
   */
  public abstract class DragSelectRecyclerViewAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {
  
      private ArrayList<Integer> mSelectedIndices;
      private SelectionListener mSelectionListener;
      private int mLastCount = -1;
      private int mMaxSelectionCount = -1;
      protected DragSelectRecyclerViewAdapter() {
          mSelectedIndices = new ArrayList<>();
      }
  
      private void fireSelectionListener() {
          if (mLastCount == mSelectedIndices.size())
              return;
          mLastCount = mSelectedIndices.size();
          if (mSelectionListener != null)
              mSelectionListener.onDragSelectionChanged(mLastCount);
      }
  
      public void setMaxSelectionCount(int maxSelectionCount) {
          this.mMaxSelectionCount = maxSelectionCount;
      }
  
      public void setSelectionListener(SelectionListener selectionListener) {
          this.mSelectionListener = selectionListener;
      }
  
      public void saveInstanceState(Bundle out) {
          saveInstanceState("selected_indices", out);
      }
  
      public void saveInstanceState(String key, Bundle out) {
          out.putSerializable(key, mSelectedIndices);
      }
  
      public void restoreInstanceState(Bundle in) {
          restoreInstanceState("selected_indices", in);
      }
  
      public void restoreInstanceState(String key, Bundle in) {
          if (in != null && in.containsKey(key)) {
              //noinspection unchecked
              mSelectedIndices = (ArrayList<Integer>) in.getSerializable(key);
              if (mSelectedIndices == null) mSelectedIndices = new ArrayList<>();
              else fireSelectionListener();
          }
      }
  
      public final void setSelected(int index, boolean selected) {
          if (!isIndexSelectable(index))
              selected = false;
          if (selected) {
              if (!mSelectedIndices.contains(index) &&
                      (mMaxSelectionCount == -1 ||
                              mSelectedIndices.size() < mMaxSelectionCount)) {
                  mSelectedIndices.add(index);
                  notifyItemChanged(index);
              }
          } else if (mSelectedIndices.contains(index)) {
              mSelectedIndices.remove((Integer) index);
              notifyItemChanged(index);
          }
          fireSelectionListener();
      }
  
      public final boolean toggleSelected(int index) {
          boolean selectedNow = false;
          if (isIndexSelectable(index)) {
              if (mSelectedIndices.contains(index)) {
                  mSelectedIndices.remove((Integer) index);
              } else if (mMaxSelectionCount == -1 ||
                      mSelectedIndices.size() < mMaxSelectionCount) {
                  mSelectedIndices.add(index);
                  selectedNow = true;
              }
              notifyItemChanged(index);
          }
          fireSelectionListener();
          return selectedNow;
      }
  
      protected boolean isIndexSelectable(int index) {
          return true;
      }
  
      @CallSuper
      @Override
      public void onBindViewHolder(VH holder, int position) {
          holder.itemView.setTag(holder);
      }
  
      public final void selectRange(int from, int to, int min, int max) {
          if (from == to) {
              // Finger is back on the initial item, unselect everything else
              for (int i = min; i <= max; i++) {
                  if (i == from) continue;
                  setSelected(i, false);
              }
              fireSelectionListener();
              return;
          }
  
          if (to < from) {
              // When selecting from one to previous items
              for (int i = to; i <= from; i++)
                  setSelected(i, true);
              if (min > -1 && min < to) {
                  // Unselect items that were selected during this drag but no longer are
                  for (int i = min; i < to; i++) {
                      if (i == from) continue;
                      setSelected(i, false);
                  }
              }
              if (max > -1) {
                  for (int i = from + 1; i <= max; i++)
                      setSelected(i, false);
              }
          } else {
              // When selecting from one to next items
              for (int i = from; i <= to; i++)
                  setSelected(i, true);
              if (max > -1 && max > to) {
                  // Unselect items that were selected during this drag but no longer are
                  for (int i = to + 1; i <= max; i++) {
                      if (i == from) continue;
                      setSelected(i, false);
                  }
              }
              if (min > -1) {
                  for (int i = min; i < from; i++)
                      setSelected(i, false);
              }
          }
          fireSelectionListener();
      }
  
      public final void selectAll() {
          int max = getItemCount();
          mSelectedIndices.clear();
          for (int i = 0; i < max; i++) {
              if (isIndexSelectable(i)) {
                  mSelectedIndices.add(i);
              }
          }
          notifyDataSetChanged();
          fireSelectionListener();
      }
  
      public final void clearSelected() {
          mSelectedIndices.clear();
          notifyDataSetChanged();
          fireSelectionListener();
      }
  
      public final int getSelectedCount() {
          return mSelectedIndices.size();
      }
  
      public final Integer[] getSelectedIndices() {
          return mSelectedIndices.toArray(new Integer[mSelectedIndices.size()]);
      }
  
      public final boolean isIndexSelected(int index) {
          return mSelectedIndices.contains(index);
      }
  
      public interface SelectionListener {
          void onDragSelectionChanged(int count);
      }
  }