Add support for multiselect

This commit is contained in:
Jimmy Shih
2013-04-30 11:11:58 -07:00
parent 6a74a18e99
commit 520ae87f14
9 changed files with 256 additions and 115 deletions
+2 -1
View File
@@ -22,7 +22,8 @@ limitations under the License.
android:paddingRight="12dp"
android:paddingTop="8dp"
android:shrinkColumns="1"
android:stretchColumns="1" >
android:stretchColumns="1"
style="@style/ListItem" >
<TableRow
android:layout_height="wrap_content"
android:layout_width="match_parent" >
+3
View File
@@ -29,6 +29,9 @@ limitations under the License.
<item name="android:orientation">horizontal</item>
<item name="android:measureWithLargestChild">true</item>
</style>
<style name="ListItem">
<item name="android:background">?android:attr/activatedBackgroundIndicator</item>
</style>
<style name="MyTracksActionBar" parent="@android:style/Widget.Holo.ActionBar">
<!-- Same image as the source except using a grey line instead of a blue line -->
<item name="android:background">@drawable/ab_transparent_dark_holo</item>
+15
View File
@@ -852,6 +852,21 @@ limitations under the License.
Imported %1$s from %2$s.
</string>
<!-- List item -->
<string name="list_deselect_all"
translation_description="Option to deselect all the items in a list.">
Deselect all
</string>
<string name="list_item_selected"
translation_description="Title indictating the number of items selected in a list.
%1$d: an integer value. E.g., '2'.">
%1$d selected
</string>
<string name="list_select_all"
translation_description="Option to select all the items in a list.">
Select all
</string>
<!-- Maps List -->
<string name="maps_list_error"
translation_description="When sending a track to an existing Google Map, the text on the Maps'
+2
View File
@@ -101,6 +101,8 @@ limitations under the License.
<item name="android:layout_gravity">center</item>
<item name="android:padding">8dp</item>
</style>
<!-- list item -->
<style name="ListItem" />
<!-- list item date -->
<style name="ListItemDate" parent="@style/TextSmall">
<item name="android:gravity">right</item>
@@ -19,27 +19,27 @@ package com.google.android.apps.mytracks;
import android.view.Menu;
/**
* Callback when an item in the contextual action mode is selected.
* Callback when items in the contextual action mode are selected.
*
* @author Jimmy Shih
*/
public interface ContextualActionModeCallback {
/**
* Invoked when an item is selected.
*
* @param itemId the context menu item id
* @param position the position of the selected row
* @param id the id of the selected row, if available
*/
public boolean onClick(int itemId, int position, long id);
/**
* Invoked to prepare the menu for the selected item.
* Invoked to prepare the menu for the selected items.
*
* @param menu the menu
* @param position the position of the selected item
* @param id the id of the selected item
* @param positions the selected items' positions
* @param ids the selected items' ids, if available
*/
public void onPrepare(Menu menu, int position, long id);
public void onPrepare(Menu menu, int[] positions, long[] ids);
/**
* Invoked when items are selected.
*
* @param itemId the context menu item id
* @param positions the selected items' positions
* @param ids the selected items' ids, if available
*/
public boolean onClick(int itemId, int[] positions, long[] ids);
}
@@ -69,15 +69,17 @@ public class MarkerListActivity extends AbstractMyTracksActivity implements Dele
private ContextualActionModeCallback
contextualActionModeCallback = new ContextualActionModeCallback() {
@Override
public boolean onClick(int itemId, int position, long id) {
return handleContextItem(itemId, id);
public void onPrepare(Menu menu, int[] positions, long[] ids) {
menu.findItem(R.id.list_context_menu_share).setVisible(false);
menu.findItem(R.id.list_context_menu_show_on_map).setVisible(ids.length == 1);
menu.findItem(R.id.list_context_menu_edit)
.setVisible(ids.length == 1 && !track.isSharedWithMe());
menu.findItem(R.id.list_context_menu_delete).setVisible(!track.isSharedWithMe());
}
public void onPrepare(Menu menu, int position, long id) {
menu.findItem(R.id.list_context_menu_share).setVisible(false);
menu.findItem(R.id.list_context_menu_show_on_map).setVisible(true);
menu.findItem(R.id.list_context_menu_edit).setVisible(!track.isSharedWithMe());
menu.findItem(R.id.list_context_menu_delete).setVisible(!track.isSharedWithMe());
@Override
public boolean onClick(int itemId, int[] positions, long[] ids) {
return handleContextItem(itemId, ids);
}
};
@@ -254,12 +256,15 @@ public class MarkerListActivity extends AbstractMyTracksActivity implements Dele
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
getMenuInflater().inflate(R.menu.list_context_menu, menu);
contextualActionModeCallback.onPrepare(menu, 0, 0);
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
contextualActionModeCallback.onPrepare(menu, new int[] {info.position}, new long[] {info.id});
}
@Override
public boolean onContextItemSelected(MenuItem item) {
if (handleContextItem(item.getItemId(), ((AdapterContextMenuInfo) item.getMenuInfo()).id)) {
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
if (handleContextItem(item.getItemId(), new long[] {info.id})) {
return true;
}
return super.onContextItemSelected(item);
@@ -269,24 +274,28 @@ public class MarkerListActivity extends AbstractMyTracksActivity implements Dele
* Handles a context item selection.
*
* @param itemId the menu item id
* @param markerId the marker id
* @param markerIds the marker ids
* @return true if handled.
*/
private boolean handleContextItem(int itemId, long markerId) {
private boolean handleContextItem(int itemId, long[] markerIds) {
Intent intent;
switch (itemId) {
case R.id.list_context_menu_show_on_map:
intent = IntentUtils.newIntent(this, TrackDetailActivity.class)
.putExtra(TrackDetailActivity.EXTRA_MARKER_ID, markerId);
startActivity(intent);
if (markerIds.length == 1) {
intent = IntentUtils.newIntent(this, TrackDetailActivity.class)
.putExtra(TrackDetailActivity.EXTRA_MARKER_ID, markerIds[0]);
startActivity(intent);
}
return true;
case R.id.list_context_menu_edit:
intent = IntentUtils.newIntent(this, MarkerEditActivity.class)
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerId);
startActivity(intent);
if (markerIds.length == 1) {
intent = IntentUtils.newIntent(this, MarkerEditActivity.class)
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerIds[0]);
startActivity(intent);
}
return true;
case R.id.list_context_menu_delete:
DeleteMarkerDialogFragment.newInstance(new long[] { markerId })
DeleteMarkerDialogFragment.newInstance(markerIds)
.show(getSupportFragmentManager(), DeleteMarkerDialogFragment.DELETE_MARKER_DIALOG_TAG);
return true;
default:
@@ -128,21 +128,32 @@ public class SearchListActivity extends AbstractSendToGoogleActivity
private ContextualActionModeCallback
contextualActionModeCallback = new ContextualActionModeCallback() {
@Override
public boolean onClick(int itemId, int position, long id) {
return handleContextItem(itemId, position);
public boolean onClick(int itemId, int[] positions, long[] ids) {
return handleContextItem(itemId, positions);
}
public void onPrepare(Menu menu, int position, long id) {
Map<String, Object> item = arrayAdapter.getItem(position);
Long trackId = (Long) item.get(TRACK_ID_FIELD);
Long markerId = (Long) item.get(MARKER_ID_FIELD);
Track track = myTracksProviderUtils.getTrack(trackId);
@Override
public void onPrepare(Menu menu, int[] positions, long[] ids) {
boolean shareWithMe = true;
Long markerId = null;
if (positions.length == 1) {
Map<String, Object> item = arrayAdapter.getItem(positions[0]);
Long trackId = (Long) item.get(TRACK_ID_FIELD);
Track track = myTracksProviderUtils.getTrack(trackId);
shareWithMe = track.isSharedWithMe();
markerId = (Long) item.get(MARKER_ID_FIELD);
}
// Only one item, the item is a track, and the item is not a sharedWithMe track
menu.findItem(R.id.list_context_menu_share)
.setVisible(markerId == null && !track.isSharedWithMe());
menu.findItem(R.id.list_context_menu_show_on_map).setVisible(markerId != null);
menu.findItem(R.id.list_context_menu_edit).setVisible(!track.isSharedWithMe());
.setVisible(positions.length == 1 && markerId == null && !shareWithMe);
// Only one item, the item is a marker
menu.findItem(R.id.list_context_menu_show_on_map)
.setVisible(positions.length == 1 && markerId != null);
// Only one item, can be track or marker, but cannot be a sharedWithMe item
menu.findItem(R.id.list_context_menu_edit)
.setVisible(positions.length == 1 && !shareWithMe);
// Only one item. If marker, cannot be a shareWithMe item. If track, no restriction
menu.findItem(R.id.list_context_menu_delete)
.setVisible(markerId == null || !track.isSharedWithMe());
.setVisible(positions.length == 1 && (markerId == null || !shareWithMe));
}
};
@@ -288,14 +299,14 @@ public class SearchListActivity extends AbstractSendToGoogleActivity
super.onCreateContextMenu(menu, v, menuInfo);
getMenuInflater().inflate(R.menu.list_context_menu, menu);
int position = ((AdapterContextMenuInfo) menuInfo).position;
contextualActionModeCallback.onPrepare(menu, position, 0);
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
contextualActionModeCallback.onPrepare(menu, new int[] {info.position}, new long[] {info.id});
}
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterContextMenuInfo adapterContextMenuInfo = (AdapterContextMenuInfo) item.getMenuInfo();
if (handleContextItem(item.getItemId(), adapterContextMenuInfo.position)) {
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
if (handleContextItem(item.getItemId(), new int[] {info.position})) {
return true;
}
return super.onContextItemSelected(item);
@@ -315,26 +326,24 @@ public class SearchListActivity extends AbstractSendToGoogleActivity
* Handles a context item selection.
*
* @param itemId the menu item id
* @param position the position of the selected row
* @param positions the positions of the selected rows
* @return true if handled.
*/
private boolean handleContextItem(int itemId, int position) {
Map<String, Object> item = arrayAdapter.getItem(position);
private boolean handleContextItem(int itemId, int[] positions) {
if (positions.length != 1) {
return false;
}
Map<String, Object> item = arrayAdapter.getItem(positions[0]);
Long trackId = (Long) item.get(TRACK_ID_FIELD);
Long markerId = (Long) item.get(MARKER_ID_FIELD);
Intent intent;
switch (itemId) {
case R.id.list_context_menu_share:
confirmShare(trackId);
confirmShare(trackId);
return true;
case R.id.list_context_menu_show_on_map:
if (markerId != null) {
intent = IntentUtils.newIntent(this, TrackDetailActivity.class)
.putExtra(TrackDetailActivity.EXTRA_MARKER_ID, markerId);
} else {
intent = IntentUtils.newIntent(this, TrackDetailActivity.class)
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId);
}
intent = IntentUtils.newIntent(this, TrackDetailActivity.class)
.putExtra(TrackDetailActivity.EXTRA_MARKER_ID, markerId);
startActivity(intent);
return true;
case R.id.list_context_menu_edit:
@@ -346,6 +355,7 @@ public class SearchListActivity extends AbstractSendToGoogleActivity
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId);
}
startActivity(intent);
// Close the search result since its content can change after edit.
finish();
return true;
@@ -194,18 +194,24 @@ public class TrackListActivity extends AbstractSendToGoogleActivity
// Callback when an item is selected in the contextual action mode
private final ContextualActionModeCallback
contextualActionModeCallback = new ContextualActionModeCallback() {
@Override
public boolean onClick(int itemId, int position, long id) {
return handleContextItem(itemId, id);
}
public void onPrepare(Menu menu, int position, long id) {
Track track = myTracksProviderUtils.getTrack(id);
menu.findItem(R.id.list_context_menu_share).setVisible(!track.isSharedWithMe());
@Override
public void onPrepare(Menu menu, int[] positions, long[] ids) {
boolean shareWithMe = true;
if (ids.length == 1) {
Track track = myTracksProviderUtils.getTrack(ids[0]);
shareWithMe = track.isSharedWithMe();
}
menu.findItem(R.id.list_context_menu_share).setVisible(!shareWithMe);
menu.findItem(R.id.list_context_menu_show_on_map).setVisible(false);
menu.findItem(R.id.list_context_menu_edit).setVisible(!track.isSharedWithMe());
menu.findItem(R.id.list_context_menu_edit).setVisible(!shareWithMe);
menu.findItem(R.id.list_context_menu_delete).setVisible(true);
}
@Override
public boolean onClick(int itemId, int[] positions, long[] ids) {
return handleContextItem(itemId, ids);
};
};
private final OnClickListener recordListener = new OnClickListener() {
@@ -631,12 +637,16 @@ public class TrackListActivity extends AbstractSendToGoogleActivity
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
getMenuInflater().inflate(R.menu.list_context_menu, menu);
contextualActionModeCallback.onPrepare(menu, 0, ((AdapterContextMenuInfo) menuInfo).id);
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
contextualActionModeCallback.onPrepare(
menu, new int[] { info.position }, new long[] { info.id });
}
@Override
public boolean onContextItemSelected(MenuItem item) {
if (handleContextItem(item.getItemId(), ((AdapterContextMenuInfo) item.getMenuInfo()).id)) {
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
if (handleContextItem(item.getItemId(), new long[] { info.id })) {
return true;
}
return super.onContextItemSelected(item);
@@ -806,27 +816,22 @@ public class TrackListActivity extends AbstractSendToGoogleActivity
* Handles a context item selection.
*
* @param itemId the menu item id
* @param trackId the track id
* @param trackIds the track ids
* @return true if handled.
*/
private boolean handleContextItem(int itemId, long trackId) {
Intent intent;
private boolean handleContextItem(int itemId, long[] trackIds) {
switch (itemId) {
case R.id.list_context_menu_share:
confirmShare(trackId);
return true;
case R.id.list_context_menu_show_on_map:
intent = IntentUtils.newIntent(this, TrackDetailActivity.class)
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId);
startActivity(intent);
confirmShare(trackIds[0]);
return true;
case R.id.list_context_menu_edit:
intent = IntentUtils.newIntent(this, TrackEditActivity.class)
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId);
Intent intent = IntentUtils.newIntent(this, TrackEditActivity.class)
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackIds[0]);
startActivity(intent);
return true;
case R.id.list_context_menu_delete:
DeleteTrackDialogFragment.newInstance(false, new long[] { trackId })
boolean deleteAll = listView.getCount() == trackIds.length;
DeleteTrackDialogFragment.newInstance(deleteAll, trackIds)
.show(getSupportFragmentManager(), DeleteTrackDialogFragment.DELETE_TRACK_DIALOG_TAG);
return true;
default:
@@ -23,17 +23,22 @@ import android.annotation.TargetApi;
import android.app.Activity;
import android.app.SearchManager;
import android.content.Context;
import android.util.SparseBooleanArray;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.SearchView;
import android.widget.Spinner;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
/**
@@ -55,51 +60,142 @@ public class Api11Adapter extends Api10Adapter {
}
@Override
public void configureListViewContextualMenu(final Activity activity, ListView listView,
public void configureListViewContextualMenu(final Activity activity, final ListView listView,
final ContextualActionModeCallback contextualActionModeCallback) {
listView.setOnItemLongClickListener(new OnItemLongClickListener() {
ActionMode actionMode;
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
listView.setMultiChoiceModeListener(new MultiChoiceModeListener() {
@Override
public boolean onItemLongClick(
AdapterView<?> parent, View view, final int position, final long id) {
if (actionMode != null) {
return false;
}
actionMode = activity.startActionMode(new ActionMode.Callback() {
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
mode.getMenuInflater().inflate(R.menu.list_context_menu, menu);
final Spinner spinner = new Spinner(activity) {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
mode.getMenuInflater().inflate(R.menu.list_context_menu, menu);
return true;
public void setSelection(int position) {
super.setSelection(position);
// Fire event when selecting the same item
if (position == getSelectedItemPosition()) {
getOnItemSelectedListener()
.onItemSelected(this, getSelectedView(), position, getSelectedItemId());
}
}
};
ArrayAdapter<StringBuilder> adapter = new ArrayAdapter<StringBuilder>(
activity, android.R.layout.simple_spinner_item,
new StringBuilder[] { new StringBuilder("") }) {
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
contextualActionModeCallback.onPrepare(menu, position, id);
// Return true to indicate change
return true;
}
public View getDropDownView(int position, View convertView, ViewGroup parent) {
// Display select all/ deselect all
View view = super.getDropDownView(position, convertView, parent);
TextView textView = (TextView) view;
int messageId = listView.getCheckedItemCount() == listView.getCount()
? R.string.list_deselect_all
: R.string.list_select_all;
textView.setText(messageId);
return view;
};
};
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
// Use a post to prevent the spinner item from firing when first created
spinner.post(new Runnable() {
@Override
public void onDestroyActionMode(ActionMode mode) {
actionMode = null;
}
public void run() {
spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
// Handle select all/ deselect all
int size = listView.getCount();
boolean allSelected = listView.getCheckedItemCount() == size;
for (int i = 0; i < size; i++) {
listView.setItemChecked(i, !allSelected);
}
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
mode.finish();
return contextualActionModeCallback.onClick(item.getItemId(), position, id);
@Override
public void onNothingSelected(AdapterView<?> parent) {
// Do nothing
}
});
}
});
TextView textView = (TextView) view.findViewById(R.id.list_item_name);
if (textView != null) {
actionMode.setTitle(textView.getText());
}
view.setSelected(true);
mode.setCustomView(spinner);
setActionModeTitle(mode);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
contextualActionModeCallback.onPrepare(
menu, getCheckedPositions(listView), listView.getCheckedItemIds());
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
// Do nothing
}
@SuppressWarnings("unchecked")
@Override
public void onItemCheckedStateChanged(
ActionMode mode, int position, long id, boolean checked) {
setActionModeTitle(mode);
// Update action mode title
Spinner spinner = (Spinner) mode.getCustomView();
ArrayAdapter<StringBuilder> adapter = (ArrayAdapter<StringBuilder>) spinner.getAdapter();
adapter.notifyDataSetChanged();
// Update contextual action mode items
mode.invalidate();
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
if (contextualActionModeCallback.onClick(
item.getItemId(), getCheckedPositions(listView), listView.getCheckedItemIds())) {
mode.finish();
}
return true;
}
/**
* Sets the action mode title
*
* @param mode action mode
*/
private void setActionModeTitle(ActionMode mode) {
Spinner spinner = (Spinner) mode.getCustomView();
StringBuilder stringBuilder = (StringBuilder) spinner.getSelectedItem();
stringBuilder.delete(0, stringBuilder.length());
int count = listView.getCheckedItemCount();
stringBuilder.append(activity.getString(R.string.list_item_selected, count));
}
/**
* Gets the checked positions in a list view.
*
* @param list the list view
*/
private int[] getCheckedPositions(ListView list) {
SparseBooleanArray positions = list.getCheckedItemPositions();
ArrayList<Integer> arrayList = new ArrayList<Integer>();
for (int i = 0; i < positions.size(); i++) {
int key = positions.keyAt(i);
if (positions.valueAt(i)) {
arrayList.add(key);
}
}
int[] result = new int[arrayList.size()];
for (int i = 0; i < arrayList.size(); i++) {
result[i] = arrayList.get(i);
}
return result;
}
});
};
}
@Override
public void configureSearchWidget(Activity activity, final MenuItem menuItem) {