Gaëtan
Gaëtan

Reputation: 12562

Display ActionMode over Toolbar

I am trying to use the android.view.ActionMode with the new android.support.v7.widget.Toolbar, in addition to the traditional android.app.ActionBar. I am able to display it with:

toolbar.startActionMode(callback);

The problem is that the ActionMode is displayed over the ActionBar, and not over the Toolbar. Is there a way to change that?

I tryied to set the following in my theme, but it does not seem to change anything:

<item name="windowActionModeOverlay">true</item>

Upvotes: 97

Views: 32501

Answers (10)

Ace
Ace

Reputation: 2228

You can leave all the rubbish that only works in certain scenarios/phones/blue moons and simply hack it (in a very clean way):

  1. Create 2 menu groups:
<menu>
    <group android:id="@+id/group_normal">
        <item id="@+id/action_edit"/>
    </group>
    <group android:id="@+id/group_action_mode"
        android:visible="false">
        <item id="@+id/action_mode_1"/>
        <item id="@+id/action_mode_2"/>
        ..
    </group>
</menu>
  1. In your Fragment (or Activity):
class MyFragment: Fragment() {

    private var actionMode = false

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        toolbar.apply {
            setNavigationOnClickListener {
                if(actionMode) endActionMode(this)
                else findNavController().navigateUp()
            }
            setOnMenuItemClickListener {
                when(it.itemId) {
                    R.id.action_edit -> {
                        // start action mode
                        startActionMode(this)
                    }
                    R.id.action_mode_1 -> {
                        // todo
                    }
                    R.id.action_mode_2 -> {
                        // todo
                        endActionMode(this)
                    }
                    ...
                    else -> return@setOnMenuItemClickListener false
                }
                true
            }
        }
    }

    private fun startActionMode(toolbar: Toolbar) {
        with(toolbar.menu) {
            setGroupVisible(R.id.group_normal, false)
            setGroupVisible(R.id.group_action_mode, true)
        }
        toolbar.title = 0 // todo format number of selected items
        actionMode = true
    }

    private fun endActionMode(toolbar: Toolbar) {
        with(toolbar.menu) {
            setGroupVisible(R.id.group_normal, true)
            setGroupVisible(R.id.group_action_mode, false)
        }
        toolbar.setTitle(R.string.original_title)
        actionMode = false
    }
}

Works every time as intended. Add extra functionality as needed.

Upvotes: 2

Leon Latsch
Leon Latsch

Reputation: 751

So, after days going thru this thread, I finally got it working. I'd like to summarize what I did.

Note: This is the solution using a Toolbar to replace the default ActionBar in AppCompatActivity.

  1. I added this line to my AppTheme: It tells android that you want your action mode to overlay the toolbar
<item name="windowActionModeOverlay">true</item>
  1. Use the right imports:

You have to use these imports in order to work with AppCompatActivity:

import androidx.appcompat.view.ActionMode;
// or
import android.support.v7.view.ActionMode;
  1. Start the ActionMode on your Activity like so:
actionMode = startSupportActionMode(callback);

And not like so:

actionMode = startActionMode(callback);

You Activity creates the ActionMode on the toolbar automatically, because it's set as the supportActionToolbar. The style handles the dsiplaying as overlay.

Thanks to @Kuffs and @Lefty.

Upvotes: 7

user2416658
user2416658

Reputation: 1

If you see the view tree,You will can write below code:

 ViewGroup decorView = (ViewGroup) getWindow().getDecorView();
    traverseView(decorView);
 /**
 * traverse find actionmodebar
 * @param root  view
 */
public void traverseView(View root) {
    if (root==null){
        return;
    }
    if (root instanceof ActionBarContextView){
        root.setVisibility(View.GONE);
        return;
    }
    if ((root instanceof ViewGroup)) { // If view is ViewGroup, apply this method on it's child views
        ViewGroup viewGroup = (ViewGroup) root;
        for (int i = 0; i < viewGroup.getChildCount(); ++i) {
            traverseView(viewGroup.getChildAt(i));
        }
    }
}

