/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* The fragments of code has been used from AbsListView.java of Android SDK
*
* @author Paramvir Bali
* @mail paramvir@rokoder.com
*/
package com.rokoder.android.lib.support.v4.widget;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.View;
import android.widget.AbsListView;
import android.widget.GridView;
import android.widget.ListAdapter;
import android.widget.ListView;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
/**
* This class is based on the GridView. We need to create this class as multiselection comes in API
* 10 into GridView. So lot of code is copied from the Android Source Code. APIs which we are
* implementing here are suffixed with 'C' to avoid recursion which when you try to call the API
* using reflection. So we made the signature different.
*
* <pre>
* Ref code:
* https://github.com/android/platform_frameworks_base/blob/master/core/java/android/widget/GridView.java
* </pre>
*/
public class GridViewCompat extends GridView {
private static final String TAG = GridViewCompat.class.getSimpleName();
/**
* Running count of how many items are currently checked
*/
int mCheckedItemCountC;
/**
* Running state of which positions are currently checked
*/
SparseBooleanArray mCheckStatesC;
/**
* Running state of which IDs are currently checked. If there is a value for a given key, the
* checked state for that ID is true and the value holds the last known position in the adapter
* for that id.
*/
LongSparseArray<Integer> mCheckedIdStatesC;
/**
* Controls if/how the user may choose/check items in the list
*/
int mChoiceModeC = ListView.CHOICE_MODE_NONE;
/**
* Variables for backward compatibility
*/
private static boolean inCompatibleMode = false;
private static Method gridView_getCheckedItemIds;
private static Method gridView_isItemChecked;
private static Method gridView_getCheckedItemPosition;
@SuppressWarnings("unused")
private static Method gridView_getCheckedItemPositions;
@SuppressWarnings("unused")
private static Method gridView_clearChoices;
@SuppressWarnings("unused")
private static Method gridView_setItemChecked;
@SuppressWarnings("unused")
private static Method gridView_setChoiceMode;
private static Method gridView_getChoiceMode;
@SuppressWarnings("unused")
private static Method gridView_getCheckedItemCount;
static {
try {
inCompatibleMode = false;
gridView_getChoiceMode = GridView.class.getMethod("getChoiceMode", (Class<?>[]) null);
gridView_getCheckedItemIds =
GridView.class.getMethod("getCheckedItemIds", (Class<?>[]) null);
gridView_isItemChecked =
GridView.class.getMethod("isItemChecked", new Class[] {
int.class
});
gridView_getCheckedItemPosition =
GridView.class.getMethod("getCheckedItemPosition", (Class<?>[]) null);
gridView_getCheckedItemPositions =
GridView.class.getMethod("getCheckedItemPositions", (Class<?>[]) null);
gridView_clearChoices = GridView.class.getMethod("clearChoices", (Class<?>[]) null);
gridView_setItemChecked =
GridView.class.getMethod("setItemChecked", new Class[] {
int.class,
boolean.class
});
gridView_setChoiceMode =
GridView.class.getMethod("setChoiceMode", new Class[] {
int.class
});
gridView_getCheckedItemCount =
GridView.class.getMethod("getCheckedItemCount", (Class<?>[]) null);
} catch (NoSuchMethodException e) {
Log.d(TAG, "Running in compatibility mode as '" + e.getMessage() + "' not found");
// If any of the method is missing, we are in compatibility mode
inCompatibleMode = true;
gridView_getCheckedItemIds = null;
gridView_isItemChecked = null;
gridView_getCheckedItemPosition = null;
gridView_getCheckedItemPositions = null;
gridView_clearChoices = null;
gridView_setItemChecked = null;
gridView_setChoiceMode = null;
gridView_getChoiceMode = null;
gridView_getCheckedItemCount = null;
}
}
/**
* SparseArrays map longs to Objects. Unlike a normal array of Objects, there can be gaps in the
* indices. It is intended to be more efficient than using a HashMap to map Longs to Objects.
*
* <pre>
* Source : https://github.com/android/platform_frameworks_base/blob/master/core/java/android/util/LongSparseArray.java
* </pre>
*
* @hide
*/
private static class LongSparseArray<E> {
private static final Object DELETED = new Object();
private boolean mGarbage = false;
/**
* Creates a new SparseArray containing no mappings.
*/
public LongSparseArray() {
this(10);
}
/**
* Creates a new SparseArray containing no mappings that will not require any additional
* memory allocation to store the specified number of mappings.
*/
public LongSparseArray(int initialCapacity) {
initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity);
mKeys = new long[initialCapacity];
mValues = new Object[initialCapacity];
mSize = 0;
}
/**
* @return A copy of all keys contained in the sparse array.
*/
@SuppressWarnings("unused")
public long[] getKeys() {
int length = mKeys.length;
long[] result = new long[length];
System.arraycopy(mKeys, 0, result, 0, length);
return result;
}
/**
* Sets all supplied keys to the given unique value.
*
* @param keys Keys to set
* @param uniqueValue Value to set all supplied keys to
*/
@SuppressWarnings("unused")
public void setValues(long[] keys, E uniqueValue) {
int length = keys.length;
for (int i = 0; i < length; i++) {
put(keys[i], uniqueValue);
}
}
/**
* Gets the Object mapped from the specified key, or <code>null</code> if no such mapping
* has been made.
*/
@SuppressWarnings("unused")
public E get(long key) {
return get(key, null);
}
/**
* Gets the Object mapped from the specified key, or the specified Object if no such mapping
* has been made.
*/
@SuppressWarnings("unchecked")
public E get(long key, E valueIfKeyNotFound) {
int i = binarySearch(mKeys, 0, mSize, key);
if (i < 0 || mValues[i] == DELETED) {
return valueIfKeyNotFound;
} else {
return (E) mValues[i];
}
}
/**
* Removes the mapping from the specified key, if there was any.
*/
public void delete(long key) {
int i = binarySearch(mKeys, 0, mSize, key);
if (i >= 0) {
if (mValues[i] != DELETED) {
mValues[i] = DELETED;
mGarbage = true;
}
}
}
/**
* Alias for {@link #delete(long)}.
*/
@SuppressWarnings("unused")
public void remove(long key) {
delete(key);
}
private void gc() {
// Log.e("SparseArray", "gc start with " + mSize);
int n = mSize;
int o = 0;
long[] keys = mKeys;
Object[] values = mValues;
for (int i = 0; i < n; i++) {
Object val = values[i];
if (val != DELETED) {
if (i != o) {
keys[o] = keys[i];
values[o] = val;
}
o++;
}
}
mGarbage = false;
mSize = o;
// Log.e("SparseArray", "gc end with " + mSize);
}
/**
* Adds a mapping from the specified key to the specified value, replacing the previous
* mapping from the specified key if there was one.
*/
public void put(long key, E value) {
int i = binarySearch(mKeys, 0, mSize, key);
if (i >= 0) {
mValues[i] = value;
} else {
i = ~i;
if (i < mSize && mValues[i] == DELETED) {
mKeys[i] = key;
mValues[i] = value;
return;
}
if (mGarbage && mSize >= mKeys.length) {
gc();
// Search again because indices may have changed.
i = ~binarySearch(mKeys, 0, mSize, key);
}
if (mSize >= mKeys.length) {
int n = ArrayUtils.idealIntArraySize(mSize + 1);
long[] nkeys = new long[n];
Object[] nvalues = new Object[n];
// Log.e("SparseArray", "grow " + mKeys.length + " to " +
// n);
System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
mKeys = nkeys;
mValues = nvalues;
}
if (mSize - i != 0) {
// Log.e("SparseArray", "move " + (mSize - i));
System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
}
mKeys[i] = key;
mValues[i] = value;
mSize++;
}
}
/**
* Returns the number of key-value mappings that this SparseArray currently stores.
*/
public int size() {
if (mGarbage) {
gc();
}
return mSize;
}
/**
* Given an index in the range <code>0...size()-1</code>, returns the key from the
* <code>index</code>th key-value mapping that this SparseArray stores.
*/
public long keyAt(int index) {
if (mGarbage) {
gc();
}
return mKeys[index];
}
/**
* Given an index in the range <code>0...size()-1</code>, returns the value from the
* <code>index</code>th key-value mapping that this SparseArray stores.
*/
@SuppressWarnings("unchecked")
public E valueAt(int index) {
if (mGarbage) {
gc();
}
return (E) mValues[index];
}
/**
* Given an index in the range <code>0...size()-1</code>, sets a new value for the
* <code>index</code>th key-value mapping that this SparseArray stores.
*/
@SuppressWarnings("unused")
public void setValueAt(int index, E value) {
if (mGarbage) {
gc();
}
mValues[index] = value;
}
/**
* Returns the index for which {@link #keyAt} would return the specified key, or a negative
* number if the specified key is not mapped.
*/
@SuppressWarnings("unused")
public int indexOfKey(long key) {
if (mGarbage) {
gc();
}
return binarySearch(mKeys, 0, mSize, key);
}
/**
* Returns an index for which {@link #valueAt} would return the specified key, or a negative
* number if no keys map to the specified value. Beware that this is a linear search, unlike
* lookups by key, and that multiple keys can map to the same value and this will find only
* one of them.
*/
@SuppressWarnings("unused")
public int indexOfValue(E value) {
if (mGarbage) {
gc();
}
for (int i = 0; i < mSize; i++)
if (mValues[i] == value)
return i;
return -1;
}
/**
* Removes all key-value mappings from this SparseArray.
*/
public void clear() {
int n = mSize;
Object[] values = mValues;
for (int i = 0; i < n; i++) {
values[i] = null;
}
mSize = 0;
mGarbage = false;
}
/**
* Puts a key/value pair into the array, optimizing for the case where the key is greater
* than all existing keys in the array.
*/
@SuppressWarnings("unused")
public void append(long key, E value) {
if (mSize != 0 && key <= mKeys[mSize - 1]) {
put(key, value);
return;
}
if (mGarbage && mSize >= mKeys.length) {
gc();
}
int pos = mSize;
if (pos >= mKeys.length) {
int n = ArrayUtils.idealIntArraySize(pos + 1);
long[] nkeys = new long[n];
Object[] nvalues = new Object[n];
// Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
mKeys = nkeys;
mValues = nvalues;
}
mKeys[pos] = key;
mValues[pos] = value;
mSize = pos + 1;
}
private static int binarySearch(long[] a, int start, int len, long key) {
int high = start + len, low = start - 1, guess;
while (high - low > 1) {
guess = (high + low) / 2;
if (a[guess] < key)
low = guess;
else
high = guess;
}
if (high == start + len)
return ~(start + len);
else if (a[high] == key)
return high;
else
return ~high;
}
@SuppressWarnings("unused")
private void checkIntegrity() {
for (int i = 1; i < mSize; i++) {
if (mKeys[i] <= mKeys[i - 1]) {
for (int j = 0; j < mSize; j++) {
Log.e("FAIL", j + ": " + mKeys[j] + " -> " + mValues[j]);
}
throw new RuntimeException();
}
}
}
private long[] mKeys;
private Object[] mValues;
private int mSize;
}
// XXX these should be changed to reflect the actual memory allocator we
// use.
// it looks like right now objects want to be powers of 2 minus 8
// and the array size eats another 4 bytes
/**
* ArrayUtils contains some methods that you can call to find out the most efficient increments
* by which to grow arrays. *
*
* <pre>
* Source : https://github.com/android/platform_frameworks_base/blob/master/core/java/com/android/internal/util/ArrayUtils.java
* </pre>
*/
private static class ArrayUtils {
private static Object[] EMPTY = new Object[0];
private static final int CACHE_SIZE = 73;
private static Object[] sCache = new Object[CACHE_SIZE];
private ArrayUtils() { /* cannot be instantiated */
}
public static int idealByteArraySize(int need) {
for (int i = 4; i < 32; i++)
if (need <= (1 << i) - 12)
return (1 << i) - 12;
return need;
}
@SuppressWarnings("unused")
public static int idealBooleanArraySize(int need) {
return idealByteArraySize(need);
}
@SuppressWarnings("unused")
public static int idealShortArraySize(int need) {
return idealByteArraySize(need * 2) / 2;
}
@SuppressWarnings("unused")
public static int idealCharArraySize(int need) {
return idealByteArraySize(need * 2) / 2;
}
public static int idealIntArraySize(int need) {
return idealByteArraySize(need * 4) / 4;
}
@SuppressWarnings("unused")
public static int idealFloatArraySize(int need) {
return idealByteArraySize(need * 4) / 4;
}
@SuppressWarnings("unused")
public static int idealObjectArraySize(int need) {
return idealByteArraySize(need * 4) / 4;
}
@SuppressWarnings("unused")
public static int idealLongArraySize(int need) {
return idealByteArraySize(need * 8) / 8;
}
/**
* Checks if the beginnings of two byte arrays are equal.
*
* @param array1 the first byte array
* @param array2 the second byte array
* @param length the number of bytes to check
* @return true if they're equal, false otherwise
*/
@SuppressWarnings("unused")
public static boolean equals(byte[] array1, byte[] array2, int length) {
if (array1 == array2) {
return true;
}
if (array1 == null || array2 == null || array1.length < length
|| array2.length < length) {
return false;
}
for (int i = 0; i < length; i++) {
if (array1[i] != array2[i]) {
return false;
}
}
return true;
}
/**
* Returns an empty array of the specified type. The intent is that it will return the same
* empty array every time to avoid reallocation, although this is not guaranteed.
*/
@SuppressWarnings({ "unused", "unchecked" })
public static <T> T[] emptyArray(Class<T> kind) {
if (kind == Object.class) {
return (T[]) EMPTY;
}
int bucket = ((System.identityHashCode(kind) / 8) & 0x7FFFFFFF) % CACHE_SIZE;
Object cache = sCache[bucket];
if (cache == null || cache.getClass().getComponentType() != kind) {
cache = Array.newInstance(kind, 0);
sCache[bucket] = cache;
// Log.e("cache", "new empty " + kind.getName() + " at " +
// bucket);
}
return (T[]) cache;
}
/**
* Checks that value is present as at least one of the elements of the array.
*
* @param array the array to check in
* @param value the value to check for
* @return true if the value is present in the array
*/
@SuppressWarnings("unused")
public static <T> boolean contains(T[] array, T value) {
for (T element : array) {
if (element == null) {
if (value == null)
return true;
} else {
if (value != null && element.equals(value))
return true;
}
}
return false;
}
@SuppressWarnings("unused")
public static boolean contains(int[] array, int value) {
for (int element : array) {
if (element == value) {
return true;
}
}
return false;
}
}
/**
* Class to save the state for the compatibility version of GridView
*/
static class SavedState extends BaseSavedState {
int checkedItemCount;
SparseBooleanArray checkState;
LongSparseArray<Integer> checkIdState;
/**
* Constructor called from {@link AbsListView#onSaveInstanceState()}
*/
SavedState(Parcelable superState) {
super(superState);
}
/**
* Constructor called from {@link #CREATOR}
*/
private SavedState(Parcel in) {
super(in);
checkedItemCount = in.readInt();
checkState = in.readSparseBooleanArray();
final int N = in.readInt();
if (N > 0) {
checkIdState = new LongSparseArray<Integer>();
for (int i = 0; i < N; i++) {
final long key = in.readLong();
final int value = in.readInt();
checkIdState.put(key, value);
}
}
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(checkedItemCount);
out.writeSparseBooleanArray(checkState);
final int N = checkIdState != null ? checkIdState.size() : 0;
out.writeInt(N);
for (int i = 0; i < N; i++) {
out.writeLong(checkIdState.keyAt(i));
out.writeInt(checkIdState.valueAt(i));
}
}
@Override
public String toString() {
return "AbsListView.SavedState{" + Integer.toHexString(System.identityHashCode(this))
+ " checkState=" + checkState + "}";
}
public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
public GridViewCompat(Context context, AttributeSet attrs) {
super(context, attrs);
initAttrs(attrs);
}
public GridViewCompat(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initAttrs(attrs);
}
public GridViewCompat(Context context) {
super(context);
}
/**
* This api is not implemented yet but can be implemented if you want to set the multi-selection
* from the xml file not from the code
*/
private void initAttrs(AttributeSet attrs) {
}
/**
* WARN Do not call the default api
*
* @see #setChoiceMode(int)
* @return The current choice mode
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public int getChoiceMode() {
if (!inCompatibleMode && gridView_getChoiceMode != null)
return super.getChoiceMode();
return mChoiceModeC;
}
/*
* (non-Javadoc)
* @see android.widget.GridView#setAdapter(android.widget.ListAdapter)
*/
@Override
public void setAdapter(ListAdapter adapter) {
if (!inCompatibleMode) {
super.setAdapter(adapter);
return;
}
// Code copied from Android source
super.setAdapter(adapter);
if (adapter != null) {
if (mChoiceModeC != ListView.CHOICE_MODE_NONE && getAdapter().hasStableIds()
&& mCheckedIdStatesC == null) {
mCheckedIdStatesC = new LongSparseArray<Integer>();
}
}
if (mCheckStatesC != null) {
mCheckStatesC.clear();
}
if (mCheckedIdStatesC != null) {
mCheckedIdStatesC.clear();
}
}
/**
* WARN Do not call the default api
*
* @return
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public long[] getCheckedItemIds() {
if (!inCompatibleMode && gridView_getCheckedItemIds != null) {
return super.getCheckedItemIds();
}
// Code copied from Android source
if (mChoiceModeC == ListView.CHOICE_MODE_NONE || mCheckedIdStatesC == null
|| getAdapter() == null) {
return new long[0];
}
final LongSparseArray<Integer> idStates = mCheckedIdStatesC;
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;
}
/**
* WARN Do not call the default api
*
* @param position
* @return
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public boolean isItemChecked(int position) {
if (!inCompatibleMode && gridView_isItemChecked != null) {
return super.isItemChecked(position);
}
// Code copied from Android source
if (mChoiceModeC != ListView.CHOICE_MODE_NONE && mCheckStatesC != null) {
return mCheckStatesC.get(position);
}
return false;
}
/**
* WARN Do not call the default api
*
* @return
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public int getCheckedItemPosition() {
if (!inCompatibleMode && gridView_getCheckedItemPosition != null) {
return super.getCheckedItemPosition();
}
// Code copied from Android source
if (mChoiceModeC == ListView.CHOICE_MODE_SINGLE && mCheckStatesC != null
&& mCheckStatesC.size() == 1) {
return mCheckStatesC.keyAt(0);
}
return INVALID_POSITION;
}
/**
* WARN Do not call the default api
*
* @return
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public SparseBooleanArray getCheckedItemPositions() {
if (!inCompatibleMode) {
return super.getCheckedItemPositions();
}
// Code copied from Android source
if (mChoiceModeC != ListView.CHOICE_MODE_NONE) {
return mCheckStatesC;
}
return null;
}
/**
* WARN Do not call the default api
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void clearChoices() {
if (!inCompatibleMode) {
super.clearChoices();
return;
}
// Code copied from Android source
if (mCheckStatesC != null) {
mCheckStatesC.clear();
}
if (mCheckedIdStatesC != null) {
mCheckedIdStatesC.clear();
}
mCheckedItemCountC = 0;
}
/**
* WARN Do not call the default api
*
* <pre>
*
* public void setItemChecked(int position, boolean value) {
* if (mChoiceMode == CHOICE_MODE_NONE) {
* return;
* }
*
* // Start selection mode if needed. We don't need to if we're unchecking
* // something.
* if (value && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
* mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
* }
*
* if (mChoiceMode == CHOICE_MODE_MULTIPLE || mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
* boolean oldValue = mCheckStates.get(position);
* mCheckStates.put(position, value);
* if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
* if (value) {
* mCheckedIdStates.put(mAdapter.getItemId(position), position);
* } else {
* mCheckedIdStates.delete(mAdapter.getItemId(position));
* }
* }
* if (oldValue != value) {
* if (value) {
* mCheckedItemCount++;
* } else {
* mCheckedItemCount--;
* }
* }
* if (mChoiceActionMode != null) {
* final long id = mAdapter.getItemId(position);
* mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode, position, id,
* value);
* }
* } else {
* boolean updateIds = mCheckedIdStates != null && mAdapter.hasStableIds();
* // Clear all values if we're checking something, or unchecking the
* // currently
* // selected item
* if (value || isItemChecked(position)) {
* mCheckStates.clear();
* if (updateIds) {
* mCheckedIdStates.clear();
* }
* }
* // this may end up selecting the value we just cleared but this way
* // we ensure length of mCheckStates is 1, a fact getCheckedItemPosition
* // relies on
* if (value) {
* mCheckStates.put(position, true);
* if (updateIds) {
* mCheckedIdStates.put(mAdapter.getItemId(position), position);
* }
* mCheckedItemCount = 1;
* } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
* mCheckedItemCount = 0;
* }
* }
*
* // Do not generate a data change while we are in the layout phase
* if (!mInLayout && !mBlockLayoutRequests) {
* mDataChanged = true;
* rememberSyncState();
* requestLayout();
* }
* }
*
* We are using it where we dont have access to private members and we need to update views
* public void invalidateViews() {
* mDataChanged = true;
* rememberSyncState();
* requestLayout();
* invalidate();
* }
* </pre>
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void setItemChecked(int position, boolean value) {
if (!inCompatibleMode) {
super.setItemChecked(position, value);
return;
}
// Code copied from Android source. The code below is slightly
// different.
if (mChoiceModeC == ListView.CHOICE_MODE_NONE) {
return;
}
if (mChoiceModeC == ListView.CHOICE_MODE_MULTIPLE) {
boolean oldValue = mCheckStatesC.get(position);
mCheckStatesC.put(position, value);
if (mCheckedIdStatesC != null && getAdapter().hasStableIds()) {
if (value) {
mCheckedIdStatesC.put(getAdapter().getItemId(position), position);
} else {
mCheckedIdStatesC.delete(getAdapter().getItemId(position));
}
}
if (oldValue != value) {
if (value) {
mCheckedItemCountC++;
} else {
mCheckedItemCountC--;
}
}
} else {
boolean updateIds = mCheckedIdStatesC != null && getAdapter().hasStableIds();
// Clear all values if we're checking something, or unchecking the
// currently
// selected item
if (value || isItemChecked(position)) {
mCheckStatesC.clear();
if (updateIds) {
mCheckedIdStatesC.clear();
}
}
// this may end up selecting the value we just cleared but this way
// we ensure length of mCheckStates is 1, a fact
// getCheckedItemPosition relies on
if (value) {
mCheckStatesC.put(position, true);
if (updateIds) {
mCheckedIdStatesC.put(getAdapter().getItemId(position), position);
}
mCheckedItemCountC = 1;
} else if (mCheckStatesC.size() == 0 || !mCheckStatesC.valueAt(0)) {
mCheckedItemCountC = 0;
}
}
// Since we dont have access to private members this is the closest we
// can get.
invalidateViews();
}
/**
* <pre>
* public boolean performItemClick(View view, int position, long id) {
* boolean handled = false;
* boolean dispatchItemClick = true;
*
* if (mChoiceMode != CHOICE_MODE_NONE) {
* handled = true;
*
* if (mChoiceMode == CHOICE_MODE_MULTIPLE
* || (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null)) {
* boolean newValue = !mCheckStates.get(position, false);
* mCheckStates.put(position, newValue);
* if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
* if (newValue) {
* mCheckedIdStates.put(mAdapter.getItemId(position), position);
* } else {
* mCheckedIdStates.delete(mAdapter.getItemId(position));
* }
* }
* if (newValue) {
* mCheckedItemCount++;
* } else {
* mCheckedItemCount--;
* }
* if (mChoiceActionMode != null) {
* mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode, position, id,
* newValue);
* dispatchItemClick = false;
* }
* } else if (mChoiceMode == CHOICE_MODE_SINGLE) {
* boolean newValue = !mCheckStates.get(position, false);
* if (newValue) {
* mCheckStates.clear();
* mCheckStates.put(position, true);
* if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
* mCheckedIdStates.clear();
* mCheckedIdStates.put(mAdapter.getItemId(position), position);
* }
* mCheckedItemCount = 1;
* } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
* mCheckedItemCount = 0;
* }
* }
*
* mDataChanged = true;
* rememberSyncState();
* requestLayout();
* }
*
* if (dispatchItemClick) {
* handled |= super.performItemClick(view, position, id);
* }
*
* return handled;
* }
*
* We are using it where we dont have access to private members and we need to update views
* public void invalidateViews() {
* mDataChanged = true;
* rememberSyncState();
* requestLayout();
* invalidate();
* }
*
* </pre>
*/
@Override
public boolean performItemClick(View view, int position, long id) {
if (!inCompatibleMode)
return super.performItemClick(view, position, id);
boolean handled = false;
boolean dispatchItemClick = true;
if (mChoiceModeC != ListView.CHOICE_MODE_NONE) {
handled = true;
if (mChoiceModeC == ListView.CHOICE_MODE_MULTIPLE) {
boolean newValue = !mCheckStatesC.get(position, false);
mCheckStatesC.put(position, newValue);
if (mCheckedIdStatesC != null && getAdapter().hasStableIds()) {
if (newValue) {
mCheckedIdStatesC.put(getAdapter().getItemId(position), position);
} else {
mCheckedIdStatesC.delete(getAdapter().getItemId(position));
}
}
if (newValue) {
mCheckedItemCountC++;
} else {
mCheckedItemCountC--;
}
} else if (mChoiceModeC == ListView.CHOICE_MODE_SINGLE) {
boolean newValue = !mCheckStatesC.get(position, false);
if (newValue) {
mCheckStatesC.clear();
mCheckStatesC.put(position, true);
if (mCheckedIdStatesC != null && getAdapter().hasStableIds()) {
mCheckedIdStatesC.clear();
mCheckedIdStatesC.put(getAdapter().getItemId(position), position);
}
mCheckedItemCountC = 1;
} else if (mCheckStatesC.size() == 0 || !mCheckStatesC.valueAt(0)) {
mCheckedItemCountC = 0;
}
}
// Since we dont have access to private members this is the closest
// we can get.
invalidateViews();
}
if (dispatchItemClick) {
handled |= super.performItemClick(view, position, id);
}
return handled;
}
/**
* WARN Do not call the default api
*
* @param choiceMode
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void setChoiceMode(int choiceMode) {
if (!inCompatibleMode) {
super.setChoiceMode(choiceMode);
return;
}
// Code copied from Android source
mChoiceModeC = choiceMode;
if (mChoiceModeC != ListView.CHOICE_MODE_NONE) {
if (mCheckStatesC == null) {
mCheckStatesC = new SparseBooleanArray();
}
if (mCheckedIdStatesC == null && getAdapter() != null && getAdapter().hasStableIds()) {
mCheckedIdStatesC = new LongSparseArray<Integer>();
}
}
}
private SparseBooleanArray makeClone(SparseBooleanArray sba) {
// Code copied from Android source
SparseBooleanArray sbaClone = new SparseBooleanArray();
int sbaLen = sba.size();
for (int i = 0; i < sbaLen; i++) {
int key = sba.keyAt(i);
sbaClone.put(key, sba.get(key));
}
return sbaClone;
}
@Override
public Parcelable onSaveInstanceState() {
if (!inCompatibleMode) {
return super.onSaveInstanceState();
}
// Restoring the state if we are in compatible mode
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
if (mCheckStatesC != null) {
ss.checkState = makeClone(mCheckStatesC);
}
if (mCheckedIdStatesC != null) {
final LongSparseArray<Integer> idState = new LongSparseArray<Integer>();
final int count = mCheckedIdStatesC.size();
for (int i = 0; i < count; i++) {
idState.put(mCheckedIdStatesC.keyAt(i), mCheckedIdStatesC.valueAt(i));
}
ss.checkIdState = idState;
}
ss.checkedItemCount = mCheckedItemCountC;
return ss;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
if (!inCompatibleMode) {
super.onRestoreInstanceState(state);
return;
}
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
if (ss.checkState != null) {
mCheckStatesC = ss.checkState;
}
if (ss.checkIdState != null) {
mCheckedIdStatesC = ss.checkIdState;
}
mCheckedItemCountC = ss.checkedItemCount;
// Since we dont have access to private members this is the closest we
// can get.
invalidateViews();
}
/**
* WARN Do not call the default api
*
* @return
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public int getCheckedItemCount() {
if (!inCompatibleMode) {
return super.getCheckedItemCount();
}
return mCheckedItemCountC;
}
}