@@ -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 ) {
@@ -1411,7 +1420,7 @@ static class SavedState extends BaseSavedState {
14111420 boolean inActionMode ;
14121421 int checkedItemCount ;
14131422 SparseBooleanArray checkState ;
1414- LongSparseArray <Boolean > checkIdState ;
1423+ LongSparseArray <Integer > checkIdState ;
14151424
14161425 /**
14171426 * Constructor called from {@link AbsListView#onSaveInstanceState()}
@@ -1435,10 +1444,14 @@ private SavedState(Parcel in) {
14351444 checkedItemCount = in .readInt ();
14361445 checkState = in .readSparseBooleanArray ();
14371446 long [] idState = in .createLongArray ();
1447+ int [] idPositions = in .createIntArray ();
14381448
1439- if (idState .length > 0 ) {
1440- checkIdState = new LongSparseArray <Boolean >();
1441- checkIdState .setValues (idState , Boolean .TRUE );
1449+ final int idLength = idState .length ;
1450+ if (idLength > 0 ) {
1451+ checkIdState = new LongSparseArray <Integer >();
1452+ for (int i = 0 ; i < idLength ; i ++) {
1453+ checkIdState .put (idState [i ], idPositions [i ]);
1454+ }
14421455 }
14431456 }
14441457
@@ -1455,6 +1468,15 @@ public void writeToParcel(Parcel out, int flags) {
14551468 out .writeInt (checkedItemCount );
14561469 out .writeSparseBooleanArray (checkState );
14571470 out .writeLongArray (checkIdState != null ? checkIdState .getKeys () : new long [0 ]);
1471+
1472+ int size = checkIdState != null ? checkIdState .size () : 0 ;
1473+ int [] idPositions = new int [size ];
1474+ if (size > 0 ) {
1475+ for (int i = 0 ; i < size ; i ++) {
1476+ idPositions [i ] = checkIdState .valueAt (i );
1477+ }
1478+ out .writeIntArray (idPositions );
1479+ }
14581480 }
14591481
14601482 @ Override
@@ -1549,7 +1571,7 @@ public Parcelable onSaveInstanceState() {
15491571 ss .checkState = mCheckStates .clone ();
15501572 }
15511573 if (mCheckedIdStates != null ) {
1552- final LongSparseArray <Boolean > idState = new LongSparseArray <Boolean >();
1574+ final LongSparseArray <Integer > idState = new LongSparseArray <Integer >();
15531575 final int count = mCheckedIdStates .size ();
15541576 for (int i = 0 ; i < count ; i ++) {
15551577 idState .put (mCheckedIdStates .keyAt (i ), mCheckedIdStates .valueAt (i ));
@@ -4770,15 +4792,63 @@ boolean resurrectSelection() {
47704792 return selectedPos >= 0 ;
47714793 }
47724794
4795+ void confirmCheckedPositionsById () {
4796+ // Clear out the positional check states, we'll rebuild it below from IDs.
4797+ mCheckStates .clear ();
4798+
4799+ boolean checkedCountChanged = false ;
4800+ for (int checkedIndex = 0 ; checkedIndex < mCheckedIdStates .size (); checkedIndex ++) {
4801+ final long id = mCheckedIdStates .keyAt (checkedIndex );
4802+ final int lastPos = mCheckedIdStates .valueAt (checkedIndex );
4803+
4804+ final long lastPosId = mAdapter .getItemId (lastPos );
4805+ if (id != lastPosId ) {
4806+ // Look around to see if the ID is nearby. If not, uncheck it.
4807+ final int start = Math .max (0 , lastPos - CHECK_POSITION_SEARCH_DISTANCE );
4808+ final int end = Math .min (lastPos + CHECK_POSITION_SEARCH_DISTANCE , mItemCount );
4809+ boolean found = false ;
4810+ for (int searchPos = start ; searchPos < end ; searchPos ++) {
4811+ final long searchId = mAdapter .getItemId (searchPos );
4812+ if (id == searchId ) {
4813+ found = true ;
4814+ mCheckStates .put (searchPos , true );
4815+ mCheckedIdStates .setValueAt (checkedIndex , searchPos );
4816+ break ;
4817+ }
4818+ }
4819+
4820+ if (!found ) {
4821+ mCheckedIdStates .delete (id );
4822+ checkedIndex --;
4823+ mCheckedItemCount --;
4824+ checkedCountChanged = true ;
4825+ if (mChoiceActionMode != null && mMultiChoiceModeCallback != null ) {
4826+ mMultiChoiceModeCallback .onItemCheckedStateChanged (mChoiceActionMode ,
4827+ lastPos , id , false );
4828+ }
4829+ }
4830+ } else {
4831+ mCheckStates .put (lastPos , true );
4832+ }
4833+ }
4834+
4835+ if (checkedCountChanged && mChoiceActionMode != null ) {
4836+ mChoiceActionMode .invalidate ();
4837+ }
4838+ }
4839+
47734840 @ Override
47744841 protected void handleDataChanged () {
47754842 int count = mItemCount ;
47764843 int lastHandledItemCount = mLastHandledItemCount ;
47774844 mLastHandledItemCount = mItemCount ;
4778- if (count > 0 ) {
47794845
4780- int newPos ;
4846+ if (mChoiceMode != CHOICE_MODE_NONE && mAdapter != null && mAdapter .hasStableIds ()) {
4847+ confirmCheckedPositionsById ();
4848+ }
47814849
4850+ if (count > 0 ) {
4851+ int newPos ;
47824852 int selectablePos ;
47834853
47844854 // Find the row we are supposed to sync to
0 commit comments