package com.mcxiaoke.view; import android.content.Context; import android.util.AttributeSet; 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; /** * The ABSListView is a compatibility class, to getIcon the ListView working together * with the ActionbarSherlock compatibility package. * It offers support for multiple selection with the context action bar of ActionBarSherlock. * The batch contextual actions in ListView (http://developer.android.com/guide/topics/ui/menus.html#CABforListView) * will be enabled with the use of this class, but have a slightly different syntax. * <p/> * You must use ListView.CHOICE_MODE_MULTIPLE as choice mode (not CHOICE_MODE_MULTIPLE_MODAL), * and use the ABSListView.MultiChoiceModeListener interface, instead of the native * android interface. Everything else should nearly work like in the native android * implementation. * <p/> * The ABSListView must be used inside of a SherlockActivity, otherwise it will * throw an error. * <p/> * TODO: We expect the items of the listview to be Checkable. That makes in our case * (and most cases I can think of) sense. If the ListView items do not implement * Checkable, they will not anyhow be highlighted, by selecting them. * Anyhow it is valid for the items not implement Checkable. For that cases * we must save the checked state in an own list, and not casting the views * to Checkable without further tests. * * @author Tim Roes <mail@timroes.de> */ public class ABSListView extends ListView { private ActionMode actionmode; private MultiChoiceModeListenerWrapper multiChoiceListener; private OnItemLongClickListenerWrapper longClickWrapper = new OnItemLongClickListenerWrapper(); private OnItemClickListenerWrapper clickWrapper = new OnItemClickListenerWrapper(); private SherlockActivity sherlockActivity; private SherlockFragmentActivity sherlockFragmentActivity; /** * Get the SherlockActivity, the ListView is used in. * * @return The parent SherlockActivity. */ private ActionMode startActionMode(MultiChoiceModeListener listener) { Context ctx = getContext(); if (SherlockActivity.class.isAssignableFrom(ctx.getClass())) { return ((SherlockActivity) ctx).startActionMode(multiChoiceListener); } else if (SherlockFragmentActivity.class.isAssignableFrom(ctx.getClass())) { return ((SherlockFragmentActivity) ctx).startActionMode(multiChoiceListener); } else { throw new Error("The view is not inside a SherlockActivity."); } } public ABSListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } public ABSListView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public ABSListView(Context context) { super(context); init(); } /** * Initialize the ListView. This will register the click and long click * wrapper as listener at the super class. So they can handle the click * and long click events, and eventually forward them to another register. */ private void init() { super.setOnItemLongClickListener(longClickWrapper); super.setOnItemClickListener(clickWrapper); } /** * The MultiChoiceModeListener must be implemented, to handle the selection * of multiple elements in this ListView. */ public interface MultiChoiceModeListener extends ActionMode.Callback { /** * Called when an item is checked or unchecked during selection mode. * * @param mode The 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 Whether the item is now checked. */ public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked); } /** * Register a callback to be invoked when an item in the list has been clicked. * The listener won't be notified when the list is in the multiple selection * mode. * * @param listener The listener to be notified. */ @Override public void setOnItemClickListener(OnItemClickListener listener) { clickWrapper.listener = listener; } /** * Register a callback to be invoked when an item in the list has been long * clicked. The listener won't be notified when the list is in the multiple * selection mode. * * @param listener The listener to be notified. */ @Override public void setOnItemLongClickListener(OnItemLongClickListener listener) { longClickWrapper.listener = listener; } /** * Returns the number of items currently selected. This will only be valid * if the choice mode is not CHOICE_MODE_NONE (default). * * @return The number of items currently selected. */ @Override public int getCheckedItemCount() { int checked = 0; for (int i = 0; i < getCheckedItemPositions().size(); i++) { if (getCheckedItemPositions().valueAt(i)) { checked++; } } return checked; } /** * DO NOT USE THIS METHOD. Use the setMultiChoiceModeListener(ABSListView.MultiChoiceModeListener) * method instead. It is replacing this method for compatibility reasons. * * @param listener DO NOT USE */ @Override public final void setMultiChoiceModeListener(android.widget.AbsListView.MultiChoiceModeListener listener) { throw new UnsupportedOperationException("Use the alternative implementation."); } /** * Set a MultiChoiceModeListener, that will manage the lifecycle of the selection * ActionMode. This will be only used, when the choice mode is set to * CHOICE_MODE_MULTIPLE. * * @param listener Listener that will manage the lifecycle. */ public void setMultiChoiceModeListener(MultiChoiceModeListener listener) { MultiChoiceModeListenerWrapper wrapper = new MultiChoiceModeListenerWrapper(listener); this.multiChoiceListener = wrapper; // Don't call the super function for this, since it doesn't exist before API 11 // We will try to imitate its behaviour. } /** * This wrapper handles the item clicks and will eventually forward them * to a registered OnItemClickListener. */ private class OnItemClickListenerWrapper implements OnItemClickListener { private OnItemClickListener listener; /** * The method will be called, if an item has been clicked. * * @param parent The parent view of the clicked item. * @param view The view, that has been clicked. * @param position The position in the list, that has been clicked. * @param id The id of the clicked item. */ public void onItemClick(AdapterView<?> parent, View view, int position, long id) { // If an actionmode has been set, just mark the item and inform the // listener about a changed item state. if (actionmode != null) { ABSListView.this.setItemChecked(position, !((Checkable) view).isChecked()); multiChoiceListener.onItemCheckedStateChanged(actionmode, position, id, ((Checkable) view).isChecked()); return; } // If a OnItemClickListener has been registered notify it. if (listener != null) listener.onItemClick(parent, view, position, id); } } /** * This wrapper handles the long clicks on items and will eventuellay forward * them to a registered OnItemLongClickListener. */ private class OnItemLongClickListenerWrapper implements OnItemLongClickListener { private OnItemLongClickListener listener; /** * This method will be called when an item has been long clicked. * * @param parent The parent view of the clicked item. * @param view The long clicked item. * @param position The position in the list, that has been clicked. * @param id The id of the clicked item. * @return Whether the click has been handled by the wrapper. */ public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { // If choice mode is none, just notify the original listener. // TODO: CHOICE_MODE_SINGLE, how is it handled in android native? if (getChoiceMode() == CHOICE_MODE_NONE || getChoiceMode() == CHOICE_MODE_SINGLE) { if (listener != null) { return listener.onItemLongClick(parent, view, position, id); } else { return false; } } else if (getChoiceMode() == CHOICE_MODE_MULTIPLE && multiChoiceListener != null) { // Start actionmode on the parent SherlockActivity if (actionmode == null) { actionmode = ABSListView.this.startActionMode(multiChoiceListener); } // Check the item and inform the MultiChoiceModeListener ABSListView.this.setItemChecked(position, !((Checkable) view).isChecked()); multiChoiceListener.onItemCheckedStateChanged(actionmode, position, id, ((Checkable) view).isChecked()); return true; } return false; } } /** * This wrapper handles the multiple selection mode calls. */ private class MultiChoiceModeListenerWrapper implements MultiChoiceModeListener { private MultiChoiceModeListener listener; /** * Create a new wrapper with a MultiChoiceModeListener. * * @param listener The listener to use. */ public MultiChoiceModeListenerWrapper(MultiChoiceModeListener listener) { this.listener = listener; } /** * This method will be called whenever an item has been checked or unchecked. * This will inform the listener and finish the ActionMode, if the last * item has been unchecked. * * @param mode The 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 Whether the item is now checked. */ public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { listener.onItemCheckedStateChanged(mode, position, id, checked); if (ABSListView.this.getCheckedItemCount() == 0) { mode.finish(); } } /** * This method will be called when the action mode is about to be created. * This will just forward to the listener, that should fill the menu. * * @param mode The ActionMode created. * @param menu The menu of the ActionMode. * @return Whether the menu has been changed. */ public boolean onCreateActionMode(ActionMode mode, Menu menu) { return listener.onCreateActionMode(mode, menu); } /** * Called to refresh an action mode's action menu whenever it is * invalidated. * * @param mode The current ActionMode. * @param menu The menu to be refreshed. * @return Whether the menu has been changed. */ public boolean onPrepareActionMode(ActionMode mode, Menu menu) { return listener.onPrepareActionMode(mode, menu); } /** * Called to report a user click on an action button. * * @param mode The current ActionMode. * @param item The item that was clicked. * @return Whether the callback handled the event. */ public boolean onActionItemClicked(ActionMode mode, MenuItem item) { return listener.onActionItemClicked(mode, item); } /** * Called when the ActionMode is about to be exited and destroyed. * * @param mode The current ActionMode being destroyed. */ public void onDestroyActionMode(ActionMode mode) { listener.onDestroyActionMode(mode); actionmode = null; ABSListView.this.clearChoices(); ABSListView.this.requestLayout(); } } }