Search code examples
androidactionmode

Android: AbsListView.setItemChecked() cause ActionMode Destroy and Recreate?


If you call AbsListView.setItemChecked() directly, it works well, and the ActionMode will activate and create.

mGridView.setItemChecked(pPosition, true);

But when you call View.startActionMode() first, then call AbsListView.setItemChecked(), the ActionMode create by startActionMode() will destroy, and recreate a new one by setItemChecked().

My question is: How to avoid this issue when call View.startActionMode() first?

Looking forward to your reply! Thanks!


Solution

  • Why recreate a new one? See the source code of AbsListView.setItemChecked(int position, boolean value) method, you can see following code:

        // Start selection mode if needed. We don't need to if we're unchecking something.
        if (value && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
            if (mMultiChoiceModeCallback == null ||
                    !mMultiChoiceModeCallback.hasWrappedCallback()) {
                throw new IllegalStateException("AbsListView: attempted to start selection mode " +
                        "for CHOICE_MODE_MULTIPLE_MODAL but no choice mode callback was " +
                        "supplied. Call setMultiChoiceModeListener to set a callback.");
            }
            mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
        }
    

    That means if mChoiceActionMode == null, it will call startActionMode(mMultiChoiceModeCallback), so will recreate a new ActionMode.

    And how to fix? Here is a simple way: use reflect to assign a ActionMode create by startActionMode() to the private field mChoiceActionMode in AbsListView.

    private void startActionMode() {
        // Get the field "mMultiChoiceModeCallback" instance by reflect
        AbsListView.MultiChoiceModeListener wrapperIns = null;
        try {
            Field wrapper = null;
            wrapper = AbsListView.class.getDeclaredField("mMultiChoiceModeCallback");
            wrapper.setAccessible(true);
            wrapperIns = (AbsListView.MultiChoiceModeListener) wrapper.get(mMessageGridView);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        // Start the ActionMode, but not select any item. 
        ActionMode actionMode = mMessageGridView.startActionMode(wrapperIns);
        // Assign actionMode to field "mChoiceActionMode" by reflect 
        try {
            Field mChoiceActionMode = null;
            mChoiceActionMode = AbsListView.class.getDeclaredField("mChoiceActionMode");
            mChoiceActionMode.setAccessible(true);
            mChoiceActionMode.set(mMessageGridView, actionMode);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
    

    Why here we use wrapper? Because AbsListView.setMultiChoiceModeListener(MultiChoiceModeListener listener) will wrap our mMultiChoiceModeListener, so we can't not use directly.