ema3272
ema3272

Reputation: 1051

How can I get the location of an icon in the action menu?

I am developing a tutorial for an application and I need to point at a particular icon in the toolbar.

Here is an extract of the XML for the action menu:

<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity">

<item android:id="@+id/AbErase"
    android:title="@string/Erase"
    android:icon="@android:drawable/ic_delete"
    android:orderInCategory="10"
    app:showAsAction="ifRoom|collapseActionView" />

<item android:id="@+id/AbSuggest"
    android:title="@string/Suggest"
    android:icon="@mipmap/ic_lightbulb_outline_white_48dp"
    android:orderInCategory="50"
    app:showAsAction="ifRoom|collapseActionView" />
<item android:id="@+id/AbUndo"
    android:title="@string/ActionBarUndo"
    android:icon="@android:drawable/ic_menu_revert"
    android:orderInCategory="51"
    app:showAsAction="ifRoom|collapseActionView" />
...

Here is my code:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    isBeingRestored = (savedInstanceState != null);

    Toolbar scToolbar = (Toolbar) findViewById(R.id.Sc_toolbar);
    setSupportActionBar(scToolbar);

    scToolbar.post(new Runnable() {
        @Override
        public void run() {
            if (!isBeingRestored) {
                //View mErase = findViewById(R.id.AbErase);
                View mErase = overflowMenu.getItem(0).getActionView();
                int[] location = new int[2];
                mErase.getLocationOnScreen(location);
                eraseIconLeft = location[0];
            }
        }
    }

With View mErase = findViewById(R.id.AbErase); mErase is set to null, **** start EDIT **** which is not suprising as AbErase is the id of a MenuItem, not the id of a View. **** end EDIT **** With View mErase = overflowMenu.getItem(0).getActionView(); location is set to (0, 24), which is wrong as there already is a logo icon and a title in the toolbar.

How can I get the absolute X coordinate of the AbErase view in the toolbar?

**** EDIT **** here is the code where the initialisation of the static variable overflowMenu can be found:

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.menu_main, menu);

    actionBar.collapseActionView();

    overflowMenu = menu;

    isInitializedMenuItem = menu.findItem(R.id.AbInitialized);
    isInitializedMenuItem.setChecked(isInitializeCbxChecked);

    return super.onCreateOptionsMenu(menu);
}

Upvotes: 0

Views: 1752

Answers (2)

Sakchham
Sakchham

Reputation: 1739

Try the following code snippet

@Nullable 
View getMenuItemView(Toolbar toolbar, @IdRes int menuItemId) throws IllegalAccessException,NoSuchFieldException {
    Field mMenuView = Toolbar.class.getDeclaredField("mMenuView");
    mMenuView.setAccessible(true);
    Object menuView = mMenuView.get(toolbar);
    Field mChildren = menuView.getClass()  //android.support.v7.internal.view.menu.ActionMenuView
                          .getSuperclass() //android.support.v7.widget.LinearLayoutCompat
                          .getSuperclass() //android.view.ViewGroup
                          .getDeclaredField("mChildren");
    mChildren.setAccessible(true);
    View[] children = (View[]) mChildren.get(menuView);
    for (View child : children) {
        if (child.getId() == menuItemId) {
            return child;
        }                          
    }
    return null;                      
}

assuming android.support.v7.widget has been used. The above method would return a View corresponding to you menu item.

View menuItemView = getMenuItemView(scToolbar,R.id.AbErase);
int x = menuItemView.getX();
int y = menuItemView.getY();

and you have your coordinates.

EDIT

after a bit more research I found out reflection isn't required.

Before fetching the menuItemView we must wait for the Toolbar to finish its layout pass

scToolbar.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            scToolbar.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            View menuItem = findViewById(R.id.AbErase);
            if (menuItem != null) {
                int[] location = new int[2];
                menuItem.getLocationOnScreen(location);
                int x = location[0]; //x coordinate
                int y = location[1]; //y coordinate
            }
        }
    });

the above snippet is tested to work, it may be placed in any convenient lifecycle callback method.

EDIT

On looking at the source for the activity the key things that must be remembered is that View menuItem = findViewById(R.id.AbErase); should only be called from a ViewTreeObserver.OnGlobalLayoutListener, onGlobalLayout callback.

Upvotes: 4

Borislav Kamenov
Borislav Kamenov

Reputation: 711

In onCreate method is too early. The view is not measured yet. Override onPrepareOptionMenu() and put your code in it. Also remove your Runnable, its unnecessary.

Upvotes: 0

Related Questions