Change ActionMode Overflow icon
Asked Answered
O

5

6

Is there a way to change the ActionMode Overflow icon without changing the icon for the "normal" ActionBar?

Otiliaotina answered 28/3, 2014 at 18:43 Comment(0)
G
10

I still need to figure out how to only change the Overflow-Icon inside of the ActionMode-Actionbar as I changed my Overflow-Icon in the default-Actionbar which is not visible in the ActionMode-Actionbar (and no, I don't want to change the background of my ActionMode-Actionbar!)

Okay.

Let's start with defining some styles. I will try and explain why we are defining them in this fashion:

// This is just your base theme. It will probably include a lot more stuff.
// We are going to define the style 'OverflowActionBar' next.

<style name="BaseTheme" parent="android:Theme.Holo.Light">
    ....
    ....
    ....
    <item name="android:actionOverflowButtonStyle">@style/OverflowActionBar</item>
</style>

// Assigning a parent to this style is important - we will inherit two attributes -
// the background (state-selector) and the content description

<style name="OverflowActionBar" parent="@android:style/Widget.Holo.ActionButton.Overflow">
    <item name="android:src">@drawable/overflow_menu_light</item>
</style>

// Next up is an extension to our 'BaseTheme'. Notice the parent here.

<style name="ChangeOverflowToDark" parent="@style/BaseTheme">
    <item name="android:actionOverflowButtonStyle">@style/OverflowActionMode</item>
</style>

// One last thing is to define 'OverflowActionMode'. Again, we inherit useful
// attributes by assigning 'Widget.Holo.ActionButton.Overflow' as the parent.

<style name="OverflowActionMode" parent="@android:style/Widget.Holo.ActionButton.Overflow">
    <item name="android:src">@drawable/overflow_menu_dark</item>
</style>

All our work with styles.xml is done. The very last bit happens at runtime. I suppose you already have an implementation of ActionMode.Callback.

In your activity, define a method - changeOverflowIcon():

public void changeOverflowIcon() {
    getTheme().applyStyle(R.style.ChangeOverflowToDark, true);
}

You will be calling this method from onCreateActionMode(...) of your ActionMode.Callback implementation:

public class CustomActionModeCallback implements ActionMode.Callback {

    @Override
    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
        changeOverflowIcon()

        // other initialization

        return true;
    }

    @Override
    public boolean onPrepareActionMode(final ActionMode mode, Menu menu) {
        return true;
    }

    @Override
    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
        return false;
    }

    @Override
    public void onDestroyActionMode(ActionMode mode) {}
}

A bit of explanation:

The assignment in 'BaseTheme' is for the ActionBar. It will pick the drawable overflow_menu_light since we are assigning it in the base theme of your app.

getTheme().applyStyle(R.style.ChangeOverflowToDark, true) 

The second argument true forces the current theme to override the old attributes with the new ones. Since we only define one attribute in ChangeOverflowToDark, its value is overwritten. The ActionBar is not affected because it has already used the old attribute. But, the action mode is yet to be created (it will be created when we return true from onCreateActionMode(...)). When the action mode checks for this attributes value, it gets the new one.

There's more...

The answer given by Manish is quite awesome. I could have never thought of using the content description to find the exact ImageButton. But what if you could find the ImageButton using a straightforward findViewById()?

Here's how you can:

First, we will need unique ids. If your project doesn't currently have a res/values/ids.xml file, create one. Add a new id to it:

<item type="id" name="my_custom_id" />

The setup I discussed above will remain the same. The only difference will be in OverflowActionMode style:

<style name="OverflowActionMode" parent="@android:style/Widget.Holo.ActionButton.Overflow">
    <item name="android:src">@drawable/overflow_menu_dark</item>
    <item name="android:id">@id/my_custom_id</item>
</style>

The id we defined above will be assigned to the ImageButton when we call getTheme().applyStyle(R.style.ChangeOverflowToDark, true);

I'll borrow the code snippet from Manish's answer here:

private ActionMode.Callback mCallback = new ActionMode.Callback()
{
    @Override
    public boolean onPrepareActionMode( ActionMode mode, Menu menu )
    {

        mDecorView.postDelayed(new Runnable() {

            @Override
            public void run() {
                ImageButton btn = (ImageButton) mDecorView.findViewById(R.id.my_custom_id);
                // Update the image here.
                btn.setImageResource(R.drawable.custom);
            }          
        }, 500); // 500 ms is quite generous // I would say that 50 will work just fine

        return true;
    }  
}

Best of both worlds?

Let's say we need R.drawable.overflow_menu_light for ActionBar and R.drawable.overflow_menu_dark for ActionMode.

Styles:

<style name="BaseTheme" parent="android:Theme.Holo.Light">
    ....
    ....
    ....
    <item name="android:actionOverflowButtonStyle">@style/OverflowActionMode</item>
</style>

<style name="OverflowActionMode" parent="@android:style/Widget.Holo.ActionButton.Overflow">
    <item name="android:src">@drawable/overflow_menu_dark</item>
    <item name="android:id">@id/my_custom_id</item>
</style>

As defined in our style, the ActionBar will pick R.drawable.overflow_menu_dark - but don't we need the light version for the ActionBar? Yes - we will assign that in the activity's onPrepareOptionsMenu(Menu) callback:

@Override
public boolean onPrepareOptionsMenu(Menu menu) {

    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            ImageButton ib = (ImageButton) 
                                 getWindow().getDecorView()
                                .findViewById(R.id.my_custom_id);
            if (ib != null)
                ib.setImageResource(R.drawable.overflow_menu_light);
        }
    }, 50L);
    return super.onPrepareOptionsMenu(menu);
}