Upvotes: 0

Kuffs
Kuffs

Reputation: 35651

Since you are using the Toolbar, I also assume you are using the AppCompatActivity and have replaced the built in ActionBar with your custom Toolbar using setSupportActionBar(toolbar);

First of all ensure you are importing the correct namespace:

import androidx.appcompat.view.ActionMode;
// Or
import android.support.v7.view.ActionMode;

and NOT

import android.view.ActionMode;

then use

_actionMode = startSupportActionMode(this);

and NOT

_actionMode = startActionMode(this);

Upvotes: 90

Lefty
Lefty

Reputation: 1422

I think the one thing people are not making clear is that the line

<item name="windowActionModeOverlay">true</item>

is placed in the Base theme i.e AppTheme and not the AppTheme.NoActionBar

<resources>

<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
    <item name="windowActionModeOverlay">true</item>
</style>

<style name="transparent" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="android:windowBackground">@android:color/transparent</item>
    <item name="android:windowIsTranslucent">true</item>
</style>


<style name="AppTheme.NoActionBar">
    <item name="windowActionBar">false</item>
    <item name="windowNoTitle">true</item>
</style>

<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />

<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />

Upvotes: 16

zhangjin
zhangjin

Reputation: 167

find your AndroidManifest.xml ,next add below code in your application or Activity theme

<item name="windowActionModeOverlay">true</item>

so like:

<style name="WorkTimeListTheme" parent="AppTheme.NoActionBar">
        <item name="windowActionModeOverlay">true</item>
        <item name="actionModeBackground">@color/theme_primary</item>
    </style>

Upvotes: 7

Shouheng Wang
Shouheng Wang

Reputation: 648

I have tried all the methods above, but it still doesn`t work. And then, I tried the below method:

    private class ActionModeCallback implements ActionMode.Callback {
    @Override
    public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
        actionMode.getMenuInflater().inflate(R.menu.note_find_action, menu);
        return true;
    }

    @Override
    public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
        ((AppCompatActivity) getActivity()).getSupportActionBar().hide();
        return false;
    }

    @Override
    public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
        return false;
    }

    @Override
    public void onDestroyActionMode(ActionMode actionMode) {
        ((AppCompatActivity) getActivity()).getSupportActionBar().show();
    }
}

Here, I used action mode and startSupportActionMode method of support library. At the same time I have also tried to modify the theme of given activity. Surely, it doesn`t work. So, if you really have no better choice you may try this one.

Just recently, I have found that I used the Colorful frame to enable multiple theme of my app, this will change the theme in code. When I tried to modify the style in this framework, it works.

Hope it works.

Upvotes: 1

Avery
Avery

Reputation: 29

This is the solution I made.

In my onCreateActionMode method of ActionMode.Callback, I add this:

StandaloneActionMode standaloneActionMode = (StandaloneActionMode) actionMode;
Field mContextView;
try {
     mContextView = StandaloneActionMode.class.getDeclaredField("mContextView");
     mContextView.setAccessible(true);
     View contextView = (View) mContextView.get(standaloneActionMode);
     MarginLayoutParams params = (MarginLayoutParams) contextView.getLayoutParams();
     params.topMargin = mToolbar.getTop();
  } catch (NoSuchFieldException e) {
            e.printStackTrace();
  } catch (IllegalAccessException e) {
            e.printStackTrace();
  } catch (IllegalArgumentException e) {
            e.printStackTrace();
  }

It works for me.

Upvotes: 2

Frank
Frank

Reputation: 12308

Do not start it on your activity, but on your toolbar. In you activity:

Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
toolbar.startActionMode(mActionModeCallback)

and you have to use

<item name="windowActionModeOverlay">true</item>

in your theme as stated by Andre.

Upvotes: 73

Andr&#233; Restivo
Andr&#233; Restivo

Reputation: 1300

Try this in your theme:

<item name="windowActionModeOverlay">true</item>

Upvotes: 48

Related Questions