5

What I am trying to do is showing a PopupWindow pointing to the overflow icon (the three dots) on the Toolbar. So I need to get a reference to the View object with the id of the icon. But what is the id?

The PopupWindow is used to tell the users that there are new entries added to the overflow menu. And suggest users to check it out.

darklord
  • 5,077
  • 12
  • 40
  • 65

5 Answers5

7

You should create the button id

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <item type="id" name="overflowActionButton"/>
</resources>

then create the button style

<style name="Widget.ActionButton.Overflow" parent="Widget.AppCompat.ActionButton.Overflow">
    <item name="android:id">@id/overflowActionButton</item>
</style>

and add this style in the theme

<style name="Theme.App" parent="Theme.MaterialComponents.Light.NoActionBar">
    <item name="actionOverflowButtonStyle">@style/Widget.ActionButton.Overflow</item>
</style>

enter image description here

finally you should find the button view by id

activity.findViewById(R.id.overflowActionButton)

and do what you want

5

The overflow menu item doesn't have a resource id. I found the overflow view by traversing the toolbar. The debugger showed an id of -1 and the Hierarchy Viewer showed no resource-id.

Here is how I found the overflow view without a resource id:

/**
 * Get the OverflowMenuButton.
 *
 * @param activity
 *     the Activity
 * @return the OverflowMenuButton or {@code null} if it doesn't exist.
 */
public static ImageView getOverflowMenuButton(Activity activity) {
  return findOverflowMenuButton(activity, findActionBar(activity));
}

static ImageView findOverflowMenuButton(Activity activity, ViewGroup viewGroup) {
  if (viewGroup == null) {
    return null;
  }
  ImageView overflow = null;
  for (int i = 0, count = viewGroup.getChildCount(); i < count; i++) {
    View v = viewGroup.getChildAt(i);
    if (v instanceof ImageView && (v.getClass().getSimpleName().equals("OverflowMenuButton") ||
        v instanceof ActionMenuView.ActionMenuChildView)) {
      overflow = (ImageView) v;
    } else if (v instanceof ViewGroup) {
      overflow = findOverflowMenuButton(activity, (ViewGroup) v);
    }
    if (overflow != null) {
      break;
    }
  }
  return overflow;
}

static ViewGroup findActionBar(Activity activity) {
  try {
    int id = activity.getResources().getIdentifier("action_bar", "id", "android");
    ViewGroup actionBar = null;
    if (id != 0) {
      actionBar = (ViewGroup) activity.findViewById(id);
    }
    if (actionBar == null) {
      return findToolbar((ViewGroup) activity.findViewById(android.R.id.content).getRootView());
    }
  } catch (Exception e) {
    e.printStackTrace();
  }
  return null;
}

static ViewGroup findToolbar(ViewGroup viewGroup) {
  ViewGroup toolbar = null;
  for (int i = 0, len = viewGroup.getChildCount(); i < len; i++) {
    View view = viewGroup.getChildAt(i);
    if (view.getClass() == android.support.v7.widget.Toolbar.class ||
        view.getClass().getName().equals("android.widget.Toolbar")) {
      toolbar = (ViewGroup) view;
    } else if (view instanceof ViewGroup) {
      toolbar = findToolbar((ViewGroup) view);
    }
    if (toolbar != null) {
      break;
    }
  }
  return toolbar;
}

Calling getOverflowMenuButton(activity) will return null in onCreate because the overflow menu isn't laid out yet. To get the overflow menu in onCreate I did the following:

findViewById(android.R.id.content).post(new Runnable() {
  @Override public void run() {
    ImageView overflow = getOverflowMenuButton(MainActivity.this);
  }
});
Jared Rummler
  • 37,824
  • 19
  • 133
  • 148
1

I found a library called TapTarget and a function TapTarget.forToolbarOverflow(). It presents a solution: https://github.com/KeepSafe/TapTargetView/blob/master/taptargetview/src/main/java/com/getkeepsafe/taptargetview/TapTarget.java#L96

The way how it finds the overflow view is not neat but should be stable.

darklord
  • 5,077
  • 12
  • 40
  • 65
  • It's a hacky solution that may not work on newer android version. – android_dev Apr 08 '19 at 08:14
  • As long as you use `androidx.appcompat.widget.Toolbar` instead of the native `android.widget.Toolbar` it only depends on the support library version, not the Android version. That is something that can easily be tested each time the support library gets updated. – devconsole Jul 17 '19 at 13:12
0

you want to create custom DropDown menu? consider this "native" way

or use android:showAsAction="never" in your menu.xml. doc of showAsAction attribute HERE. when one of MenuItems have set never value then you will get overflow three-dot icon automatically and these MenuItems will be hidding there

also you may try to use Hierarchy Viewer to investigate this id if really needed

Community
  • 1
  • 1
snachmsm
  • 17,866
  • 3
  • 32
  • 74
  • No the menu items are already there. I am trying to show a popup window to tell users that there are several new options added to the overflow menu. The popup window will make the new entries in the overflow menu more discoverable. – darklord Mar 22 '17 at 15:03
  • maybe try to use [Hierarchy Viewer](https://developer.android.com/studio/profile/hierarchy-viewer-walkthru.html) to investigate this id – snachmsm Mar 22 '17 at 15:11
0

Instead of using expensive and complicated layout traversal to find the overflow menu, I have achieved showing the PopupWindow under the overflow menu by using the Toolbar view as anchor and setting gravity to Gravity.END:

 /**
  * Sets the anchor view and shows the popup. In case of narrow display the menu items may be hidden in an overflow
  * menu, in that case anchorView may be null and the popup will be anchored to the end of the toolbar.
  */
 public void show(@Nullable View anchorView, @NonNull View toolbarView) {
     if (anchorView == null) {
         setDropDownGravity(Gravity.END);
         setAnchorView(toolbarView);
     } else {
         setAnchorView(anchorView);
     }
     show();
 }
Miloš Černilovský
  • 3,846
  • 1
  • 28
  • 30