Skip to content

5.x | ActionModeHelper

Davide Steduto edited this page Sep 11, 2018 · 11 revisions

In this page


Introduction

This Helper incredibly simplifies the development of the ActionMode. With this helper it has never been so easy to start and manage the ActionMode.

From Android®️ suggestion I decided to use view Activation state instead of view Selection state as others libraries mistakenly apply:

A view can be activated or not. Note that activation is not the same as selection. Selection is a transient property, representing the view (hierarchy) the user is currently interacting with.
Activation is a longer-term state that the user can move views in and out of. For example, in a list view with single or multiple selection enabled, the views in the current selection set are activated. (Um, yeah, we are deeply sorry about the terminology here.) The activated state is propagated down to children of the view it is set on.

Class location 👓

eu.davidea.flexibleadapter.helpers
  |_ ActionModeHelper

Configuration & Initialization

No XML configuration option is recommended. You can now call the new DrawableUtils static methods to compose dynamic backgrounds, see wiki page Utils. You don't need XML anymore.

From the Activity, initialize the ActionMode as following:

public class MainActivity extends AppCompatActivity implements
            ActionMode.Callback,
            FlexibleAdapter.OnItemClickListener,
            FlexibleAdapter.OnItemLongClickListener {

    private FlexibleAdapter<IFlexible> mAdapter;
    private ActionModeHelper mActionModeHelper;

    private void initializeActionModeHelper(@Mode int mode) {
        //this = ActionMode.Callback instance
        mActionModeHelper = new ActionModeHelper(mAdapter, R.menu.menu_context, this) {
            // Override to customize the title
            @Override
            public void updateContextTitle(int count) {
                // You can use the internal mActionMode instance
                if (mActionMode != null) {
                    mActionMode.setTitle(count == 1 ?
                            getString(R.string.action_selected_one, count) :
                            getString(R.string.action_selected_many, count));
                }
            }
        }.withDefaultMode(mode);
    }
}

The ActionMode.Callback has to continue to be implemented in the Activity:

// Change the status bar color when ActionMode is starting and ending
public boolean onCreateActionMode(ActionMode mode, Menu menu);
public void onDestroyActionMode(ActionMode mode);
// Adjust and handle the Context menu
public boolean onPrepareActionMode(ActionMode mode, Menu menu);
public boolean onActionItemClicked(ActionMode mode, MenuItem item);

ℹ️ Note: Check the code @ MainActivity#908

ℹ️ Note: Make sure the onItemClick & onItemLongClick are triggered by adding the overriden methods to the listener

mAdapter.addListener(this);

Simple selection (IDLE)

Mode IDLE is the default value at the start up. However to switch from another mode to IDLE, write the following statements and change the default mode as well:

// from SINGLE to IDLE
import eu.davidea.flexibleadapter.SelectableAdapter.Mode;

mAdapter.setMode(Mode.IDLE);
mActionModeHelper.withDefaultMode(Mode.IDLE);

Single selection (SINGLE)

When you need to switch mode from IDLE to SINGLE and change the default mode as well:

// from IDLE to SINGLE
import eu.davidea.flexibleadapter.SelectableAdapter.Mode;

mAdapter.setMode(Mode.SINGLE);
mActionModeHelper.withDefaultMode(Mode.SINGLE);

Single selection can work in conjunction with ActionMode for Multi selection. See Multi selection (MULTI) below here to know how they can work together.

Multi selection (MULTI)

In the method mActionModeHelper.onClick() the modes SINGLE and MULTI are handled automatically! 👍

@Override
public boolean onItemClick(int position) {
   // Action on elements are allowed if Mode is IDLE, otherwise selection has priority
   if (mAdapter.getMode() != Mode.IDLE && mActionModeHelper != null) {
      boolean activate = mActionModeHelper.onClick(position);
      // Last activated position is now available
      Log.d(TAG, "Last activated position " + mActionModeHelper.getActivatedPosition());
      return activate;
   } else {
      // Handle the item click listener
      ...
      // We don't need to activate anything
      return false;
   }
}

@Override
public void onItemLongClick(int position) {
   mActionModeHelper.onLongClick(this, position);
}

When it is needed, the following methods are very useful. For example, after items have been deleted OR a restoration is performing OR the button back has been pressed (onBackPressed()).

// To restore the selection after a restoration
if (mAdapter.isRestoreWithSelection()) {
   mActionModeHelper.restoreSelection(this);
}

// Finish the action mode
mActionModeHelper.destroyActionModeIfCan();

// New title for context
mActionModeHelper.updateContextTitle(mAdapter.getSelectedItemCount());

ℹ️ Note: With version 5.x the ViewHolder automatically handles the view activation. Check the wiki page ViewHolders for more details.

Optionally, you can now set View Elevation for the activation, API compatibility is maintained.

/**
 * Allows to set elevation while the view is activated.
 * Override to return desired value of elevation on this itemView.
 *
 * @return never elevate, returns 0dp if not overridden
 */
public float getActivationElevation() {
   return 0f;
}

ℹ️ Note: Also the binding of the selection status (when user scrolls) is automatically handled by the Adapter, you don't have to call any view activation. This is an extract of the pre-implemented onBindViewHolder():

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads) {
   //When user scrolls, this line binds the correct selection status
   holder.itemView.setActivated(isSelected(position));
   ...
}

Handling rotation

Preserve the current selection state, together with other flags:

@Override
public void onSaveInstanceState(Bundle outState) {
   mAdapter.onSaveInstanceState(outState);
   super.onSaveInstanceState(outState);
}

Restore the previous selection state:

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
   super.onRestoreInstanceState(savedInstanceState);
   // Restore previous state
   if (savedInstanceState != null && mAdapter != null) {
      //Selection
      mAdapter.onRestoreInstanceState(savedInstanceState);
      mActionModeHelper.restoreSelection(this);
    }
}

⚠️ Note: Pay attention to re-initialize the adapter with previous elements, before restoring the selection! More details in issue #581.

selectAll() and clearSelection()

No more notifyItemRangeChanged() in selectAll() and clearSelection() methods: This means no more item binding!
Now, bound selectable ViewHolders will have the StateListDrawable background switching status (activated/normal) when invoking internally FlexibleViewHolder.toggleSelection(), so that, all inner views can complete animations with no interruptions.

ℹ️ Note:

  • The cached ViewHolders are removed when they are recycled by the RecyclerView.
  • The ViewHolders must extend FlexibleViewHolder, otherwise, item binding still occurs.
Clone this wiki locally