26

Background

I have a menu item in the action bar (toolbar actually) that when clicked, shows a list of items to choose from, similar to radio-buttons:

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

    <item
        android:icon="@drawable/..."
        android:title="@string/..."
        app:showAsAction="always">
        <menu>
            <group
                android:id="@+id/..."
                android:checkableBehavior="single">
                <item .../>
                <item .../>
                <item .../>
            </group>
        </menu>
    </item>
</menu>

I need to put an item below this list of items, that will have a divider between it and the list. Similar to what the material design guidelines show (taken from here) :

enter image description here

EDIT: here's a sketch of what I want to do:

enter image description here

The problem

I can't find a way to do it.

What I've tried

The only possible solutions I've found are:

  1. change the theme of the activity (here), but this will also affect other menu items of the activity

  2. methods to put a divider between menu items when they appear on the action bar, but here they do not appear on the toolbar itself. They appear on a popup menu of a selected item.

  3. I tried to put fake items between the list and the extra item, and I also tried to put a group, an empty group and even tried various attributes.

Sadly nothing worked.

The question

How can I add a divider between specific items of an action-item's popup menu ?

Perhaps I need to create a custom popup menu when clicking on the action item (like here) ? If so, how do I put a divider between specific items there ? Maybe use a Spinner as an action item?

Community
  • 1
  • 1
android developer
  • 106,412
  • 122
  • 641
  • 1,128
  • Look at my answer at the following http://stackoverflow.com/questions/32695710/navigation-drawer-menu-items-selected-within-different-groups. IMO, you can try using icon or background for grouping items (I mean item android:title="Communi...) – BNK Oct 25 '15 at 13:27
  • It doesn't work. I'm talking about the menu of the action bar. Please try it there. You can do it easily by creating a new project, and choose the one with the FAB (in the wizard). It will create a menu for you. – android developer Oct 25 '15 at 15:35
  • There's a workaround, however it is not pretty, that you can add items like the following `` – BNK Oct 26 '15 at 02:55
  • @BNK It doesn't work. It just shows a whole empty item instead, without any drawable. I'm talking about a menu, not iconized action items. One that appears when clicking an item on the actionbar . I will put another screenshot to show what I mean. – android developer Oct 26 '15 at 08:47
  • Here is my screenshot https://drive.google.com/file/d/0B2HGUM4c0YwpdEFVSmlyMjIxc3M/view?usp=sharing and you should update `` – BNK Oct 26 '15 at 08:51
  • @BNK I don't know why you got to show the image, but it still takes a full row (that's also probably clickable), instead of the size of the drawable. Plus, for some reason you got it to be black, but that's probably easy to fix. – android developer Oct 26 '15 at 08:55
  • I forgot to tell you, must add `@Override public boolean onMenuOpened(int featureId, Menu menu) { if (featureId == 108 && menu != null) { if (menu.getClass().getSimpleName().equals("MenuBuilder")) {` – BNK Oct 26 '15 at 08:58
  • `try { Method m = menu.getClass().getDeclaredMethod( "setOptionalIconsVisible", Boolean.TYPE); m.setAccessible(true); m.invoke(menu, true); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (Exception e) { throw new RuntimeException(e); } } } return super.onMenuOpened(featureId, menu); }` – BNK Oct 26 '15 at 08:58
  • @Bnk Can you please post as an answer. It's hard to read this way. Also, will it work as I've written? A divider that's not of the same height as a normal row? One that I can configure? And, why do you use reflection? Isn't there a better way? – android developer Oct 26 '15 at 09:03
  • Sorry, I cannot chat at this moment. I uploaded my full sample code to https://drive.google.com/file/d/0B2HGUM4c0YwpWmxjWFFtQ3VyQkE/view?usp=sharing – BNK Oct 26 '15 at 09:03
  • The chat was by mistake. I wanted to put a comment and then I did. – android developer Oct 26 '15 at 09:04
  • Please try this answer http://stackoverflow.com/questions/30625280/how-to-create-a-simple-divider-in-the-new-navigationview – Awadesh Oct 26 '15 at 09:05
  • @BNK The sample on the Google-drive link works, but the divider takes a full, clickable row. Plus it's black for some reason. – android developer Oct 26 '15 at 09:07
  • @Awadesh Already tried it. It's not for menus of actionbar. Only for navigationView. – android developer Oct 26 '15 at 09:08
  • So I said "it is not pretty" above :). I think the screenshot in your question is for navi drawer/view – BNK Oct 26 '15 at 09:36
  • @BNK I thought you meant the code isn't pretty... – android developer Oct 26 '15 at 09:37
  • I think the pic of group divider in your question is for navi drawer/view only. – BNK Oct 26 '15 at 09:40
  • IMO, you can try create a custom view which contains a horizontal line for that menu item. I have not tried yet. – BNK Oct 26 '15 at 09:45
  • put divider after complete all group menu or seperate menu divider ? –  Oct 26 '15 at 10:03
  • @Harshad I don't understand the question. The divider should be below the group. and above the item that's after the group. It divides the group from the extra item. – android developer Oct 26 '15 at 10:09
  • @BNK That's what I'm trying. In fact, I have succeeded, by using Spinner. The only thing that's missing for me is the style of each item, to make it look exactly like menu items. For some reason, I can't find how to do it. I will now add an answer. I hope someone could help with the styling – android developer Oct 26 '15 at 10:09
  • Use below link : [link] (http://stackoverflow.com/questions/10414668/item-separator-for-context-menu-in-android) –  Oct 26 '15 at 11:55
  • @Harshad This is not about the menu of the action bar, and even then, it shows an item that just has "----..." in it, which is also clickable and takes a full row. – android developer Oct 26 '15 at 12:31

7 Answers7

17

You should use action layout

<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=".LandingActivity">
    <item
        android:id="@+id/action_cart"
        android:title="cart"
        android:actionLayout="@layout/cart_update_count"
        android:icon="@drawable/shape_notification"
        app:showAsAction="always"/>
</menu>

and then the action layout can have the textview with divider.

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <View
        android:id="@+id/divider"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/divider"/>

    <TextView
        android:id="@android:id/text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?android:attr/selectableItemBackground"
        android:gravity="center_vertical"          
        android:textAppearance="?attr/textAppearanceListItemSmall"/>

</LinearLayout>

then you can add the click listener in code

shreyas
  • 2,108
  • 3
  • 16
  • 30
3

OK, I've found a nice workaround, but I'm not sure the styling should be this way. That's what I'm missing:

  1. background of items is on top of the background of the popup of the spinner, and I'm not sure if that's the correct way to put it.
  2. I used the white background of the support library for the popup of the spinner. I think there should be a better way to make it white.
  3. I need to know what is the correct style of the divider. for now I used a simple one
  4. Action bar item style is missing. I just used a simple ImageView, and I think it should be different.
  5. For some reason, on some Android versions (maybe Lollipop and below) the background of the items look black instead of white.
  6. The spinner might sometimes have issues with setOnItemSelectedListener , not sure when.

MainActivity

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.menu_main, menu);
    final MenuItem item = menu.findItem(R.id.action_settings);
    final Spinner spinner = ((Spinner) MenuItemCompat.getActionView(item));
    SimpleImageArrayAdapter adapter = new SimpleImageArrayAdapter(this);
    spinner.setAdapter(adapter);
    return true;
}

public class SimpleImageArrayAdapter extends ArrayAdapter<String> {
    private final String[] items = {"item 1", "item 2", "item 3", "extra item"};

    public SimpleImageArrayAdapter(Context context) {
        super(context, 0);
    }

    @Override
    public int getCount() {
        return items.length;
    }

    @Override
    public String getItem(final int position) {
        return items[position];
    }

    @Override
    public View getDropDownView(int position, View convertView, ViewGroup parent) {
        View rootView = convertView == null ? LayoutInflater.from(getContext()).inflate(R.layout.spinner_item, parent, false) : convertView;
        TextView tv = (TextView) rootView.findViewById(android.R.id.text1);
        tv.setTextColor(0xff000000);
        tv.setText(items[position]);
        boolean isLastItem = position == getCount() - 1;
        rootView.findViewById(R.id.action_divider).setVisibility(isLastItem ? View.VISIBLE : View.GONE);
        rootView.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
        return rootView;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        //this is the view that's shown for the spinner when it's closed
        ImageView iv = new ImageView(getContext());
        iv.setImageResource(android.R.drawable.ic_menu_add);
        int viewSize = getDimensionFromAttribute(MainActivity.this, android.support.v7.appcompat.R.attr.actionBarSize);
        iv.setLayoutParams(new ViewGroup.LayoutParams(viewSize, viewSize));
        iv.setScaleType(ScaleType.CENTER_INSIDE);
        iv.setBackgroundResource(getResIdFromAttribute(MainActivity.this, R.attr.selectableItemBackground));
        return iv;
    }

}

public static int getResIdFromAttribute(final Activity activity, final int attr) {
    if (attr == 0)
        return 0;
    final TypedValue typedValue = new TypedValue();
    activity.getTheme().resolveAttribute(attr, typedValue, true);
    return typedValue.resourceId;
}

public static int getDimensionFromAttribute(final Context context, final int attr) {
    final TypedValue typedValue = new TypedValue();
    if (context.getTheme().resolveAttribute(attr, typedValue, true))
        return TypedValue.complexToDimensionPixelSize(typedValue.data, context.getResources().getDisplayMetrics());
    return 0;
}

res/menu/menu_main.xml

<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="com.example.user.myapplication.MainActivity">
    <item
        android:id="@+id/action_settings"
        android:actionLayout="@layout/spinner"
        android:title=""
        app:actionLayout="@layout/spinner"
        app:showAsAction="always"
        />
</menu>

res/layout/spinner_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/action_divider"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/divider"/>

    <TextView
        android:id="@android:id/text1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?android:attr/selectableItemBackground"
        android:gravity="center_vertical"
        android:minHeight="?attr/listPreferredItemHeightSmall"
        android:paddingEnd="?attr/listPreferredItemPaddingRight"
        android:paddingLeft="?attr/listPreferredItemPaddingLeft"
        android:paddingRight="?attr/listPreferredItemPaddingRight"
        android:paddingStart="?attr/listPreferredItemPaddingLeft"
        android:textAppearance="?attr/textAppearanceListItemSmall"/>

</LinearLayout>

res/layout/spinner.xml

<?xml version="1.0" encoding="utf-8"?>
<Spinner
    android:id="@+id/spinner"
    style="@style/SpinnerWithoutArrow"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

res/values/styles.xml

<style name="SpinnerWithoutArrow" parent="@style/Widget.AppCompat.Spinner">
    <item name="android:background">@null</item>
    <item name="android:popupBackground">@drawable/abc_popup_background_mtrl_mult</item>
</style>

res/drawable/divider.xml

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <size
        android:height="1dp"/>
    <solid android:color="#FFff0000" />
</shape>
android developer
  • 106,412
  • 122
  • 641
  • 1,128
3

As of SDK version 28, you can use menu.setGroupDividerEnabled(boolean). If you're using ContextMenu this is only supported on SDK 28+, but MenuCompat offers backwards compatibility when used in onCreateOptionsMenu().

This will add a divider between the actions for each different groupId, shown as 0 and 1 below:

menu.add(0, getAdapterPosition(), action1, R.string.action1);
menu.add(1, getAdapterPosition(), action2, R.string.action2);
menu.setGroupDividerEnabled(true); 

// Or for MenuCompat < SDK 28:
MenuCompat.setGroupDividerEnabled(menu, true);

Documentation here: https://developer.android.com/reference/android/view/Menu#setGroupDividerEnabled(boolean)


EDIT: Sample code as requested by asker:

Here's the code I am currently using in my app, located in a RecyclerView Adapter. It should work with your menu implementation as well. Since you're defining the menu by XML, the below will also work for you as long as you reference the menu resource. Here's what the result looks like:

ContextMenu divider

Override onCreateContextMenu or your menu's relevant onCreate.. method like so within the:

