diff options
| author | Adam Powell <adamp@google.com> | 2010-08-13 18:27:04 -0700 |
|---|---|---|
| committer | Adam Powell <adamp@google.com> | 2010-08-16 11:05:57 -0700 |
| commit | f343e1ba7e796120eefa917dbf78348feea98e11 (patch) | |
| tree | f69d0281bcf2f8c2b3ab0ae864b64ea552f1d5a9 /core | |
| parent | fed762b944efb690b10ca2705f02343dda0aab03 (diff) | |
| download | frameworks_base-f343e1ba7e796120eefa917dbf78348feea98e11.zip frameworks_base-f343e1ba7e796120eefa917dbf78348feea98e11.tar.gz frameworks_base-f343e1ba7e796120eefa917dbf78348feea98e11.tar.bz2 | |
Add selection mode support to GridView.
Changed 9.xml to make update-api behave with the API change involved.
Several fields and methods moved from ListView to AbsListView. This
should not break source or binary compatibility.
Change-Id: Icad6e238522b8b7cc5bbc935ad89ac3e015572c1
Diffstat (limited to 'core')
| -rw-r--r-- | core/java/android/widget/AbsListView.java | 473 | ||||
| -rw-r--r-- | core/java/android/widget/GridView.java | 31 | ||||
| -rw-r--r-- | core/java/android/widget/ListView.java | 504 | ||||
| -rwxr-xr-x | core/res/res/values/attrs.xml | 34 |
4 files changed, 524 insertions, 518 deletions
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index e572d3d..c155dda 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -34,10 +34,16 @@ import android.text.TextUtils; import android.text.TextWatcher; import android.util.AttributeSet; import android.util.Log; +import android.util.LongSparseArray; +import android.util.SparseBooleanArray; +import android.view.ActionMode; +import android.view.ContextMenu.ContextMenuInfo; import android.view.Gravity; import android.view.HapticFeedbackConstants; import android.view.KeyEvent; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; @@ -45,7 +51,6 @@ import android.view.ViewConfiguration; import android.view.ViewDebug; import android.view.ViewGroup; import android.view.ViewTreeObserver; -import android.view.ContextMenu.ContextMenuInfo; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; @@ -69,6 +74,7 @@ import java.util.List; * @attr ref android.R.styleable#AbsListView_cacheColorHint * @attr ref android.R.styleable#AbsListView_fastScrollEnabled * @attr ref android.R.styleable#AbsListView_smoothScrollbar + * @attr ref android.R.styleable#AbsListView_choiceMode */ public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher, ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener, @@ -167,6 +173,57 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te static final int LAYOUT_MOVE_SELECTION = 6; /** + * Normal list that does not indicate choices + */ + public static final int CHOICE_MODE_NONE = 0; + + /** + * The list allows up to one choice + */ + public static final int CHOICE_MODE_SINGLE = 1; + + /** + * The list allows multiple choices + */ + public static final int CHOICE_MODE_MULTIPLE = 2; + + /** + * The list allows multiple choices in a modal selection mode + */ + public static final int CHOICE_MODE_MULTIPLE_MODAL = 3; + + /** + * Controls if/how the user may choose/check items in the list + */ + int mChoiceMode = CHOICE_MODE_NONE; + + /** + * Controls CHOICE_MODE_MULTIPLE_MODAL. null when inactive. + */ + ActionMode mChoiceActionMode; + + /** + * Wrapper for the multiple choice mode callback; AbsListView needs to perform + * a few extra actions around what application code does. + */ + MultiChoiceModeWrapper mMultiChoiceModeCallback; + + /** + * Running count of how many items are currently checked + */ + int mCheckedItemCount; + + /** + * Running state of which positions are currently checked + */ + SparseBooleanArray mCheckStates; + + /** + * Running state of which IDs are currently checked + */ + LongSparseArray<Boolean> mCheckedIdStates; + + /** * Controls how the next layout will happen */ int mLayoutMode = LAYOUT_NORMAL; @@ -577,6 +634,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te }); } + setChoiceMode(a.getInt(R.styleable.AbsListView_choiceMode, CHOICE_MODE_NONE)); + a.recycle(); } @@ -596,6 +655,311 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } /** + * {@inheritDoc} + */ + @Override + public void setAdapter(ListAdapter adapter) { + if (adapter != null) { + if (mChoiceMode != CHOICE_MODE_NONE && mAdapter.hasStableIds() && + mCheckedIdStates == null) { + mCheckedIdStates = new LongSparseArray<Boolean>(); + } + } + + if (mCheckStates != null) { + mCheckStates.clear(); + } + + if (mCheckedIdStates != null) { + mCheckedIdStates.clear(); + } + } + + /** + * Returns the number of items currently selected. This will only be valid + * if the choice mode is not {@link #CHOICE_MODE_NONE} (default). + * + * <p>To determine the specific items that are currently selected, use one of + * the <code>getChecked*</code> methods. + * + * @return The number of items currently selected + * + * @see #getCheckedItemPosition() + * @see #getCheckedItemPositions() + * @see #getCheckedItemIds() + */ + public int getCheckedItemCount() { + return mCheckedItemCount; + } + + /** + * Returns the checked state of the specified position. The result is only + * valid if the choice mode has been set to {@link #CHOICE_MODE_SINGLE} + * or {@link #CHOICE_MODE_MULTIPLE}. + * + * @param position The item whose checked state to return + * @return The item's checked state or <code>false</code> if choice mode + * is invalid + * + * @see #setChoiceMode(int) + */ + public boolean isItemChecked(int position) { + if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) { + return mCheckStates.get(position); + } + + return false; + } + + /** + * Returns the currently checked item. The result is only valid if the choice + * mode has been set to {@link #CHOICE_MODE_SINGLE}. + * + * @return The position of the currently checked item or + * {@link #INVALID_POSITION} if nothing is selected + * + * @see #setChoiceMode(int) + */ + public int getCheckedItemPosition() { + if (mChoiceMode == CHOICE_MODE_SINGLE && mCheckStates != null && mCheckStates.size() == 1) { + return mCheckStates.keyAt(0); + } + + return INVALID_POSITION; + } + + /** + * Returns the set of checked items in the list. The result is only valid if + * the choice mode has not been set to {@link #CHOICE_MODE_NONE}. + * + * @return A SparseBooleanArray which will return true for each call to + * get(int position) where position is a position in the list, + * or <code>null</code> if the choice mode is set to + * {@link #CHOICE_MODE_NONE}. + */ + public SparseBooleanArray getCheckedItemPositions() { + if (mChoiceMode != CHOICE_MODE_NONE) { + return mCheckStates; + } + return null; + } + + /** + * Returns the set of checked items ids. The result is only valid if the + * choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter + * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true}) + * + * @return A new array which contains the id of each checked item in the + * list. + */ + public long[] getCheckedItemIds() { + if (mChoiceMode == CHOICE_MODE_NONE || mCheckedIdStates == null || mAdapter == null) { + return new long[0]; + } + + final LongSparseArray<Boolean> idStates = mCheckedIdStates; + 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; + } + + /** + * Clear any choices previously set + */ + public void clearChoices() { + if (mCheckStates != null) { + mCheckStates.clear(); + } + if (mCheckedIdStates != null) { + mCheckedIdStates.clear(); + } + mCheckedItemCount = 0; + } + + /** + * Sets the checked state of the specified position. The is only valid if + * the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or + * {@link #CHOICE_MODE_MULTIPLE}. + * + * @param position The item whose checked state is to be checked + * @param value The new checked state for the item + */ + 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), Boolean.TRUE); + } 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), Boolean.TRUE); + } + 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(); + } + } + + @Override + public boolean performItemClick(View view, int position, long id) { + boolean handled = false; + + 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), Boolean.TRUE); + } else { + mCheckedIdStates.delete(mAdapter.getItemId(position)); + } + } + if (newValue) { + mCheckedItemCount++; + } else { + mCheckedItemCount--; + } + if (mChoiceActionMode != null) { + mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode, + position, id, newValue); + } + } 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), Boolean.TRUE); + } + mCheckedItemCount = 1; + } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) { + mCheckedItemCount = 0; + } + } + + mDataChanged = true; + rememberSyncState(); + requestLayout(); + } + + handled |= super.performItemClick(view, position, id); + + return handled; + } + + /** + * @see #setChoiceMode(int) + * + * @return The current choice mode + */ + public int getChoiceMode() { + return mChoiceMode; + } + + /** + * Defines the choice behavior for the List. By default, Lists do not have any choice behavior + * ({@link #CHOICE_MODE_NONE}). By setting the choiceMode to {@link #CHOICE_MODE_SINGLE}, the + * List allows up to one item to be in a chosen state. By setting the choiceMode to + * {@link #CHOICE_MODE_MULTIPLE}, the list allows any number of items to be chosen. + * + * @param choiceMode One of {@link #CHOICE_MODE_NONE}, {@link #CHOICE_MODE_SINGLE}, or + * {@link #CHOICE_MODE_MULTIPLE} + */ + public void setChoiceMode(int choiceMode) { + mChoiceMode = choiceMode; + if (mChoiceActionMode != null) { + mChoiceActionMode.finish(); + mChoiceActionMode = null; + } + if (mChoiceMode != CHOICE_MODE_NONE) { + if (mCheckStates == null) { + mCheckStates = new SparseBooleanArray(); + } + if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) { + mCheckedIdStates = new LongSparseArray<Boolean>(); + } + // Modal multi-choice mode only has choices when the mode is active. Clear them. + if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) { + clearChoices(); + setLongClickable(true); + } + } + } + + /** + * Set a {@link MultiChoiceModeListener} that will manage the lifecycle of the + * selection {@link ActionMode}. Only used when the choice mode is set to + * {@link #CHOICE_MODE_MULTIPLE_MODAL}. + * + * @param listener Listener that will manage the selection mode + * + * @see #setChoiceMode(int) + */ + public void setMultiChoiceModeListener(MultiChoiceModeListener listener) { + if (mMultiChoiceModeCallback == null) { + mMultiChoiceModeCallback = new MultiChoiceModeWrapper(); + } + mMultiChoiceModeCallback.setWrapped(listener); + } + + /** * Enables fast scrolling by letting the user quickly scroll through lists by * dragging the fast scroll thumb. The adapter attached to the list may want * to implement {@link SectionIndexer} if it wishes to display alphabet preview and @@ -813,6 +1177,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te int position; int height; String filter; + SparseBooleanArray checkState; + LongSparseArray<Boolean> checkIdState; /** * Constructor called from {@link AbsListView#onSaveInstanceState()} @@ -832,6 +1198,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te position = in.readInt(); height = in.readInt(); filter = in.readString(); + checkState = in.readSparseBooleanArray(); + long[] idState = in.createLongArray(); + + if (idState.length > 0) { + checkIdState = new LongSparseArray<Boolean>(); + checkIdState.setValues(idState, Boolean.TRUE); + } } @Override @@ -843,6 +1216,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te out.writeInt(position); out.writeInt(height); out.writeString(filter); + out.writeSparseBooleanArray(checkState); + out.writeLongArray(checkIdState != null ? checkIdState.getKeys() : new long[0]); } @Override @@ -854,7 +1229,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te + " viewTop=" + viewTop + " position=" + position + " height=" + height - + " filter=" + filter + "}"; + + " filter=" + filter + + " checkState=" + checkState + "}"; } public static final Parcelable.Creator<SavedState> CREATOR @@ -918,6 +1294,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } } + ss.checkState = mCheckStates; + ss.checkIdState = mCheckedIdStates; + return ss; } @@ -949,6 +1328,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te setFilterText(ss.filter); + if (ss.checkState != null) { + mCheckStates = ss.checkState; + } + + if (ss.checkIdState != null) { + mCheckedIdStates = ss.checkIdState; + } + requestLayout(); } @@ -1774,8 +2161,19 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te boolean performLongPress(final View child, final int longPressPosition, final long longPressId) { - boolean handled = false; + // CHOICE_MODE_MULTIPLE_MODAL takes over long press. + if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) { + if (mChoiceActionMode == null) { + mChoiceActionMode = startActionMode(mMultiChoiceModeCallback); + setItemChecked(longPressPosition, true); + } + // TODO Should we select the long pressed item if we were already in + // selection mode? (i.e. treat it like an item click?) + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + return true; + } + boolean handled = false; if (mOnItemLongClickListener != null) { handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child, longPressPosition, longPressId); @@ -3991,6 +4389,75 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } /** + * A MultiChoiceModeListener receives events for {@link AbsListView#CHOICE_MODE_MULTIPLE_MODAL}. + * It acts as the {@link ActionMode.Callback} for the selection mode and also receives + * {@link #onItemCheckedStateChanged(ActionMode, int, long, boolean)} events when the user + * selects and deselects list items. + */ + public interface MultiChoiceModeListener extends ActionMode.Callback { + /** + * Called when an item is checked or unchecked during selection mode. + * + * @param mode The {@link ActionMode} providing the selection mode + * @param position Adapter position of the item that was checked or unchecked + * @param id Adapter ID of the item that was checked or unchecked + * @param checked <code>true</code> if the item is now checked, <code>false</code> + * if the item is now unchecked. + */ + public void onItemCheckedStateChanged(ActionMode mode, + int position, long id, boolean checked); + } + + class MultiChoiceModeWrapper implements MultiChoiceModeListener { + private MultiChoiceModeListener mWrapped; + + public void setWrapped(MultiChoiceModeListener wrapped) { + mWrapped = wrapped; + } + + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + if (mWrapped.onCreateActionMode(mode, menu)) { + // Initialize checked graphic state? + setLongClickable(false); + return true; + } + return false; + } + + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return mWrapped.onPrepareActionMode(mode, menu); + } + + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + return mWrapped.onActionItemClicked(mode, item); + } + + public void onDestroyActionMode(ActionMode mode) { + mWrapped.onDestroyActionMode(mode); + mChoiceActionMode = null; + + // Ending selection mode means deselecting everything. + clearChoices(); + + mDataChanged = true; + rememberSyncState(); + requestLayout(); + + setLongClickable(true); + } + + public void onItemCheckedStateChanged(ActionMode mode, + int position, long id, boolean checked) { + mWrapped.onItemCheckedStateChanged(mode, position, id, checked); + + // If there are no items selected we no longer need the selection mode. + if (getCheckedItemCount() == 0) { + mode.finish(); + } + } + } + + /** * AbsListView extends LayoutParams to provide a place to hold the view type. */ public static class LayoutParams extends ViewGroup.LayoutParams { diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java index a5b3ed5..ea767f6 100644 --- a/core/java/android/widget/GridView.java +++ b/core/java/android/widget/GridView.java @@ -104,6 +104,26 @@ public class GridView extends AbsListView { a.recycle(); } + /** + * Set how the user may select items from the grid. + * + * <p>GridView only supports {@link AbsListView#CHOICE_MODE_NONE} and + * {@link AbsListView#CHOICE_MODE_MULTIPLE_MODAL}. Attempting to set an unsupported choice + * mode will throw an UnsupportedOperationException. + */ + @Override + public void setChoiceMode(int choiceMode) { + switch (choiceMode) { + case CHOICE_MODE_NONE: + case CHOICE_MODE_MULTIPLE_MODAL: + super.setChoiceMode(choiceMode); + break; + + default: + throw new UnsupportedOperationException("Unsupported choice mode " + choiceMode); + } + } + @Override public ListAdapter getAdapter() { return mAdapter; @@ -136,7 +156,10 @@ public class GridView extends AbsListView { mOldSelectedPosition = INVALID_POSITION; mOldSelectedRowId = INVALID_ROW_ID; - + + // AbsListView#setAdapter will update choice mode states. + super.setAdapter(adapter); + if (mAdapter != null) { mOldItemCount = mItemCount; mItemCount = mAdapter.getCount(); @@ -1312,6 +1335,12 @@ public class GridView extends AbsListView { child.setPressed(isPressed); } + if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) { + if (child instanceof Checkable) { + ((Checkable) child).setChecked(mCheckStates.get(position)); + } + } + if (needToMeasure) { int childHeightSpec = ViewGroup.getChildMeasureSpec( MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.height); diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index 0d0a1ba..de7157b 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -28,17 +28,10 @@ import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; -import android.os.Parcel; -import android.os.Parcelable; import android.util.AttributeSet; -import android.util.LongSparseArray; import android.util.SparseBooleanArray; -import android.view.ActionMode; import android.view.FocusFinder; -import android.view.HapticFeedbackConstants; import android.view.KeyEvent; -import android.view.Menu; -import android.view.MenuItem; import android.view.MotionEvent; import android.view.SoundEffectConstants; import android.view.View; @@ -67,7 +60,6 @@ import java.util.ArrayList; * @attr ref android.R.styleable#ListView_entries * @attr ref android.R.styleable#ListView_divider * @attr ref android.R.styleable#ListView_dividerHeight - * @attr ref android.R.styleable#ListView_choiceMode * @attr ref android.R.styleable#ListView_headerDividersEnabled * @attr ref android.R.styleable#ListView_footerDividersEnabled */ @@ -79,26 +71,6 @@ public class ListView extends AbsListView { static final int NO_POSITION = -1; /** - * Normal list that does not indicate choices - */ - public static final int CHOICE_MODE_NONE = 0; - - /** - * The list allows up to one choice - */ - public static final int CHOICE_MODE_SINGLE = 1; - - /** - * The list allows multiple choices - */ - public static final int CHOICE_MODE_MULTIPLE = 2; - - /** - * The list allows multiple choices in a modal selection mode - */ - public static final int CHOICE_MODE_MULTIPLE_MODAL = 3; - - /** * When arrow scrolling, ListView will never scroll more than this factor * times the height of the list. */ @@ -141,11 +113,6 @@ public class ListView extends AbsListView { private boolean mItemsCanFocus = false; - private int mChoiceMode = CHOICE_MODE_NONE; - - private SparseBooleanArray mCheckStates; - private LongSparseArray<Boolean> mCheckedIdStates; - // used for temporary calculations. private final Rect mTempRect = new Rect(); private Paint mDividerPaint; @@ -157,11 +124,6 @@ public class ListView extends AbsListView { // Keeps focused children visible through resizes private FocusSelector mFocusSelector; - // Controls CHOICE_MODE_MULTIPLE_MODAL. null when inactive. - private ActionMode mChoiceActionMode; - private MultiChoiceModeWrapper mMultiChoiceModeCallback; - private int mCheckedItemCount; - public ListView(Context context) { this(context, null); } @@ -196,8 +158,6 @@ public class ListView extends AbsListView { setDividerHeight(dividerHeight); } - setChoiceMode(a.getInt(R.styleable.ListView_choiceMode, CHOICE_MODE_NONE)); - mHeaderDividersEnabled = a.getBoolean(R.styleable.ListView_headerDividersEnabled, true); mFooterDividersEnabled = a.getBoolean(R.styleable.ListView_footerDividersEnabled, true); @@ -457,6 +417,10 @@ public class ListView extends AbsListView { mOldSelectedPosition = INVALID_POSITION; mOldSelectedRowId = INVALID_ROW_ID; + + // AbsListView#setAdapter will update choice mode states. + super.setAdapter(adapter); + if (mAdapter != null) { mAreAllItemsSelectable = mAdapter.areAllItemsEnabled(); mOldItemCount = mItemCount; @@ -481,13 +445,6 @@ public class ListView extends AbsListView { // Nothing selected checkSelectionChanged(); } - - if (mChoiceMode != CHOICE_MODE_NONE && - mAdapter.hasStableIds() && - mCheckedIdStates == null) { - mCheckedIdStates = new LongSparseArray<Boolean>(); - } - } else { mAreAllItemsSelectable = true; checkFocus(); @@ -495,14 +452,6 @@ public class ListView extends AbsListView { checkSelectionChanged(); } - if (mCheckStates != null) { - mCheckStates.clear(); - } - - if (mCheckedIdStates != null) { - mCheckedIdStates.clear(); - } - requestLayout(); } @@ -3360,270 +3309,6 @@ public class ListView extends AbsListView { } /** - * @see #setChoiceMode(int) - * - * @return The current choice mode - */ - public int getChoiceMode() { - return mChoiceMode; - } - - /** - * Defines the choice behavior for the List. By default, Lists do not have any choice behavior - * ({@link #CHOICE_MODE_NONE}). By setting the choiceMode to {@link #CHOICE_MODE_SINGLE}, the - * List allows up to one item to be in a chosen state. By setting the choiceMode to - * {@link #CHOICE_MODE_MULTIPLE}, the list allows any number of items to be chosen. - * - * @param choiceMode One of {@link #CHOICE_MODE_NONE}, {@link #CHOICE_MODE_SINGLE}, or - * {@link #CHOICE_MODE_MULTIPLE} - */ - public void setChoiceMode(int choiceMode) { - mChoiceMode = choiceMode; - if (mChoiceActionMode != null) { - mChoiceActionMode.finish(); - mChoiceActionMode = null; - } - if (mChoiceMode != CHOICE_MODE_NONE) { - if (mCheckStates == null) { - mCheckStates = new SparseBooleanArray(); - } - if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) { - mCheckedIdStates = new LongSparseArray<Boolean>(); - } - // Modal multi-choice mode only has choices when the mode is active. Clear them. - if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) { - clearChoices(); - setLongClickable(true); - } - } - } - - /** - * Set a {@link MultiChoiceModeListener} that will manage the lifecycle of the - * selection {@link ActionMode}. Only used when the choice mode is set to - * {@link #CHOICE_MODE_MULTIPLE_MODAL}. - * - * @param listener Listener that will manage the selection mode - * - * @see #setChoiceMode(int) - */ - public void setMultiChoiceModeListener(MultiChoiceModeListener listener) { - if (mMultiChoiceModeCallback == null) { - mMultiChoiceModeCallback = new MultiChoiceModeWrapper(); - } - mMultiChoiceModeCallback.setWrapped(listener); - } - - @Override - boolean performLongPress(final View child, - final int longPressPosition, final long longPressId) { - boolean handled = false; - if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) { - handled = true; - if (mChoiceActionMode == null) { - mChoiceActionMode = startActionMode(mMultiChoiceModeCallback); - setItemChecked(longPressPosition, true); - } - // TODO Should we select the long pressed item if we were already in - // selection mode? (i.e. treat it like an item click?) - performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - } - return handled | super.performLongPress(child, longPressPosition, longPressId); - } - - @Override - public boolean performItemClick(View view, int position, long id) { - boolean handled = false; - - 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), Boolean.TRUE); - } else { - mCheckedIdStates.delete(mAdapter.getItemId(position)); - } - } - if (newValue) { - mCheckedItemCount++; - } else { - mCheckedItemCount--; - } - if (mChoiceActionMode != null) { - mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode, - position, id, newValue); - } - } 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), Boolean.TRUE); - } - mCheckedItemCount = 1; - } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) { - mCheckedItemCount = 0; - } - } - - mDataChanged = true; - rememberSyncState(); - requestLayout(); - } - - handled |= super.performItemClick(view, position, id); - - return handled; - } - - /** - * Sets the checked state of the specified position. The is only valid if - * the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or - * {@link #CHOICE_MODE_MULTIPLE}. - * - * @param position The item whose checked state is to be checked - * @param value The new checked state for the item - */ - 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), Boolean.TRUE); - } 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), Boolean.TRUE); - } - 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(); - } - } - - /** - * Returns the number of items currently selected. This will only be valid - * if the choice mode is not {@link #CHOICE_MODE_NONE} (default). - * - * <p>To determine the specific items that are currently selected, use one of - * the <code>getChecked*</code> methods. - * - * @return The number of items currently selected - * - * @see #getCheckedItemPosition() - * @see #getCheckedItemPositions() - * @see #getCheckedItemIds() - */ - public int getCheckedItemCount() { - return mCheckedItemCount; - } - - /** - * Returns the checked state of the specified position. The result is only - * valid if the choice mode has been set to {@link #CHOICE_MODE_SINGLE} - * or {@link #CHOICE_MODE_MULTIPLE}. - * - * @param position The item whose checked state to return - * @return The item's checked state or <code>false</code> if choice mode - * is invalid - * - * @see #setChoiceMode(int) - */ - public boolean isItemChecked(int position) { - if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) { - return mCheckStates.get(position); - } - - return false; - } - - /** - * Returns the currently checked item. The result is only valid if the choice - * mode has been set to {@link #CHOICE_MODE_SINGLE}. - * - * @return The position of the currently checked item or - * {@link #INVALID_POSITION} if nothing is selected - * - * @see #setChoiceMode(int) - */ - public int getCheckedItemPosition() { - if (mChoiceMode == CHOICE_MODE_SINGLE && mCheckStates != null && mCheckStates.size() == 1) { - return mCheckStates.keyAt(0); - } - - return INVALID_POSITION; - } - - /** - * Returns the set of checked items in the list. The result is only valid if - * the choice mode has not been set to {@link #CHOICE_MODE_NONE}. - * - * @return A SparseBooleanArray which will return true for each call to - * get(int position) where position is a position in the list, - * or <code>null</code> if the choice mode is set to - * {@link #CHOICE_MODE_NONE}. - */ - public SparseBooleanArray getCheckedItemPositions() { - if (mChoiceMode != CHOICE_MODE_NONE) { - return mCheckStates; - } - return null; - } - - /** * Returns the set of checked items ids. The result is only valid if the * choice mode has not been set to {@link #CHOICE_MODE_NONE}. * @@ -3667,185 +3352,4 @@ public class ListView extends AbsListView { } return new long[0]; } - - /** - * Returns the set of checked items ids. The result is only valid if the - * choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter - * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true}) - * - * @return A new array which contains the id of each checked item in the - * list. - */ - public long[] getCheckedItemIds() { - if (mChoiceMode == CHOICE_MODE_NONE || mCheckedIdStates == null || mAdapter == null) { - return new long[0]; - } - - final LongSparseArray<Boolean> idStates = mCheckedIdStates; - 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; - } - - /** - * Clear any choices previously set - */ - public void clearChoices() { - if (mCheckStates != null) { - mCheckStates.clear(); - } - if (mCheckedIdStates != null) { - mCheckedIdStates.clear(); - } - mCheckedItemCount = 0; - } - - /** - * A MultiChoiceModeListener receives events for {@link ListView#CHOICE_MODE_MULTIPLE_MODAL}. - * It acts as the {@link ActionMode.Callback} for the selection mode and also receives - * {@link #onItemCheckedStateChanged(ActionMode, int, long, boolean)} events when the user - * selects and deselects list items. - */ - public interface MultiChoiceModeListener extends ActionMode.Callback { - /** - * Called when an item is checked or unchecked during selection mode. - * - * @param mode The {@link ActionMode} providing the selection mode - * @param position Adapter position of the item that was checked or unchecked - * @param id Adapter ID of the item that was checked or unchecked - * @param checked <code>true</code> if the item is now checked, <code>false</code> - * if the item is now unchecked. - */ - public void onItemCheckedStateChanged(ActionMode mode, - int position, long id, boolean checked); - } - - private class MultiChoiceModeWrapper implements MultiChoiceModeListener { - private MultiChoiceModeListener mWrapped; - - public void setWrapped(MultiChoiceModeListener wrapped) { - mWrapped = wrapped; - } - - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - if (mWrapped.onCreateActionMode(mode, menu)) { - // Initialize checked graphic state? - setLongClickable(false); - return true; - } - return false; - } - - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - return mWrapped.onPrepareActionMode(mode, menu); - } - - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - return mWrapped.onActionItemClicked(mode, item); - } - - public void onDestroyActionMode(ActionMode mode) { - mWrapped.onDestroyActionMode(mode); - mChoiceActionMode = null; - - // Ending selection mode means deselecting everything. - clearChoices(); - - mDataChanged = true; - rememberSyncState(); - requestLayout(); - - setLongClickable(true); - } - - public void onItemCheckedStateChanged(ActionMode mode, - int position, long id, boolean checked) { - mWrapped.onItemCheckedStateChanged(mode, position, id, checked); - - // If there are no items selected we no longer need the selection mode. - if (getCheckedItemCount() == 0) { - mode.finish(); - } - } - } - - static class SavedState extends BaseSavedState { - SparseBooleanArray checkState; - LongSparseArray<Boolean> checkIdState; - - /** - * Constructor called from {@link ListView#onSaveInstanceState()} - */ - SavedState(Parcelable superState, SparseBooleanArray checkState, - LongSparseArray<Boolean> checkIdState) { - super(superState); - this.checkState = checkState; - this.checkIdState = checkIdState; - } - - /** - * Constructor called from {@link #CREATOR} - */ - private SavedState(Parcel in) { - super(in); - checkState = in.readSparseBooleanArray(); - long[] idState = in.createLongArray(); - - if (idState.length > 0) { - checkIdState = new LongSparseArray<Boolean>(); - checkIdState.setValues(idState, Boolean.TRUE); - } - } - - @Override - public void writeToParcel(Parcel out, int flags) { - super.writeToParcel(out, flags); - out.writeSparseBooleanArray(checkState); - out.writeLongArray(checkIdState != null ? checkIdState.getKeys() : new long[0]); - } - - @Override - public String toString() { - return "ListView.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]; - } - }; - } - - @Override - public Parcelable onSaveInstanceState() { - Parcelable superState = super.onSaveInstanceState(); - return new SavedState(superState, mCheckStates, mCheckedIdStates); - } - - @Override - public void onRestoreInstanceState(Parcelable state) { - SavedState ss = (SavedState) state; - - super.onRestoreInstanceState(ss.getSuperState()); - - if (ss.checkState != null) { - mCheckStates = ss.checkState; - } - - if (ss.checkIdState != null) { - mCheckedIdStates = ss.checkIdState; - } - } } diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index e0608f9..67327b2 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -1594,6 +1594,26 @@ <attr name="smoothScrollbar" format="boolean" /> <!-- A reference to an XML description of the adapter to attach to the list. --> <attr name="adapter" format="reference" /> + <!-- Defines the choice behavior for the view. By default, lists do not have + any choice behavior. By setting the choiceMode to singleChoice, the list + allows up to one item to be in a chosen state. By setting the choiceMode to + multipleChoice, the list allows any number of items to be chosen. + Finally, by setting the choiceMode to multipleChoiceModal the list allows + any number of items to be chosen in a special selection mode. + The application will supply a + {@link android.widget.AbsListView.MultiChoiceModeListener} using + {@link android.widget.AbsListView#setMultiChoiceModeListener} to control the + selection mode. This uses the {@link android.view.ActionMode} API. --> + <attr name="choiceMode"> + <!-- Normal list that does not indicate choices. --> + <enum name="none" value="0" /> + <!-- The list allows up to one choice. --> + <enum name="singleChoice" value="1" /> + <!-- The list allows multiple choices. --> + <enum name="multipleChoice" value="2" /> + <!-- The list allows multiple choices in a custom selection mode. --> + <enum name="multipleChoiceModal" value="3" /> + </attr> </declare-styleable> <declare-styleable name="AbsSpinner"> <!-- Reference to an array resource that will populate the Spinner. For static content, @@ -1800,20 +1820,6 @@ <!-- Height of the divider. Will use the intrinsic height of the divider if this is not specified. --> <attr name="dividerHeight" format="dimension" /> - <!-- Defines the choice behavior for the ListView. By default, lists do not have - any choice behavior. By setting the choiceMode to singleChoice, the List - allows up to one item to be in a chosen state. By setting the choiceMode to - multipleChoice, the list allows any number of items to be chosen. --> - <attr name="choiceMode"> - <!-- Normal list that does not indicate choices. --> - <enum name="none" value="0" /> - <!-- The list allows up to one choice. --> - <enum name="singleChoice" value="1" /> - <!-- The list allows multiple choices. --> - <enum name="multipleChoice" value="2" /> - <!-- The list allows multiple choices in a custom selection mode. --> - <enum name="multipleChoiceModal" value="3" /> - </attr> <!-- When set to false, the ListView will not draw the divider after each header view. The default value is true. --> <attr name="headerDividersEnabled" format="boolean" /> |
