Advertisement
Guest User

Untitled

a guest
Feb 21st, 2017
65
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 13.34 KB | None | 0 0
  1. package be.digitalia.common.widgets;
  2.  
  3. import android.content.Context;
  4. import android.os.Build;
  5. import android.os.Parcel;
  6. import android.os.Parcelable;
  7. import android.support.annotation.NonNull;
  8. import android.support.v4.util.LongSparseArray;
  9. import android.support.v7.app.AppCompatActivity;
  10. import android.support.v7.view.ActionMode;
  11. import android.support.v7.widget.RecyclerView;
  12. import android.util.SparseBooleanArray;
  13. import android.view.Menu;
  14. import android.view.MenuItem;
  15. import android.view.View;
  16. import android.widget.Checkable;
  17.  
  18. /**
  19. * Helper class to reproduce ListView's modal MultiChoice mode with a RecyclerView.
  20. * Compatible with API 7+.
  21. * Declare and use this class from inside your Adapter.
  22. *
  23. * @author Christophe Beyls
  24. */
  25. public class MultiChoiceHelper {
  26.  
  27. /**
  28. * A handy ViewHolder base class which works with the MultiChoiceHelper
  29. * and reproduces the default behavior of a ListView.
  30. */
  31. public static abstract class ViewHolder extends RecyclerView.ViewHolder {
  32.  
  33. View.OnClickListener clickListener;
  34. MultiChoiceHelper multiChoiceHelper;
  35.  
  36. public ViewHolder(View itemView) {
  37. super(itemView);
  38. itemView.setOnClickListener(new View.OnClickListener() {
  39. @Override
  40. public void onClick(View view) {
  41. if (isMultiChoiceActive()) {
  42. int position = getAdapterPosition();
  43. if (position != RecyclerView.NO_POSITION) {
  44. multiChoiceHelper.toggleItemChecked(position, false);
  45. updateCheckedState(position);
  46. }
  47. } else {
  48. if (clickListener != null) {
  49. clickListener.onClick(view);
  50. }
  51. }
  52. }
  53. });
  54. itemView.setOnLongClickListener(new View.OnLongClickListener() {
  55. @Override
  56. public boolean onLongClick(View view) {
  57. if ((multiChoiceHelper == null) || isMultiChoiceActive()) {
  58. return false;
  59. }
  60. int position = getAdapterPosition();
  61. if (position != RecyclerView.NO_POSITION) {
  62. multiChoiceHelper.setItemChecked(position, true, false);
  63. updateCheckedState(position);
  64. }
  65. return true;
  66. }
  67. });
  68. }
  69.  
  70. void updateCheckedState(int position) {
  71. final boolean isChecked = multiChoiceHelper.isItemChecked(position);
  72. if (itemView instanceof Checkable) {
  73. ((Checkable) itemView).setChecked(isChecked);
  74. } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
  75. itemView.setActivated(isChecked);
  76. }
  77. }
  78.  
  79. public void setOnClickListener(View.OnClickListener clickListener) {
  80. this.clickListener = clickListener;
  81. }
  82.  
  83. public void bind(MultiChoiceHelper multiChoiceHelper, int position) {
  84. this.multiChoiceHelper = multiChoiceHelper;
  85. if (multiChoiceHelper != null) {
  86. updateCheckedState(position);
  87. }
  88. }
  89.  
  90. public boolean isMultiChoiceActive() {
  91. return (multiChoiceHelper != null) && (multiChoiceHelper.getCheckedItemCount() > 0);
  92. }
  93. }
  94.  
  95. public interface MultiChoiceModeListener extends ActionMode.Callback {
  96. /**
  97. * Called when an item is checked or unchecked during selection mode.
  98. *
  99. * @param mode The {@link ActionMode} providing the selection startSupportActionModemode
  100. * @param position Adapter position of the item that was checked or unchecked
  101. * @param id Adapter ID of the item that was checked or unchecked
  102. * @param checked <code>true</code> if the item is now checked, <code>false</code>
  103. * if the item is now unchecked.
  104. */
  105. void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked);
  106. }
  107.  
  108. private static final int CHECK_POSITION_SEARCH_DISTANCE = 20;
  109.  
  110. private final AppCompatActivity activity;
  111. private final RecyclerView.Adapter adapter;
  112. private SparseBooleanArray checkStates;
  113. private LongSparseArray<Integer> checkedIdStates;
  114. private int checkedItemCount = 0;
  115. private MultiChoiceModeWrapper multiChoiceModeCallback;
  116. ActionMode choiceActionMode;
  117.  
  118. /**
  119. * Make sure this constructor is called before setting the adapter on the RecyclerView
  120. * so this class will be notified before the RecyclerView in case of data set changes.
  121. */
  122. public MultiChoiceHelper(@NonNull AppCompatActivity activity, @NonNull RecyclerView.Adapter adapter) {
  123. this.activity = activity;
  124. this.adapter = adapter;
  125. adapter.registerAdapterDataObserver(new AdapterDataSetObserver());
  126. checkStates = new SparseBooleanArray(0);
  127. if (adapter.hasStableIds()) {
  128. checkedIdStates = new LongSparseArray<>(0);
  129. }
  130. }
  131.  
  132. public Context getContext() {
  133. return activity;
  134. }
  135.  
  136. public void setMultiChoiceModeListener(MultiChoiceModeListener listener) {
  137. if (listener == null) {
  138. multiChoiceModeCallback = null;
  139. return;
  140. }
  141. if (multiChoiceModeCallback == null) {
  142. multiChoiceModeCallback = new MultiChoiceModeWrapper();
  143. }
  144. multiChoiceModeCallback.setWrapped(listener);
  145. }
  146.  
  147. public int getCheckedItemCount() {
  148. return checkedItemCount;
  149. }
  150.  
  151. public boolean isItemChecked(int position) {
  152. return checkStates.get(position);
  153. }
  154.  
  155. public SparseBooleanArray getCheckedItemPositions() {
  156. return checkStates;
  157. }
  158.  
  159. public long[] getCheckedItemIds() {
  160. final LongSparseArray<Integer> idStates = checkedIdStates;
  161. if (idStates == null) {
  162. return new long[0];
  163. }
  164.  
  165. final int count = idStates.size();
  166. final long[] ids = new long[count];
  167.  
  168. for (int i = 0; i < count; i++) {
  169. ids[i] = idStates.keyAt(i);
  170. }
  171.  
  172. return ids;
  173. }
  174.  
  175. public void clearChoices() {
  176. if (checkedItemCount > 0) {
  177. final int start = checkStates.keyAt(0);
  178. final int end = checkStates.keyAt(checkStates.size() - 1);
  179. checkStates.clear();
  180. if (checkedIdStates != null) {
  181. checkedIdStates.clear();
  182. }
  183. checkedItemCount = 0;
  184.  
  185. adapter.notifyItemRangeChanged(start, end - start + 1);
  186.  
  187. if (choiceActionMode != null) {
  188. choiceActionMode.finish();
  189. }
  190. }
  191. }
  192.  
  193. public void setItemChecked(int position, boolean value, boolean notifyChanged) {
  194. // Start selection mode if needed. We don't need to if we're unchecking something.
  195. if (value) {
  196. startSupportActionModeIfNeeded();
  197. }
  198.  
  199. boolean oldValue = checkStates.get(position);
  200. checkStates.put(position, value);
  201.  
  202. if (oldValue != value) {
  203. final long id = adapter.getItemId(position);
  204.  
  205. if (checkedIdStates != null) {
  206. if (value) {
  207. checkedIdStates.put(id, position);
  208. } else {
  209. checkedIdStates.delete(id);
  210. }
  211. }
  212.  
  213. if (value) {
  214. checkedItemCount++;
  215. } else {
  216. checkedItemCount--;
  217. }
  218.  
  219. if (notifyChanged) {
  220. adapter.notifyItemChanged(position);
  221. }
  222.  
  223. if (choiceActionMode != null) {
  224. multiChoiceModeCallback.onItemCheckedStateChanged(choiceActionMode, position, id, value);
  225. if (checkedItemCount == 0) {
  226. choiceActionMode.finish();
  227. }
  228. }
  229. }
  230. }
  231.  
  232. public void toggleItemChecked(int position, boolean notifyChanged) {
  233. setItemChecked(position, !isItemChecked(position), notifyChanged);
  234. }
  235.  
  236. public Parcelable onSaveInstanceState() {
  237. SavedState savedState = new SavedState();
  238. savedState.checkedItemCount = checkedItemCount;
  239. savedState.checkStates = clone(checkStates);
  240. if (checkedIdStates != null) {
  241. savedState.checkedIdStates = checkedIdStates.clone();
  242. }
  243. return savedState;
  244. }
  245.  
  246. private static SparseBooleanArray clone(SparseBooleanArray original) {
  247. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
  248. return original.clone();
  249. }
  250. final int size = original.size();
  251. SparseBooleanArray clone = new SparseBooleanArray(size);
  252. for (int i = 0; i < size; ++i) {
  253. clone.append(original.keyAt(i), original.valueAt(i));
  254. }
  255. return clone;
  256. }
  257.  
  258. public void onRestoreInstanceState(Parcelable state) {
  259. if ((state != null) && (checkedItemCount == 0)) {
  260. SavedState savedState = (SavedState) state;
  261. checkedItemCount = savedState.checkedItemCount;
  262. checkStates = savedState.checkStates;
  263. checkedIdStates = savedState.checkedIdStates;
  264.  
  265. if (checkedItemCount > 0) {
  266. // Empty adapter is given a chance to be populated before completeRestoreInstanceState()
  267. if (adapter.getItemCount() > 0) {
  268. confirmCheckedPositions();
  269. }
  270. activity.getWindow().getDecorView().post(new Runnable() {
  271. @Override
  272. public void run() {
  273. completeRestoreInstanceState();
  274. }
  275. });
  276. }
  277. }
  278. }
  279.  
  280. void completeRestoreInstanceState() {
  281. if (checkedItemCount > 0) {
  282. if (adapter.getItemCount() == 0) {
  283. // Adapter was not populated, clear the selection
  284. confirmCheckedPositions();
  285. } else {
  286. startSupportActionModeIfNeeded();
  287. }
  288. }
  289. }
  290.  
  291. private void startSupportActionModeIfNeeded() {
  292. if (choiceActionMode == null) {
  293. if (multiChoiceModeCallback == null) {
  294. throw new IllegalStateException("No callback set");
  295. }
  296. choiceActionMode = activity.startSupportActionMode(multiChoiceModeCallback);
  297. }
  298. }
  299.  
  300. public static class SavedState implements Parcelable {
  301.  
  302. int checkedItemCount;
  303. SparseBooleanArray checkStates;
  304. LongSparseArray<Integer> checkedIdStates;
  305.  
  306. SavedState() {
  307. }
  308.  
  309. SavedState(Parcel in) {
  310. checkedItemCount = in.readInt();
  311. checkStates = in.readSparseBooleanArray();
  312. final int n = in.readInt();
  313. if (n >= 0) {
  314. checkedIdStates = new LongSparseArray<>(n);
  315. for (int i = 0; i < n; i++) {
  316. final long key = in.readLong();
  317. final int value = in.readInt();
  318. checkedIdStates.append(key, value);
  319. }
  320. }
  321. }
  322.  
  323. @Override
  324. public void writeToParcel(Parcel out, int flags) {
  325. out.writeInt(checkedItemCount);
  326. out.writeSparseBooleanArray(checkStates);
  327. final int n = checkedIdStates != null ? checkedIdStates.size() : -1;
  328. out.writeInt(n);
  329. for (int i = 0; i < n; i++) {
  330. out.writeLong(checkedIdStates.keyAt(i));
  331. out.writeInt(checkedIdStates.valueAt(i));
  332. }
  333. }
  334.  
  335. @Override
  336. public int describeContents() {
  337. return 0;
  338. }
  339.  
  340. public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
  341. @Override
  342. public SavedState createFromParcel(Parcel in) {
  343. return new SavedState(in);
  344. }
  345.  
  346. @Override
  347. public SavedState[] newArray(int size) {
  348. return new SavedState[size];
  349. }
  350. };
  351. }
  352.  
  353. void confirmCheckedPositions() {
  354. if (checkedItemCount == 0) {
  355. return;
  356. }
  357.  
  358. final int itemCount = adapter.getItemCount();
  359. boolean checkedCountChanged = false;
  360.  
  361. if (itemCount == 0) {
  362. // Optimized path for empty adapter: remove all items.
  363. checkStates.clear();
  364. if (checkedIdStates != null) {
  365. checkedIdStates.clear();
  366. }
  367. checkedItemCount = 0;
  368. checkedCountChanged = true;
  369. } else if (checkedIdStates != null) {
  370. // Clear out the positional check states, we'll rebuild it below from IDs.
  371. checkStates.clear();
  372.  
  373. for (int checkedIndex = 0; checkedIndex < checkedIdStates.size(); checkedIndex++) {
  374. final long id = checkedIdStates.keyAt(checkedIndex);
  375. final int lastPos = checkedIdStates.valueAt(checkedIndex);
  376.  
  377. if ((lastPos >= itemCount) || (id != adapter.getItemId(lastPos))) {
  378. // Look around to see if the ID is nearby. If not, uncheck it.
  379. final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE);
  380. final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, itemCount);
  381. boolean found = false;
  382. for (int searchPos = start; searchPos < end; searchPos++) {
  383. final long searchId = adapter.getItemId(searchPos);
  384. if (id == searchId) {
  385. found = true;
  386. checkStates.put(searchPos, true);
  387. checkedIdStates.setValueAt(checkedIndex, searchPos);
  388. break;
  389. }
  390. }
  391.  
  392. if (!found) {
  393. checkedIdStates.delete(id);
  394. checkedIndex--;
  395. checkedItemCount--;
  396. checkedCountChanged = true;
  397. if (choiceActionMode != null && multiChoiceModeCallback != null) {
  398. multiChoiceModeCallback.onItemCheckedStateChanged(choiceActionMode, lastPos, id, false);
  399. }
  400. }
  401. } else {
  402. checkStates.put(lastPos, true);
  403. }
  404. }
  405. } else {
  406. // If the total number of items decreased, remove all out-of-range check indexes.
  407. for (int i = checkStates.size() - 1; (i >= 0) && (checkStates.keyAt(i) >= itemCount); i--) {
  408. if (checkStates.valueAt(i)) {
  409. checkedItemCount--;
  410. checkedCountChanged = true;
  411. }
  412. checkStates.delete(checkStates.keyAt(i));
  413. }
  414. }
  415.  
  416. if (checkedCountChanged && choiceActionMode != null) {
  417. if (checkedItemCount == 0) {
  418. choiceActionMode.finish();
  419. } else {
  420. choiceActionMode.invalidate();
  421. }
  422. }
  423. }
  424.  
  425. class AdapterDataSetObserver extends RecyclerView.AdapterDataObserver {
  426.  
  427. @Override
  428. public void onChanged() {
  429. confirmCheckedPositions();
  430. }
  431.  
  432. @Override
  433. public void onItemRangeInserted(int positionStart, int itemCount) {
  434. confirmCheckedPositions();
  435. }
  436.  
  437. @Override
  438. public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
  439. confirmCheckedPositions();
  440. }
  441.  
  442. @Override
  443. public void onItemRangeRemoved(int positionStart, int itemCount) {
  444. confirmCheckedPositions();
  445. }
  446. }
  447.  
  448. class MultiChoiceModeWrapper implements MultiChoiceModeListener {
  449.  
  450. private MultiChoiceModeListener wrapped;
  451.  
  452. public void setWrapped(@NonNull MultiChoiceModeListener wrapped) {
  453. this.wrapped = wrapped;
  454. }
  455.  
  456. @Override
  457. public boolean onCreateActionMode(ActionMode mode, Menu menu) {
  458. return wrapped.onCreateActionMode(mode, menu);
  459. }
  460.  
  461. @Override
  462. public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
  463. return wrapped.onPrepareActionMode(mode, menu);
  464. }
  465.  
  466. @Override
  467. public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
  468. return wrapped.onActionItemClicked(mode, item);
  469. }
  470.  
  471. @Override
  472. public void onDestroyActionMode(ActionMode mode) {
  473. wrapped.onDestroyActionMode(mode);
  474. choiceActionMode = null;
  475. clearChoices();
  476. }
  477.  
  478. @Override
  479. public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
  480. wrapped.onItemCheckedStateChanged(mode, position, id, checked);
  481. }
  482. }
  483. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement