Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- package be.digitalia.common.widgets;
- import android.content.Context;
- import android.os.Build;
- import android.os.Parcel;
- import android.os.Parcelable;
- import android.support.annotation.NonNull;
- import android.support.v4.util.LongSparseArray;
- import android.support.v7.app.AppCompatActivity;
- import android.support.v7.view.ActionMode;
- import android.support.v7.widget.RecyclerView;
- import android.util.SparseBooleanArray;
- import android.view.Menu;
- import android.view.MenuItem;
- import android.view.View;
- import android.widget.Checkable;
- /**
- * Helper class to reproduce ListView's modal MultiChoice mode with a RecyclerView.
- * Compatible with API 7+.
- * Declare and use this class from inside your Adapter.
- *
- * @author Christophe Beyls
- */
- public class MultiChoiceHelper {
- /**
- * A handy ViewHolder base class which works with the MultiChoiceHelper
- * and reproduces the default behavior of a ListView.
- */
- public static abstract class ViewHolder extends RecyclerView.ViewHolder {
- View.OnClickListener clickListener;
- MultiChoiceHelper multiChoiceHelper;
- public ViewHolder(View itemView) {
- super(itemView);
- itemView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- if (isMultiChoiceActive()) {
- int position = getAdapterPosition();
- if (position != RecyclerView.NO_POSITION) {
- multiChoiceHelper.toggleItemChecked(position, false);
- updateCheckedState(position);
- }
- } else {
- if (clickListener != null) {
- clickListener.onClick(view);
- }
- }
- }
- });
- itemView.setOnLongClickListener(new View.OnLongClickListener() {
- @Override
- public boolean onLongClick(View view) {
- if ((multiChoiceHelper == null) || isMultiChoiceActive()) {
- return false;
- }
- int position = getAdapterPosition();
- if (position != RecyclerView.NO_POSITION) {
- multiChoiceHelper.setItemChecked(position, true, false);
- updateCheckedState(position);
- }
- return true;
- }
- });
- }
- void updateCheckedState(int position) {
- final boolean isChecked = multiChoiceHelper.isItemChecked(position);
- if (itemView instanceof Checkable) {
- ((Checkable) itemView).setChecked(isChecked);
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
- itemView.setActivated(isChecked);
- }
- }
- public void setOnClickListener(View.OnClickListener clickListener) {
- this.clickListener = clickListener;
- }
- public void bind(MultiChoiceHelper multiChoiceHelper, int position) {
- this.multiChoiceHelper = multiChoiceHelper;
- if (multiChoiceHelper != null) {
- updateCheckedState(position);
- }
- }
- public boolean isMultiChoiceActive() {
- return (multiChoiceHelper != null) && (multiChoiceHelper.getCheckedItemCount() > 0);
- }
- }
- public interface MultiChoiceModeListener extends ActionMode.Callback {
- /**
- * Called when an item is checked or unchecked during selection mode.
- *
- * @param mode The {@link ActionMode} providing the selection startSupportActionModemode
- * @param position Adapter position of the item that was checked or unchecked
- * @param id Adapter ID of the item that was checked or unchecked
- * @param checked <code>true</code> if the item is now checked, <code>false</code>
- * if the item is now unchecked.
- */
- void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked);
- }
- private static final int CHECK_POSITION_SEARCH_DISTANCE = 20;
- private final AppCompatActivity activity;
- private final RecyclerView.Adapter adapter;
- private SparseBooleanArray checkStates;
- private LongSparseArray<Integer> checkedIdStates;
- private int checkedItemCount = 0;
- private MultiChoiceModeWrapper multiChoiceModeCallback;
- ActionMode choiceActionMode;
- /**
- * Make sure this constructor is called before setting the adapter on the RecyclerView
- * so this class will be notified before the RecyclerView in case of data set changes.
- */
- public MultiChoiceHelper(@NonNull AppCompatActivity activity, @NonNull RecyclerView.Adapter adapter) {
- this.activity = activity;
- this.adapter = adapter;
- adapter.registerAdapterDataObserver(new AdapterDataSetObserver());
- checkStates = new SparseBooleanArray(0);
- if (adapter.hasStableIds()) {
- checkedIdStates = new LongSparseArray<>(0);
- }
- }
- public Context getContext() {
- return activity;
- }
- public void setMultiChoiceModeListener(MultiChoiceModeListener listener) {
- if (listener == null) {
- multiChoiceModeCallback = null;
- return;
- }
- if (multiChoiceModeCallback == null) {
- multiChoiceModeCallback = new MultiChoiceModeWrapper();
- }
- multiChoiceModeCallback.setWrapped(listener);
- }
- public int getCheckedItemCount() {
- return checkedItemCount;
- }
- public boolean isItemChecked(int position) {
- return checkStates.get(position);
- }
- public SparseBooleanArray getCheckedItemPositions() {
- return checkStates;
- }
- public long[] getCheckedItemIds() {
- final LongSparseArray<Integer> idStates = checkedIdStates;
- if (idStates == null) {
- return new long[0];
- }
- final int count = idStates.size();
- final long[] ids = new long[count];
- for (int i = 0; i < count; i++) {
- ids[i] = idStates.keyAt(i);
- }
- return ids;
- }
- public void clearChoices() {
- if (checkedItemCount > 0) {
- final int start = checkStates.keyAt(0);
- final int end = checkStates.keyAt(checkStates.size() - 1);
- checkStates.clear();
- if (checkedIdStates != null) {
- checkedIdStates.clear();
- }
- checkedItemCount = 0;
- adapter.notifyItemRangeChanged(start, end - start + 1);
- if (choiceActionMode != null) {
- choiceActionMode.finish();
- }
- }
- }
- public void setItemChecked(int position, boolean value, boolean notifyChanged) {
- // Start selection mode if needed. We don't need to if we're unchecking something.
- if (value) {
- startSupportActionModeIfNeeded();
- }
- boolean oldValue = checkStates.get(position);
- checkStates.put(position, value);
- if (oldValue != value) {
- final long id = adapter.getItemId(position);
- if (checkedIdStates != null) {
- if (value) {
- checkedIdStates.put(id, position);
- } else {
- checkedIdStates.delete(id);
- }
- }
- if (value) {
- checkedItemCount++;
- } else {
- checkedItemCount--;
- }
- if (notifyChanged) {
- adapter.notifyItemChanged(position);
- }
- if (choiceActionMode != null) {
- multiChoiceModeCallback.onItemCheckedStateChanged(choiceActionMode, position, id, value);
- if (checkedItemCount == 0) {
- choiceActionMode.finish();
- }
- }
- }
- }
- public void toggleItemChecked(int position, boolean notifyChanged) {
- setItemChecked(position, !isItemChecked(position), notifyChanged);
- }
- public Parcelable onSaveInstanceState() {
- SavedState savedState = new SavedState();
- savedState.checkedItemCount = checkedItemCount;
- savedState.checkStates = clone(checkStates);
- if (checkedIdStates != null) {
- savedState.checkedIdStates = checkedIdStates.clone();
- }
- return savedState;
- }
- private static SparseBooleanArray clone(SparseBooleanArray original) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
- return original.clone();
- }
- final int size = original.size();
- SparseBooleanArray clone = new SparseBooleanArray(size);
- for (int i = 0; i < size; ++i) {
- clone.append(original.keyAt(i), original.valueAt(i));
- }
- return clone;
- }
- public void onRestoreInstanceState(Parcelable state) {
- if ((state != null) && (checkedItemCount == 0)) {
- SavedState savedState = (SavedState) state;
- checkedItemCount = savedState.checkedItemCount;
- checkStates = savedState.checkStates;
- checkedIdStates = savedState.checkedIdStates;
- if (checkedItemCount > 0) {
- // Empty adapter is given a chance to be populated before completeRestoreInstanceState()
- if (adapter.getItemCount() > 0) {
- confirmCheckedPositions();
- }
- activity.getWindow().getDecorView().post(new Runnable() {
- @Override
- public void run() {
- completeRestoreInstanceState();
- }
- });
- }
- }
- }
- void completeRestoreInstanceState() {
- if (checkedItemCount > 0) {
- if (adapter.getItemCount() == 0) {
- // Adapter was not populated, clear the selection
- confirmCheckedPositions();
- } else {
- startSupportActionModeIfNeeded();
- }
- }
- }
- private void startSupportActionModeIfNeeded() {
- if (choiceActionMode == null) {
- if (multiChoiceModeCallback == null) {
- throw new IllegalStateException("No callback set");
- }
- choiceActionMode = activity.startSupportActionMode(multiChoiceModeCallback);
- }
- }
- public static class SavedState implements Parcelable {
- int checkedItemCount;
- SparseBooleanArray checkStates;
- LongSparseArray<Integer> checkedIdStates;
- SavedState() {
- }
- SavedState(Parcel in) {
- checkedItemCount = in.readInt();
- checkStates = in.readSparseBooleanArray();
- final int n = in.readInt();
- if (n >= 0) {
- checkedIdStates = new LongSparseArray<>(n);
- for (int i = 0; i < n; i++) {
- final long key = in.readLong();
- final int value = in.readInt();
- checkedIdStates.append(key, value);
- }
- }
- }
- @Override
- public void writeToParcel(Parcel out, int flags) {
- out.writeInt(checkedItemCount);
- out.writeSparseBooleanArray(checkStates);
- final int n = checkedIdStates != null ? checkedIdStates.size() : -1;
- out.writeInt(n);
- for (int i = 0; i < n; i++) {
- out.writeLong(checkedIdStates.keyAt(i));
- out.writeInt(checkedIdStates.valueAt(i));
- }
- }
- @Override
- public int describeContents() {
- return 0;
- }
- public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
- @Override
- public SavedState createFromParcel(Parcel in) {
- return new SavedState(in);
- }
- @Override
- public SavedState[] newArray(int size) {
- return new SavedState[size];
- }
- };
- }
- void confirmCheckedPositions() {
- if (checkedItemCount == 0) {
- return;
- }
- final int itemCount = adapter.getItemCount();
- boolean checkedCountChanged = false;
- if (itemCount == 0) {
- // Optimized path for empty adapter: remove all items.
- checkStates.clear();
- if (checkedIdStates != null) {
- checkedIdStates.clear();
- }
- checkedItemCount = 0;
- checkedCountChanged = true;
- } else if (checkedIdStates != null) {
- // Clear out the positional check states, we'll rebuild it below from IDs.
- checkStates.clear();
- for (int checkedIndex = 0; checkedIndex < checkedIdStates.size(); checkedIndex++) {
- final long id = checkedIdStates.keyAt(checkedIndex);
- final int lastPos = checkedIdStates.valueAt(checkedIndex);
- if ((lastPos >= itemCount) || (id != adapter.getItemId(lastPos))) {
- // Look around to see if the ID is nearby. If not, uncheck it.
- final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE);
- final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, itemCount);
- boolean found = false;
- for (int searchPos = start; searchPos < end; searchPos++) {
- final long searchId = adapter.getItemId(searchPos);
- if (id == searchId) {
- found = true;
- checkStates.put(searchPos, true);
- checkedIdStates.setValueAt(checkedIndex, searchPos);
- break;
- }
- }
- if (!found) {
- checkedIdStates.delete(id);
- checkedIndex--;
- checkedItemCount--;
- checkedCountChanged = true;
- if (choiceActionMode != null && multiChoiceModeCallback != null) {
- multiChoiceModeCallback.onItemCheckedStateChanged(choiceActionMode, lastPos, id, false);
- }
- }
- } else {
- checkStates.put(lastPos, true);
- }
- }
- } else {
- // If the total number of items decreased, remove all out-of-range check indexes.
- for (int i = checkStates.size() - 1; (i >= 0) && (checkStates.keyAt(i) >= itemCount); i--) {
- if (checkStates.valueAt(i)) {
- checkedItemCount--;
- checkedCountChanged = true;
- }
- checkStates.delete(checkStates.keyAt(i));
- }
- }
- if (checkedCountChanged && choiceActionMode != null) {
- if (checkedItemCount == 0) {
- choiceActionMode.finish();
- } else {
- choiceActionMode.invalidate();
- }
- }
- }
- class AdapterDataSetObserver extends RecyclerView.AdapterDataObserver {
- @Override
- public void onChanged() {
- confirmCheckedPositions();
- }
- @Override
- public void onItemRangeInserted(int positionStart, int itemCount) {
- confirmCheckedPositions();
- }
- @Override
- public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
- confirmCheckedPositions();
- }
- @Override
- public void onItemRangeRemoved(int positionStart, int itemCount) {
- confirmCheckedPositions();
- }
- }
- class MultiChoiceModeWrapper implements MultiChoiceModeListener {
- private MultiChoiceModeListener wrapped;
- public void setWrapped(@NonNull MultiChoiceModeListener wrapped) {
- this.wrapped = wrapped;
- }
- @Override
- public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- return wrapped.onCreateActionMode(mode, menu);
- }
- @Override
- public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
- return wrapped.onPrepareActionMode(mode, menu);
- }
- @Override
- public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
- return wrapped.onActionItemClicked(mode, item);
- }
- @Override
- public void onDestroyActionMode(ActionMode mode) {
- wrapped.onDestroyActionMode(mode);
- choiceActionMode = null;
- clearChoices();
- }
- @Override
- public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
- wrapped.onItemCheckedStateChanged(mode, position, id, checked);
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement