5

Here's the default position of the contextual menu in Android:

default position

However, the app Inbox has it farther from the top and the right edges of the screen:

inbox

Is there a relatively straight-forward way to achieve this?

espinchi
  • 9,144
  • 6
  • 58
  • 65
  • I think you can read http://stackoverflow.com/questions/26979476/why-is-my-overflow-dropdown-menu-on-top-of-the-actionbar – BNK Jan 28 '16 at 05:56

6 Answers6

5

Is there a relatively straight-forward way to achieve this?

Yes, of course. And, it only requires 5 lines of code.

Konstantin Loginov takes a bottom-up approach where you literally handle everything in code/design. As much as I appreciate the said approach, it adds complexity to your source. Its always better to check whether a top-down approach is available before resorting to a custom solution.

Here's the top-down version:

Following line will go inside your activity's base theme definition:

<item name="actionOverflowMenuStyle">@style/OverflowMenuStyle</item>

The style OverflowMenuStyle will be defined as:

<style name="OverflowMenuStyle" parent="Widget.AppCompat.PopupMenu.Overflow">
    <item name="android:dropDownHorizontalOffset">-16dp</item>
    <item name="android:dropDownVerticalOffset">16dp</item>
</style>

Results:

Unstyled:

enter image description here

Styled:

enter image description here

Additional notes (possibly trivial to you, but they may help others):

A typical app-theme setup is:

/res/values/styles.xml:

<!-- Values defined here apply to all supported API versions unless overridden in children -->
<BaseTheme />

<!-- Defined values apply to all supported API versions unless overridden in res/values-vXX/styles.xml -->
<AppTheme extends BaseTheme />

/res/values-vXX/styles.xml:

<!-- Values defined here apply to all API versions >= XX unless overridden in res/values-vYY/styles.xml where YY > XX -->
<AppTheme extends BaseTheme />

In this kind of setup, actionOverflowMenuStyle will be defined under BaseTheme. Note the absence of android: prefix - we are overriding actionOverflowMenuStyle provided by appcompat library, not the android system.

Community
  • 1
  • 1
Vikram
  • 51,313
  • 11
  • 93
  • 122
2

About Inbox app

What you see on the screenshot, is not exactly a Menu, but a FrameLayout with background with shadows. It's just a custom View.

enter image description here
You can check it with UI Automator Viewer

So you can do same trick and instead of inflating PopupMenu, create a custom View and place it wherever you want.

How to archive same behaviour as a Menu

First, generate background for the popup menu by using Android Action Bar Style Generator(or create a 9patch background by your own).

Then, take menu_dropdown_panel.9 file:

enter image description here

and add paddings there (I used Paint.NET to do it: resize the canvas, insert old image back and move black lines to the sides of the canvas):

enter image description here

Set new background as android:popupBackground in styles:

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <item name="popupMenuStyle">@style/PopupMenu.Example</item>
</style>

<style name="PopupMenu.Example" parent="@style/Widget.AppCompat.Light.PopupMenu">
    <item name="android:popupBackground">@drawable/menu_dropdown_panel_background</item>
</style>

Result looks like this:

enter image description hereenter image description here
(vertical position depends on the anchor you passes to PopupMenu's constructor)

To show this PopupMenu, I use this code:

PopupMenu popup = new PopupMenu(MainActivity.this, fab);
popup.getMenuInflater().inflate(R.menu.popup_menu, popup.getMenu());
popup.show();

I hope, it helps

Community
  • 1
  • 1
Konstantin Loginov
  • 15,802
  • 5
  • 58
  • 95
  • This would work unless you need the view to overlap Actionbar, like in the picture OP posted. To make it overlap, it has to be in it's own activity that doesn't have an Actionbar. – C0D3LIC1OU5 Jan 28 '16 at 16:20
  • @C0D3LIC1OU5 I tend to disagree with you. It's a regular Toolbar and you can put a Custom View on top of it fairly easy, as we can see in Inbox. Again, you can check it Automator Viewer – Konstantin Loginov Jan 28 '16 at 16:37
  • 1
    @C0D3LIC1OU5 Just to avoid being unfounded - here's an example, how Toolbar can be overlapped: http://stackoverflow.com/q/32986588/1658267 Inbox does same thing. (Though, I'm not judging if it's ideal or not) – Konstantin Loginov Jan 28 '16 at 16:51
  • @C0D3LIC1OU5 added screenshot of `PopupMenu` which overlaps `Toolbar`. Works like a charm :-) – Konstantin Loginov Jan 28 '16 at 19:09
1

you can get rid of these issue with this solution, but it wouln't overlay the menu, instead it will act like below the menu button, if you want then try it,

create a style

 <style name="OverflowMenu" parent="Widget.AppCompat.PopupMenu.Overflow" tools:targetApi="21">
    <item name="overlapAnchor">false</item>
    <!--<item name="dropDownVerticalOffset">?attr/actionBarSize</item>-->
    <item name="android:overlapAnchor">false</item>
</style>

then add it to your base style

<item name="actionOverflowMenuStyle">@style/OverflowMenu</item>
Brendon
  • 1,368
  • 1
  • 12
  • 28
1

There is no straight-forward way. But there is a way to mimic the behavior or create an illusion of it happening.

Something similar is already answered in this post. It was a slightly different question but the answer may serve your purpose too.

Instead of having an actual overflow menu you could "cheat" a little bit. Have an icon in your actionbar that looks like the overflow icon. You should set showAsAction to always on this MenuItem. OnClick of the overflow icon, you show a ListPopupWindow that's anchored to the MenuItem view. If the ListPopupWindow doesn't show up where you want it to, you can call ListPopupWindow.setHorizontalOffset() and ListPopupWindow.setVerticalOffset()

Community
  • 1
  • 1
Viral Patel
  • 32,418
  • 18
  • 82
  • 110
0

This isn't really possible to do with default Overflow Menu. My guess is that they added a custom overflow button to open a custom Activity (to overlay the ActionBar) that has a transparent root view, and, say, a properly sized and positioned CardView for the visible part. CardView is taking care of the border and elevation effects, but there's probably a custom opening animation too. This is custom work.

C0D3LIC1OU5
  • 8,600
  • 2
  • 37
  • 47
  • It's not a CardView, look at my answer - I had Layout Hierarchy snapshot there :-) – Konstantin Loginov Jan 28 '16 at 16:29
  • Not using a CardView means you have to style the view manually - which is time consuming. Even if they didn't use CardView in the Inbox app, they should have, instead of styling the view manually to look like a standard CardView. – C0D3LIC1OU5 Jan 28 '16 at 16:37
  • That's something I can't argue with. They have plenty of various hacks in Inbox, btw. I sincerely recommend to play with Automator & Inbox. For example, I liked how they solve problem of having list of emails withing one huge "card" (which actually is just set of cards) in landscape/tablet mode – Konstantin Loginov Jan 28 '16 at 16:40
0

i think this is what exactly you looking for,you need to customize the view

public void onClick(View v) {
    // code from above
    // ...

    popupMenu.show();

    // Try to force some horizontal offset
    try {
        Field fListPopup = menuHelper.getClass().getDeclaredField("mPopup");
        fListPopup.setAccessible(true);
        Object listPopup = fListPopup.get(menuHelper);
        argTypes = new Class[] { int.class };
        Class listPopupClass = listPopup.getClass();

        // Get the width of the popup window
        int width = (Integer) listPopupClass.getDeclaredMethod("getWidth").invoke(listPopup);

        // Invoke setHorizontalOffset() with the negative width to move left by that distance
        listPopupClass.getDeclaredMethod("setHorizontalOffset", argTypes).invoke(listPopup, -width);

        // Invoke show() to update the window's position
        listPopupClass.getDeclaredMethod("show").invoke(listPopup);
    } catch (Exception e) {
        // Again, an exception here indicates a programming error rather than an exceptional condition
        // at runtime
        Log.w(TAG, "Unable to force offset", e);
    }
}

For more Check this

Aditya Vyas-Lakhan
  • 13,409
  • 16
  • 61
  • 96