android developer
android developer

Reputation: 116060

How to add dividers between specific menu items?

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?

Upvotes: 32

Views: 37237

Answers (8)

iroiroys
iroiroys

Reputation: 963

In Material3 Design, MDC-Android, make menu items in each group, and call setGroupDividerEnabled(Boolean).

Dividers will inserted between group or menu item(s).

Menu divider example

MyActivity.kt

private val binding by lazy { ActivityMyBinding.inflate(layoutInflater) }

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(binding.root)
     
    // other initializations..
    // ...

    binding.toolbar.menu.setGroupDividerEnabled(true)
}

activity_my.xml

<androidx.coordinatorlayout.widget.CoordinatorLayout 
    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"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <com.google.android.material.appbar.MaterialToolbar
            <!-- Other properties -->
            android:id="@+id/toolbar"
            app:menu="@menu/main" />

    </com.google.android.material.appbar.AppBarLayout>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

menu.xml

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

    <item
        android:id="@+id/item0"
        android:title="item0"
        app:showAsAction="never" />


    <item
        android:id="@+id/item1"
        android:title="item1"
        app:showAsAction="never" />

    <group android:id="@+id/group0">
        <item
            android:id="@+id/item2"
            android:title="group0 item2"
            app:showAsAction="never" />
    </group>

    <group android:id="@+id/group1">
        <item
            android:id="@+id/item3"
            android:title="group1 item3"
            app:showAsAction="never" />

        <item
            android:id="@+id/item4"
            android:title="group1 item4"
            app:showAsAction="never" />
    </group>

</menu>

Upvotes: 0

Bryan W
Bryan W

Reputation: 1167

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

Upvotes: 8

Ariel Teve
Ariel Teve

Reputation: 23

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>

Upvotes: -2

Hiren Patel
Hiren Patel

Reputation: 52810

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.

Upvotes: -2

Hexise
Hexise

Reputation: 1568

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

Upvotes: 1

shreyas
shreyas

Reputation: 2166

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

Upvotes: 17

android developer
android developer

Reputation: 116060

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>

Upvotes: 3

tiny sunlight
tiny sunlight

Reputation: 6251

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;

Upvotes: -5

Related Questions