@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
    menu.setHeaderTitle(getStr(R.string.actions_title));

    // Groups 0 and 1, first parameter for menu.add()
    menu.add(0, getAdapterPosition(), 0, R.string.homescreen);
    menu.add(0, getAdapterPosition(), 1, R.string.lockscreen);
    menu.add(0, getAdapterPosition(), 2, R.string.wpLocation_both);
    menu.add(1, getAdapterPosition(), 3, R.string.action_download);

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        menu.setGroupDividerEnabled(true);  // This adds the divider between groups 0 and 1, but only supported on Android 9.0 and up.
    }
}
Bryan W
  • 720
  • 9
  • 20
  • Wait, now there is an official way to do it? Can you please share a Github sample? Also a screenshot of how it looks? Is there also a way to customize the divider? – android developer Sep 30 '20 at 06:33
  • 1
    @androiddeveloper I've updated my answer with a sample from my app. Note this is only compatible on Android 9.0 and up. I don't believe there is a direct way to customize the divider, although there are some more options that you could look into such as submenus. – Bryan W Oct 01 '20 at 00:07
  • 1
    Seems to work for `onCreateOptionsMenu` too. Is it possible in XML too somehow, and not just in code? Also, how come `setHeaderTitle` isn't available for `onCreateOptionsMenu ` ? And I think it's not just for Android 9 and up, if you use support library. You can use `MenuCompat.setGroupDividerEnabled(menu,true)` , at least according to my tests. – android developer Oct 01 '20 at 07:48
  • @androiddeveloper I quickly skimmed the menu resource docs, seems like the only way is by referencing the menu resource `R.menu.filename` and adding the flag from the inflater, along with `` tags in the XML. Also, `setHeaderTitle` is only available for ContextMenu because it's not attached to the toolbar/appbar (likely Google's design choice). Thanks for info about MenuCompat, I've added that to the answer. – Bryan W Oct 02 '20 at 00:48
  • I don't understand. You say it's impossible to do it via XML ? Also, about MenuCompat, I don't see you've updated the code of the answer. – android developer Oct 02 '20 at 15:19
  • @androiddeveloper Sorry for the confusion. You can add dividers only with the `setGroupDividerEnabled()` method, as long as you define `` tags when you're using the XML menu resource. I have not tested MenuCompat, but seems you can use it like this: `MenuCompat.setGroupDividerEnabled(menu, true);` in your onCreateOptionsMenu override. – Bryan W Oct 03 '20 at 23:55
  • Can you please show a sample with XML? As for `MenuCompat.setGroupDividerEnabled`, I know it works. I just said that you didn't update your code to use it instead, as it supports older versions of Android too. – android developer Oct 04 '20 at 06:42
  • Say, is it also possible to customize the looks of each item there? – android developer Oct 04 '20 at 07:14
1

This can be done by using popup window and list view. In your list view, you can have different view types, such as menu item and divider.

I list the code for popup window part:

    LayoutInflater inflater = LayoutInflater.from(context);
    View view = inflater.inflate(R.layout.option_menu, null);
    ListView listView = (ListView) view.findViewById(R.id.listView);
    listView.setDivider(null);

    mAdapter = new OptionListAdapter(context, options);
    listView.setAdapter(mAdapter);

    listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            //TODO: The code when item is clicked.
        }
    });

    mPopupWindow = new PopupWindow(context, null, R.attr.popupMenuStyle);
    mPopupWindow.setFocusable(true); // otherwise on android 4.1.x the onItemClickListener won't work.
    mPopupWindow.setContentView(view);
    mPopupWindow.setOutsideTouchable(true);

    int height = 0;
    int width = 0;
    float density = context.getResources().getDisplayMetrics().density;
    int minWidth = Math.round(196 * density); // min width 196dip, from abc_popup_menu_item_layout.xml
    int cellHeight = context.getResources().getDimensionPixelOffset(R.dimen.option_height);
    int dividerHeight = context.getResources().getDimensionPixelOffset(R.dimen.divider_height);
    final int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    final int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    for (int i = 0; i < mAdapter.getCount(); i++) {
        Object item = mAdapter.getItem(i);
        if (item != null) {
            View childView = mAdapter.getView(i, null, listView);
            childView.measure(widthMeasureSpec, heightMeasureSpec);
            height += cellHeight;
            width = Math.max(width, childView.getMeasuredWidth());
        } else {
            height += dividerHeight; // divider
        }
    }
    width = Math.max(minWidth, width);
    Drawable background = mPopupWindow.getBackground(); // 9-pitch images
    if (background != null) {
        Rect padding = new Rect();
        background.getPadding(padding);
        height += padding.top + padding.bottom;
        width += padding.left + padding.right;
    }
    mPopupWindow.setWidth(width);
    mPopupWindow.setHeight(height);
    mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);

