Reputation: 116060
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) :
EDIT: here's a sketch of what I want to do:
I can't find a way to do it.
The only possible solutions I've found are:
change the theme of the activity (here), but this will also affect other menu items of the activity
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.
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.
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
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).
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
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:
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
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
Reputation: 52810
I did it this way:
Reference screenshot:
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
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:
Upvotes: 1
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
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:
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
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