We are doing this here because before onPrepareOptionsMenu(Menu), the ImageButton would not have been created.

Now, we don't need to deal with ActionMode - because it will pick the dark drawable from the theme.

My apologies for this gigantic post. I really hope it helps.

Glucinum answered 22/8, 2014 at 16:37 Comment(2)
This is just an amazing explanation! Your first tutorial worked out quite well! In my opinion this is a way more beautiful than dealing with Handlers and Runnables.... Thanks for that! :))Pouliot
Tried the first solution with styles, works for me for pre Android 5.0, but does not work with Android Lollipop 5.0. Any updates on this?Shirleyshirlie
O
3

ImageButton is the widget used to display the menu overflow. actionOverflowButtonStyle is used for styling the ImageButton. This styling is applied in ActionMenuPresenter.

private class OverflowMenuButton extends ImageButton implements ActionMenuChildView {
   public OverflowMenuButton(Context context) {
      super(context, null, com.android.internal.R.attr.actionOverflowButtonStyle);
      ...
   }
}

ActionMenuPresenter class is used for building action menus both in action bar and action modes. Hence by overriding the theme files will apply same style in both modes. The only way to accomplish is it programatically as it is done here for the action bar.

Here is the code of how it can be done for action mode overflow icon. You can assign the drawable to the ImageButton in ActionMode.Callback.onPrepareActionMode method.

public class MainActivity extends Activity {
    ViewGroup mDecorView;

    public void onCreate(Bundle savedInstanceState) {
        // Assign mDecorView to later use in action mode callback
        mDecorView = (ViewGroup) getWindow().getDecorView();
    }

    private ActionMode.Callback mCallback = new ActionMode.Callback()
    {
        @Override
        public boolean onPrepareActionMode( ActionMode mode, Menu menu )
        {
            // We have to update the icon after it is displayed, 
            // hence this postDelayed variant. 
            // This is what I don't like, but it is the only way to move forward.

            mDecorView.postDelayed(new Runnable() {

                @Override
                public void run() {
                    ArrayList<View> outViews = new ArrayList<View>();
                    // The content description of overflow button is "More options".
                    // If you want, you can override the style and assign custom content
                    // description and use it here.
                    mDecorView.findViewsWithText(outViews, "More Options", View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION);
                    if(!outViews.isEmpty()) {
                        View v = outViews.get(0);
                        if(v instanceof ImageButton) {
                            ImageButton btn = (ImageButton) v;
                            // Update the image here.
                            btn.setImageResource(R.drawable.custom);
                        }                       
                    }
                }               
            }, 500);

            return true;
        }  

    }
}
Overmantel answered 22/8, 2014 at 6:21 Comment(3)
Thanks! I've already seen this way of implementing this but thought that there has to be a way to do it in xml. However, your explanation is very good and I now think the way you suggest is the only way to do this... Have fun with the bounty (in 14 hrs...)Pouliot
@Pouliot Just a suggestion..make complete use of your bounty offer. You can keep this answer in unaccepted state, so that others will make an attempt to provide (may be) a better solution that this. When the bounty nears it expiry time you can assign the bounty manually.Overmantel
Yeah you're right - but as you explained nobody is going to submit a plain xml-solution ;)Pouliot
R
1

You should be able to do that using styles:

ActionBarSherlock:

<style name="MyTheme" parent="Theme.Sherlock.Light">
    <item name="actionOverflowButtonStyle">@style/MyTheme.OverFlow</item>
</style>

<style name="MyTheme.OverFlow" parent="Widget.Sherlock.ActionButton.Overflow">
    <item name="android:src">@drawable/YOUR_ICON_GOES_HERE</item>
</style>

ActioBar:

<style name="MyTheme" parent="@android:style/Theme.Holo">
    <item name="android:actionOverflowButtonStyle">@style/MyTheme.OverFlow</item>
</style>

<style name="MyTheme.OverFlow" parent="@android:style/Widget.Holo.ActionButton.Overflow">
    <item name="android:src">@drawable/YOUR_ICON_GOES_HERE</item>
</style>

Make sure to set MyTheme in the manifest.

Rf answered 28/3, 2014 at 19:4 Comment(1)
Yes I did that for the ActionBar but I need the Holo.Dark icon for the ActionBar and the Holo.Light icon for the ActionMode.Otiliaotina
C
1

Is there a way to change the ActionMode Overflow icon without changing the icon for the "normal" ActionBar?

Regards how to change the overflow icon, I think there are many answers as above.

If you just want to change the color of the overflow icon, you can use a simple way.

<style name="BaseAppTheme" parent="Theme.xxxx.Light.NoActionBar.xxx">
    ...           
    <item name="actionOverflowButtonStyle">@style/ActionMode.OverFlow</item>
</style>

<style name="ActionMode.OverFlow" parent="@style/Widget.AppCompat.ActionButton.Overflow">
    <item name="android:tint">@color/black</item>  #or any color you want.#
</style>

It works for me. I investigated a bit, just check this screenshot http://prntscr.com/vqx1ov you will know the reason.

And I don't suggest to set the colour of colorControlNormal, it will change the color of "back arrow" and "overflow icon" on ActionBar.

Confraternity answered 27/11, 2020 at 2:38 Comment(0)
C
0

In my case, I just want a different color of the three dots icon, and to achieve it, I set <item name="actionBarTheme">@style/Widget.ActionMode.ActionBar</item> in my theme, and Widget.ActionMode.ActionBar looks like below:

    <style name="Widget.ActionMode.ActionBar" parent="@style/ThemeOverlay.AppCompat.Light">
        <item name="colorControlNormal">the color I want</item>
    </style>
Convergence answered 11/10, 2020 at 9:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.