Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change ActionMode Overflow icon

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

like image 643
TeKo Avatar asked Mar 28 '14 18:03

TeKo


2 Answers

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;
        }  

    }
}
like image 42
Manish Mulimani Avatar answered Sep 30 '22 14:09

Manish Mulimani


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.

like image 66
Vikram Avatar answered Sep 30 '22 13:09

Vikram