Question

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:

How to set list item background (with cursor adapter) at runtime and not duplicate it, when list item view is reused?

Was it helpful?

Solution 2

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:

  • This IS NOT EFFICIENT at all, because it enforces to create new list items for each row without reusing the old ones.
  • In this technique there's no need to create ViewHolder pattern, because list items won't be used again

OTHER TIPS

is it just a problem with actionBarSherlock? if not :

  1. listview is supposed to re-use old views. if you don't do it , and keep returning a new view for the getView method, you will end up with OOM . i've written a post about it here.

  2. just as you've written, you can have multiple selection on listview , even if it re-uses the items. just hold some kind of collection (or an array, or a sparse array of booleans) that will hold which items are selected, and in the getView set the background of the item (even if it's re-used) accordingly.

  3. if you are new to listView, please watch the lecture "the world of listView" .

  4. if you don't have a lot of items, you can safely use a scrollView with multiple views in it.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top