@@ -237,9 +237,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
237237 SparseBooleanArray mCheckStates ;
238238
239239 /**
240- * Running state of which IDs are currently checked
240+ * Running state of which IDs are currently checked.
241+ * If there is a value for a given key, the checked state for that ID is true
242+ * and the value holds the last known position in the adapter for that id.
241243 */
242- LongSparseArray <Boolean > mCheckedIdStates ;
244+ LongSparseArray <Integer > mCheckedIdStates ;
243245
244246 /**
245247 * Controls how the next layout will happen
@@ -471,6 +473,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
471473 */
472474 static final int OVERSCROLL_LIMIT_DIVISOR = 3 ;
473475
476+ /**
477+ * How many positions in either direction we will search to try to
478+ * find a checked item with a stable ID that moved position across
479+ * a data set change. If the item isn't found it will be unselected.
480+ */
481+ private static final int CHECK_POSITION_SEARCH_DISTANCE = 20 ;
482+
474483 /**
475484 * Used to request a layout when we changed touch mode
476485 */
@@ -806,7 +815,7 @@ public void setAdapter(ListAdapter adapter) {
806815 if (adapter != null ) {
807816 if (mChoiceMode != CHOICE_MODE_NONE && mAdapter .hasStableIds () &&
808817 mCheckedIdStates == null ) {
809- mCheckedIdStates = new LongSparseArray <Boolean >();
818+ mCheckedIdStates = new LongSparseArray <Integer >();
810819 }
811820 }
812821
@@ -901,7 +910,7 @@ public long[] getCheckedItemIds() {
901910 return new long [0 ];
902911 }
903912
904- final LongSparseArray <Boolean > idStates = mCheckedIdStates ;
913+ final LongSparseArray <Integer > idStates = mCheckedIdStates ;
905914 final int count = idStates .size ();
906915 final long [] ids = new long [count ];
907916
@@ -948,7 +957,7 @@ public void setItemChecked(int position, boolean value) {
948957 mCheckStates .put (position , value );
949958 if (mCheckedIdStates != null && mAdapter .hasStableIds ()) {
950959 if (value ) {
951- mCheckedIdStates .put (mAdapter .getItemId (position ), Boolean . TRUE );
960+ mCheckedIdStates .put (mAdapter .getItemId (position ), position );
952961 } else {
953962 mCheckedIdStates .delete (mAdapter .getItemId (position ));
954963 }
@@ -980,7 +989,7 @@ public void setItemChecked(int position, boolean value) {
980989 if (value ) {
981990 mCheckStates .put (position , true );
982991 if (updateIds ) {
983- mCheckedIdStates .put (mAdapter .getItemId (position ), Boolean . TRUE );
992+ mCheckedIdStates .put (mAdapter .getItemId (position ), position );
984993 }
985994 mCheckedItemCount = 1 ;
986995 } else if (mCheckStates .size () == 0 || !mCheckStates .valueAt (0 )) {
@@ -1010,7 +1019,7 @@ public boolean performItemClick(View view, int position, long id) {
10101019 mCheckStates .put (position , newValue );
10111020 if (mCheckedIdStates != null && mAdapter .hasStableIds ()) {
10121021 if (newValue ) {
1013- mCheckedIdStates .put (mAdapter .getItemId (position ), Boolean . TRUE );
1022+ mCheckedIdStates .put (mAdapter .getItemId (position ), position );
10141023 } else {
10151024 mCheckedIdStates .delete (mAdapter .getItemId (position ));
10161025 }
@@ -1032,7 +1041,7 @@ public boolean performItemClick(View view, int position, long id) {
10321041 mCheckStates .put (position , true );
10331042 if (mCheckedIdStates != null && mAdapter .hasStableIds ()) {
10341043 mCheckedIdStates .clear ();
1035- mCheckedIdStates .put (mAdapter .getItemId (position ), Boolean . TRUE );
1044+ mCheckedIdStates .put (mAdapter .getItemId (position ), position );
10361045 }
10371046 mCheckedItemCount = 1 ;
10381047 } else if (mCheckStates .size () == 0 || !mCheckStates .valueAt (0 )) {
@@ -1081,7 +1090,7 @@ public void setChoiceMode(int choiceMode) {
10811090 mCheckStates = new SparseBooleanArray ();
10821091 }
10831092 if (mCheckedIdStates == null && mAdapter != null && mAdapter .hasStableIds ()) {
1084- mCheckedIdStates = new LongSparseArray <Boolean >();
1093+ mCheckedIdStates = new LongSparseArray <Integer >();
10851094 }
10861095 // Modal multi-choice mode only has choices when the mode is active. Clear them.
10871096 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL ) {
@@ -1427,7 +1436,7 @@ static class SavedState extends BaseSavedState {
14271436 boolean inActionMode ;
14281437 int checkedItemCount ;
14291438 SparseBooleanArray checkState ;
1430- LongSparseArray <Boolean > checkIdState ;
1439+ LongSparseArray <Integer > checkIdState ;
14311440
14321441 /**
14331442 * Constructor called from {@link AbsListView#onSaveInstanceState()}
@@ -1451,10 +1460,14 @@ private SavedState(Parcel in) {
14511460 checkedItemCount = in .readInt ();
14521461 checkState = in .readSparseBooleanArray ();
14531462 long [] idState = in .createLongArray ();
1463+ int [] idPositions = in .createIntArray ();
14541464
1455- if (idState .length > 0 ) {
1456- checkIdState = new LongSparseArray <Boolean >();
1457- checkIdState .setValues (idState , Boolean .TRUE );
1465+ final int idLength = idState .length ;
1466+ if (idLength > 0 ) {
1467+ checkIdState = new LongSparseArray <Integer >();
1468+ for (int i = 0 ; i < idLength ; i ++) {
1469+ checkIdState .put (idState [i ], idPositions [i ]);
1470+ }
14581471 }
14591472 }
14601473
@@ -1471,6 +1484,15 @@ public void writeToParcel(Parcel out, int flags) {
14711484 out .writeInt (checkedItemCount );
14721485 out .writeSparseBooleanArray (checkState );
14731486 out .writeLongArray (checkIdState != null ? checkIdState .getKeys () : new long [0 ]);
1487+
1488+ int size = checkIdState != null ? checkIdState .size () : 0 ;
1489+ int [] idPositions = new int [size ];
1490+ if (size > 0 ) {
1491+ for (int i = 0 ; i < size ; i ++) {
1492+ idPositions [i ] = checkIdState .valueAt (i );
1493+ }
1494+ out .writeIntArray (idPositions );
1495+ }
14741496 }
14751497
14761498 @ Override
@@ -1565,7 +1587,7 @@ public Parcelable onSaveInstanceState() {
15651587 ss .checkState = mCheckStates .clone ();
15661588 }
15671589 if (mCheckedIdStates != null ) {
1568- final LongSparseArray <Boolean > idState = new LongSparseArray <Boolean >();
1590+ final LongSparseArray <Integer > idState = new LongSparseArray <Integer >();
15691591 final int count = mCheckedIdStates .size ();
15701592 for (int i = 0 ; i < count ; i ++) {
15711593 idState .put (mCheckedIdStates .keyAt (i ), mCheckedIdStates .valueAt (i ));
@@ -4786,15 +4808,63 @@ boolean resurrectSelection() {
47864808 return selectedPos >= 0 ;
47874809 }
47884810
4811+ void confirmCheckedPositionsById () {
4812+ // Clear out the positional check states, we'll rebuild it below from IDs.
4813+ mCheckStates .clear ();
4814+
4815+ boolean checkedCountChanged = false ;
4816+ for (int checkedIndex = 0 ; checkedIndex < mCheckedIdStates .size (); checkedIndex ++) {
4817+ final long id = mCheckedIdStates .keyAt (checkedIndex );
4818+ final int lastPos = mCheckedIdStates .valueAt (checkedIndex );
4819+
4820+ final long lastPosId = mAdapter .getItemId (lastPos );
4821+ if (id != lastPosId ) {
4822+ // Look around to see if the ID is nearby. If not, uncheck it.
4823+ final int start = Math .max (0 , lastPos - CHECK_POSITION_SEARCH_DISTANCE );
4824+ final int end = Math .min (lastPos + CHECK_POSITION_SEARCH_DISTANCE , mItemCount );
4825+ boolean found = false ;
4826+ for (int searchPos = start ; searchPos < end ; searchPos ++) {
4827+ final long searchId = mAdapter .getItemId (searchPos );
4828+ if (id == searchId ) {
4829+ found = true ;
4830+ mCheckStates .put (searchPos , true );
4831+ mCheckedIdStates .setValueAt (checkedIndex , searchPos );
4832+ break ;
4833+ }
4834+ }
4835+
4836+ if (!found ) {
4837+ mCheckedIdStates .delete (id );
4838+ checkedIndex --;
4839+ mCheckedItemCount --;
4840+ checkedCountChanged = true ;
4841+ if (mChoiceActionMode != null && mMultiChoiceModeCallback != null ) {
4842+ mMultiChoiceModeCallback .onItemCheckedStateChanged (mChoiceActionMode ,
4843+ lastPos , id , false );
4844+ }
4845+ }
4846+ } else {
4847+ mCheckStates .put (lastPos , true );
4848+ }
4849+ }
4850+
4851+ if (checkedCountChanged && mChoiceActionMode != null ) {
4852+ mChoiceActionMode .invalidate ();
4853+ }
4854+ }
4855+
47894856 @ Override
47904857 protected void handleDataChanged () {
47914858 int count = mItemCount ;
47924859 int lastHandledItemCount = mLastHandledItemCount ;
47934860 mLastHandledItemCount = mItemCount ;
4794- if (count > 0 ) {
47954861
4796- int newPos ;
4862+ if (mChoiceMode != CHOICE_MODE_NONE && mAdapter != null && mAdapter .hasStableIds ()) {
4863+ confirmCheckedPositionsById ();
4864+ }
47974865
4866+ if (count > 0 ) {
4867+ int newPos ;
47984868 int selectablePos ;
47994869
48004870 // Find the row we are supposed to sync to
0 commit comments