I want to use contextual menu in ActionBarSherlock which actually doesn't allow to use ListView.CHOICE_MODE_MULTIPLE_MODAL
.
I've created my own implementation for multi-selecting items on list, but the problem is (actually that's a great feature in other cases) that android reuses views in ListView
.
I handle multi-selecting items on list by coloring their background to mark them as selected, moreover I store selection in SparseArray
but it doesn't matter now, because it's working perfectly.
As you can guess, background color is replicated every 10 items on my list starting from item which I've selected.
So what should I do to provide different view for each list item? Or maybe there's another solution for that problem?
I use SimpleCursorAdapter
with ViewBinder
to fill in my list items.
/***************************************
******** ACTION MODES HANDLING ********
***************************************/
@Override
public boolean onItemLongClick(AdapterView<?> parent, View v, int position, long id) {
switchActionMode();
if (isInActionMode) {
checkItem(position, v);
startActionMode();
}
else {
uncolorAndClearAllItems();
actionMode.finish();
}
return true;
}
private void switchActionMode() {
isInActionMode ^= true;
}
private void checkItem(int position, View v) {
boolean isChecked = isItemChecked(position);
setItemChecked(position, v, !isChecked);
colorItem(v, !isChecked);
}
private boolean isItemChecked(int position) {
return checkedItems.get(position, false);
}
private void startActionMode() {
actionMode = activity.startActionMode(new ActionModeCallback());
}
private void setItemChecked(int position, View v, boolean isChecked) {
if (isChecked) {
checkedItems.put(position, true);
}
else {
checkedItems.delete(position);
}
}
private void colorItem(View v, boolean isChecked) {
int color;
if (isChecked) {
color = COLOR_BLUE;
}
else {
color = Color.TRANSPARENT;
}
v.setBackgroundColor(color);
}
private void colorItem(int position, boolean isChecked) {
View listItemView = listView.getChildAt(position);
colorItem(listItemView, isChecked);
}
private final class ActionModeCallback implements ActionMode.Callback {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
activity.getSupportMenuInflater().inflate(R.menu.list_action_menu, menu);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
isInActionMode = true;
swipeDismissList.pause();
return true;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
int itemsSize = checkedItems.size();
switch (item.getItemId()) {
case R.id.menu_dismiss:
dismissFeeds(itemsSize);
break;
case R.id.menu_mark_as_read:
markFeedsAsRead(itemsSize);
break;
}
restartLoaderIfNecessary(itemsSize);
mode.finish();
return true;
}
private void dismissFeeds(int itemsSize) {
ArrayList<Feed> feeds = new ArrayList<Feed>();
for (int i = itemsSize - 1; i >= 0; i--) {
int position = checkedItems.keyAt(i);
Feed dismissedFeed = dismissFeedAndReturn(position);
feeds.add(dismissedFeed);
}
createUndoAction(feeds, R.string.undobar_message_deleted_selected, Action.DISMISSED);
}
private void markFeedsAsRead(int itemsSize) {
ArrayList<Long> feedsIDs = new ArrayList<Long>();
for (int i = itemsSize - 1; i >= 0; i--) {
int position = checkedItems.keyAt(i);
Feed readFeed = markFeedAsReadAndReturn(position);
feedsIDs.add(readFeed.ID());
}
createUndoAction(feedsIDs, R.string.undobar_message_read_selected, Action.READ);
}
private void createUndoAction(ArrayList<? extends Serializable> feedsIDs, int messageId, Action actionType) {
UndoableCollection undoableAction = new UndoableCollection(feedsIDs, actionType);
undoBarController.showUndoBar(undoableAction, messageId);
}
private void restartLoaderIfNecessary(int itemsSize) {
if (itemsWillChange(itemsSize)) {
restartLoader();
}
}
private boolean itemsWillChange(int itemsSize) {
return itemsSize != 0;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
isInActionMode = false;
swipeDismissList.resume();
uncolorAndClearAllItems();
}
}
private void uncolorAndClearAllItems() {
int size = checkedItems.size();
for (int i = size - 1; i >= 0; i--) {
int position = checkedItems.keyAt(i);
colorItem(position, false);
}
checkedItems.clear();
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
if (isInActionMode) {
checkItem(position, v);
clearCheckedItemsIfNoSelection();
}
}
private void clearCheckedItemsIfNoSelection() {
if (noItemSelected()) {
checkedItems.clear();
actionMode.finish();
}
}
private boolean noItemSelected() {
return checkedItems.size() == 0;
}
SimpleCursorAdapter
class FeedCursorAdapter(context: Context, cursor: Cursor)
extends SimpleCursorAdapter(
context,
R.layout.list_item,
cursor,
Array(C_TITLE, C_SITE, C_ADDED_DATE, C_IMAGE),
Array(R.id.textViewFeedTitle, R.id.textViewChannelSite, R.id.textViewFeedDate, R.id.imageViewFeedImage),
0) {
setViewBinder(new FeedCursorViewBinder(context))
}
ViewBinder
class FeedCursorViewBinder(context: Context) extends SimpleCursorAdapter.ViewBinder {
implicit def int2bool(int: Int) = if (int == 1) true else false
implicit def longDate2String(longDate: Long) = new Date(longDate).toLocaleString
private lazy val bitmapUtils = new BitmapUtils(context)
override def setViewValue(view: View, cursor: Cursor, columnIndex: Int) = {
/**
* Get columns indexes from cursor
*/
def getColumnIndex(columnName: String) = cursor.getColumnIndex(columnName)
val indexTitle = getColumnIndex(C_TITLE)
val indexRead = getColumnIndex(C_READ)
val indexDate = getColumnIndex(C_ADDED_DATE)
val indexImage = getColumnIndex(C_IMAGE)
val indexChannel = getColumnIndex(C_CHANNEL)
val indexSite = getColumnIndex(C_SITE)
def setTextAndColor(index: Int) = {
val text = cursor.getString(index)
val textView = view.asInstanceOf[TextView]
textView.setTextColor(getTextColor)
textView.setText(text)
true
}
def getTextColor = {
val isRead: Boolean = cursor.getInt(indexRead)
if (isRead) Color.GRAY
else Color.BLACK
}
columnIndex match {
case `indexTitle` | `indexSite` => setTextAndColor(columnIndex)
case `indexImage` => {
val imageId: Int = cursor.getInt(indexImage)
val imageFromFile = bitmapUtils.readBitmapForFeed(imageId)
val feedImage =
if (imageFromFile != null) imageFromFile
else BitmapFactory.decodeResource(context.getResources, imageId)
val imageViewFeedImage = view.asInstanceOf[ImageView]
imageViewFeedImage.setImageBitmap(feedImage)
true
}
case `indexDate` => {
val dateLong = cursor.getLong(indexDate)
val textViewFeedDate = view.asInstanceOf[TextView]
textViewFeedDate.setText(dateLong)
true
}
case _ => false
}
}
}
Maybe I'll precise my question:
The answer is so simle that I'm ashamed that I didn't find it earlier.
It's just enough to override the following methods:
override def getViewTypeCount = if (getCount < 1) LIST_COUNT else getCount
// which returns how many different views will be in list view
override def getItemViewType(position: Int) = position
// which defines the type of current list item (when some of list items returns the same type, this view will be reused!)
where LIST_COUNT
is big enough to be sure that no more items will be displayed in a ListView
.
Only two more restriction:
ViewHolder
pattern, because list items won't be used again