Then you can use following method to show the popup window:

PopupWindowCompat.showAsDropDown(mPopupWindow, parent, x, y, gravity);

In the adapter for list view, you can override getViewTypeCount() and getItemViewType() to support both menu item layout and divider layout, also you can add any view type that you need.

Here is a snapshot in my app:

enter image description here

Hexise
  • 1,280
  • 14
  • 20
  • Wow this looks really nice. Are there any disadvantages or issues when using it? Sadly I've moved on from this question, so I can't accept it without checking it out, but I will give +1 for the effort :) – android developer Mar 25 '16 at 15:19
  • 2
    No issue found yet. The UI is just made from scratch so everything can be customized. I tested from Android 4.0.3 to Android 6.0, not including lower version API level devices. – Hexise Mar 25 '16 at 15:32
  • 1
    I was trying this but where is ""OptionListAdapter"" im not getting that... or can you plz upload the above project into gethub ??? – Tarit Ray Jun 02 '18 at 06:47
  • @TaritRay OptionListAdapter is the subclass of ListView adapter, you can give it any name you want. – Hexise Jun 07 '18 at 16:04
0

I did it this way:

Reference screenshot:

enter image description here

style.xml:

    <style name="popup" parent="Widget.AppCompat.ListView.DropDown">
            <item name="android:divider">@color/colorPrimary</item>
            <item name="android:dividerHeight">1dp</item>
            <item name="android:textColor">@color/colorPrimary</item>
            <item name="android:itemBackground">@android:color/white</item>
    </style>

 <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">

        <!--- Customize popmenu -->
        <item name="android:dropDownListViewStyle">@style/popup</item>


    </style>

Java code:

private void showPopup(View v) {
        Context wrapper = new ContextThemeWrapper(this, R.style.popup);
        PopupMenu mypopupmenu = new PopupMenu(wrapper, v);
        MenuInflater inflater = mypopupmenu.getMenuInflater();
        inflater.inflate(R.menu.menu_patient_language, mypopupmenu.getMenu());
        mypopupmenu.show();
        mypopupmenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem item) {
                txtPreferredLanguage.setText(item.getTitle().toString());
                switch (item.getItemId()) {
                    case R.id.menuEnglish:
                        // Your code goes here
                        break;

                    case R.id.menuFrench:
                        // Your code goes here
                        break;
                }
                return false;
            }
        });
    }

Hope this will help you.

Hiren Patel
  • 48,538
  • 20
  • 161
  • 144
0

Super simple solution that worked out for me:

Define a drawable for the background:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

    <solid android:color="@android:color/white"/>
    <stroke
        android:width="3dp"
        android:color="@color/colorPrimary"/>

</shape>

then in Styles use the background:

<style name="bluetooth_popup" parent="@android:style/Widget.DeviceDefault.Light.PopupMenu">
    <item name="android:textColor">@color/colorPrimary</item>
    <item name="android:textStyle">bold</item>
    <item name="android:textAllCaps">true</item>
    <item name="android:background">@android:color/transparent</item>
    <item name="android:itemBackground">@drawable/bluetooth_popup_buttons</item>
Ariel Teve
  • 17
  • 2
  • 5
    I didn't try it out but I think it will draw a divider between each item, while this post is more about setting a divider in specific places not below each time. – RJFares Apr 04 '17 at 08:31
  • I think it will not work fir the question ""How to add dividers between specific menu items?"" , but it will work those who need divider for each item – Tarit Ray Jun 01 '18 at 13:03
-4

now :

group1[ item0 item1 item2 ] group1[item3];

change to :

group1[ item0 item1] group1[item2 item3]

group has a divider; it likes that group can add a divder between item;

if divider is unavailable,try background; i never user menu; its my guess;

tiny sunlight
  • 6,096
  • 3
  • 18
  • 41