Skip to content

Commit e0f2515

Browse files
adampAndroid (Google) Code Review
authored andcommitted
Merge "Fix bug 5399568 - ListView check states inconsistent after data set change"
2 parents 41c95f3 + 14c0804 commit e0f2515

File tree

1 file changed

+86
-16
lines changed

1 file changed

+86
-16
lines changed

core/java/android/widget/AbsListView.java

Lines changed: 86 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)