package com.rubika.aotalk; import android.annotation.TargetApi; import android.content.Context; import android.util.AttributeSet; import android.util.SparseBooleanArray; import android.view.View; import android.widget.AdapterView; import android.widget.Checkable; import android.widget.ListView; import com.actionbarsherlock.app.SherlockActivity; import com.actionbarsherlock.app.SherlockFragmentActivity; import com.actionbarsherlock.view.ActionMode; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuItem; /** * Provides backwards compatible multiple choice ActionMode support on Froyo+ * using ActionBarSherlock. */ public class SherlockListView extends ListView { // API 11+ reference, but ok because the value will be inlined. public static final int CHOICE_MODE_MULTIPLE_MODAL_COMPAT = CHOICE_MODE_MULTIPLE_MODAL; /** * Wrapper to intercept delegation of long click events, and pass to * {@link #doLongPress} */ class OnItemLongClickListenerWrapper implements OnItemLongClickListener { private OnItemLongClickListener wrapped; public void setWrapped(OnItemLongClickListener listener) { this.wrapped = listener; } @Override public boolean onItemLongClick(AdapterView<?> view, View child, int position, long id) { // this would be easier if AbsListView.performLongPress wasn't package // protected :-( boolean handled = doLongPress(child, position, id); if (!handled && wrapped != null) { return wrapped.onItemLongClick(view, child, position, id); } return true; } } /** * Hijack the onLongClickListener so we can intercept delegation. */ @Override public void setOnItemLongClickListener(OnItemLongClickListener listener) { if (longClickListenerWrapper == null) { longClickListenerWrapper = new OnItemLongClickListenerWrapper(); } longClickListenerWrapper.setWrapped(listener); super.setOnItemLongClickListener(longClickListenerWrapper); } /** * 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. */ @SuppressWarnings("javadoc") public interface MultiChoiceModeListenerCompat 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 MultiChoiceModeListenerCompat { private MultiChoiceModeListenerCompat wrapped; public void setWrapped(MultiChoiceModeListenerCompat wrapped) { this.wrapped = wrapped; } @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { if (wrapped == null) { return false; } if (wrapped.onCreateActionMode(mode, menu)) { // Initialize checked graphic state? setLongClickable(false); return true; } return false; } @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { if (wrapped == null) { return false; } return wrapped.onPrepareActionMode(mode, menu); } @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { if (wrapped == null) { return false; } return wrapped.onActionItemClicked(mode, item); } @Override public void onDestroyActionMode(ActionMode mode) { if (wrapped == null) { return; } wrapped.onDestroyActionMode(mode); actionMode = null; // Ending selection mode means deselecting everything. clearChoices(); checkedItemCount = 0; updateOnScreenCheckedViews(); invalidateViews(); setLongClickable(true); requestLayout(); invalidate(); } @Override public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { if (wrapped == null) { return; } wrapped.onItemCheckedStateChanged(mode, position, id, checked); // If there are no items selected we no longer need the selection mode. if (checkedItemCount == 0) { mode.finish(); } } } private com.actionbarsherlock.view.ActionMode actionMode; private OnItemLongClickListenerWrapper longClickListenerWrapper; private MultiChoiceModeWrapper choiceModeListener; private int choiceMode; private int checkedItemCount; public SherlockListView(Context context) { super(context); init(context); } public SherlockListView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public SherlockListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } void init(Context context) { if (!(context instanceof SherlockActivity || context instanceof SherlockFragmentActivity)) { throw new IllegalStateException( "This view must be hosted in a SherlockActivity or SherlockFragmentActivity"); } setOnItemLongClickListener(null); } @Override public void setChoiceMode(int mode) { choiceMode = mode; if (actionMode != null) { actionMode.finish(); actionMode = null; } if (choiceMode != CHOICE_MODE_NONE) { if (mode == CHOICE_MODE_MULTIPLE_MODAL_COMPAT) { clearChoices(); checkedItemCount = 0; setLongClickable(true); updateOnScreenCheckedViews(); requestLayout(); invalidate(); mode = CHOICE_MODE_MULTIPLE; } super.setChoiceMode(mode); } } @Override public int getChoiceMode() { return choiceMode; } public void setMultiChoiceModeListener(MultiChoiceModeListenerCompat listener) { if (choiceModeListener == null) { choiceModeListener = new MultiChoiceModeWrapper(); } choiceModeListener.setWrapped(listener); } @Override public boolean performItemClick(View view, int position, long id) { boolean handled = false; boolean dispatchItemClick = true; boolean checkStateChanged = false; if (choiceMode != CHOICE_MODE_NONE) { handled = true; if (choiceMode == CHOICE_MODE_MULTIPLE || (choiceMode == CHOICE_MODE_MULTIPLE_MODAL_COMPAT && actionMode != null)) { boolean newValue = !getCheckedItemPositions().get(position); setItemChecked(position, newValue); if (actionMode != null) { choiceModeListener.onItemCheckedStateChanged(actionMode, position, id, newValue); dispatchItemClick = false; } checkStateChanged = true; return false; } else if (choiceMode == CHOICE_MODE_SINGLE) { boolean newValue = !getCheckedItemPositions().get(position); setItemChecked(position, newValue); checkStateChanged = true; } if (checkStateChanged) { updateOnScreenCheckedViews(); } } if (dispatchItemClick) { handled |= super.performItemClick(view, position, id); } return handled; } /** * Perform a quick, in-place update of the checked or activated state on all * visible item views. This should only be called when a valid choice mode is * active. * <p> * (Taken verbatim from AbsListView.java) */ @TargetApi(11) private void updateOnScreenCheckedViews() { final int firstPos = getFirstVisiblePosition(); final int count = getChildCount(); final boolean useActivated = getContext().getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.HONEYCOMB; for (int i = 0; i < count; i++) { final View child = getChildAt(i); final int position = firstPos + i; if (child instanceof Checkable) { ((Checkable) child).setChecked(getCheckedItemPositions().get(position)); } else if (useActivated) { child.setActivated(getCheckedItemPositions().get(position)); } } } public ActionMode startActionMode(ActionMode.Callback callback) { if (actionMode != null) { return actionMode; } Context context = getContext(); if (context instanceof SherlockActivity) { actionMode = ((SherlockActivity) getContext()).startActionMode(callback); } else if (context instanceof SherlockFragmentActivity) { actionMode = ((SherlockFragmentActivity) context).startActionMode(callback); } else { throw new IllegalStateException( "This view must be hosted in a SherlockActivity or SherlockFragmentActivity"); } return actionMode; } boolean doLongPress(final View child, final int longPressPosition, final long longPressId) { if (choiceMode == CHOICE_MODE_MULTIPLE_MODAL_COMPAT) { if (actionMode == null && (actionMode = startActionMode(choiceModeListener)) != null) { setItemChecked(longPressPosition, true); } return true; } return false; } /** * 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 */ @Override public void setItemChecked(int position, boolean value) { if (choiceMode == CHOICE_MODE_NONE) { return; } SparseBooleanArray checkStates = getCheckedItemPositions(); // Start selection mode if needed. We don't need to if we're unchecking // something. if (value && choiceMode == CHOICE_MODE_MULTIPLE_MODAL_COMPAT && actionMode == null) { actionMode = startActionMode(choiceModeListener); } if (choiceMode == CHOICE_MODE_MULTIPLE || choiceMode == CHOICE_MODE_MULTIPLE_MODAL) { //boolean oldValue = checkStates.get(position); checkStates.put(position, value); if (value) { checkedItemCount++; } else { checkedItemCount--; } if (actionMode != null) { final long id = getAdapter().getItemId(position); choiceModeListener.onItemCheckedStateChanged(actionMode, position, id, value); } } else { if (value || isItemChecked(position)) { checkStates.clear(); } // this may end up selecting the value we just cleared but this way // we ensure length of checkStates is 1, a fact getCheckedItemPosition // relies on if (value) { checkStates.put(position, true); } } requestLayout(); invalidate(); } }