【问题标题】:How to add dividers between specific menu items?如何在特定菜单项之间添加分隔符?
【发布时间】:2016-01-21 12:02:20
【问题描述】:

背景

我在操作栏(实际上是工具栏)中有一个菜单项,单击它时会显示可供选择的项目列表,类似于单选按钮:

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

我需要在这个项目列表下方放置一个项目,这将在它和列表之间有一个分隔符。类似于材料设计指南显示的内容(取自here):

编辑:这是我想要做的草图:

问题

我找不到办法。

我尝试过的

我发现的唯一可能的解决方案是:

  1. 更改活动主题(here),但这也会影响活动的其他菜单项

  2. 当菜单项出现在操作栏上时在它们之间放置分隔符的方法,但这里它们不会出现在工具栏本身上。它们出现在所选项目的弹出菜单上。

  3. 我尝试在列表和额外的项目之间放置假项目,我还尝试放置一个组、一个空组,甚至尝试了各种属性。

遗憾的是没有任何效果。

问题

如何在操作项目的弹出菜单的特定项目之间添加分隔符?

也许我需要在单击操作项时创建一个自定义弹出菜单(如here)?如果是这样,我如何在特定项目之间放置分隔符? 也许将 Spinner 用作操作项?

【问题讨论】:

  • 在下面stackoverflow.com/questions/32695710/…看我的回答。 IMO,您可以尝试使用图标或背景对项目进行分组(我的意思是项目 android:title="Communi...)
  • 它不起作用。我说的是操作栏的菜单。请在那里试一试。您可以通过创建一个新项目来轻松完成此操作,然后选择带有 FAB 的项目(在向导中)。它会为你创建一个菜单。
  • 有一个解决方法,但它并不漂亮,您可以添加如下项目&lt;item android:id="@+id/menu_divider" android:enabled="false" android:icon="@drawable/white_divider" android:title="" /&gt;
  • @BNK 它不起作用。它只是显示一个完整的空项目,没有任何可绘制的。我说的是菜单,而不是图标化的操作项。单击操作栏上的项目时出现的一个。我会放另一个截图来说明我的意思。
  • 这是我的截图drive.google.com/file/d/0B2HGUM4c0YwpdEFVSmlyMjIxc3M/…,你应该更新&lt;style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat" /&gt;

标签: android android-actionbar submenu android-toolbar divider


【解决方案1】:

好的,我找到了一个很好的解决方法,但我不确定样式是否应该是这种方式。这就是我所缺少的:

  1. 项目的背景位于微调器弹出窗口的背景之上,我不确定这是否是正确的放置方式。
  2. 我使用支持库的白色背景来弹出微调器。我认为应该有更好的方法让它变白。
  3. 我需要知道分隔线的正确样式是什么。现在我用了一个简单的
  4. 缺少操作栏项目样式。我只是用了一个简单的ImageView,我觉得应该不一样。
  5. 出于某种原因,在某些 Android 版本(可能是 Lollipop 及更低版本)上,项目的背景看起来是黑色而不是白色。
  6. 微调器有时可能会遇到 setOnItemSelectedListener 问题,不确定何时。

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>

【讨论】:

    【解决方案2】:

    现在:

    group1[ item0 item1 item2 ] group1[item3];
    

    改为:

    group1[ item0 item1] group1[item2 item3]
    

    组有一个divider; 喜欢那个群可以在item之间加一个divder

    如果divider不可用,请尝试background; 我从不使用menu; 这是我的猜测;

    【讨论】:

      【解决方案3】:

      你应该使用动作布局

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

      然后动作布局可以有带分隔符的文本视图。

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

      然后你可以在代码中添加点击监听

      【讨论】:

      • 你真的试过了吗?据我所知,actionLayout 仅适用于操作项,但不适用于它们的弹出菜单。
      【解决方案4】:

      这可以通过使用弹出窗口和列表视图来完成。在您的列表视图中,您可以有不同的视图类型,例如菜单项和分隔符。

      我列出了弹出窗口部分的代码:

          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);
      

      然后您可以使用以下方法显示弹出窗口:

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

      在列表视图的适配器中,您可以重写 getViewTypeCount() 和 getItemViewType() 以支持菜单项布局和分隔线布局,也可以添加您需要的任何视图类型。

      这是我的应用中的快照:

      【讨论】:

      • 哇,这看起来真不错。使用时有什么缺点或问题吗?可悲的是,我已经从这个问题继续前进,所以我不能在不检查的情况下接受它,但我会为此付出 +1 :)
      • 尚未发现问题。用户界面是从头开始制作的,所以一切都可以定制。我从 Android 4.0.3 测试到 Android 6.0,不包括较低版本的 API 级别设备。
      • 我正在尝试这个,但是“OptionListAdapter”在哪里我没有得到那个......或者你可以将上面的项目上传到 gethub 吗???
      • @TaritRay OptionListAdapter 是 ListView 适配器的子类,你可以给它任何你想要的名字。
      【解决方案5】:

      我是这样做的:

      参考截图:

      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 代码:

      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;
                  }
              });
          }
      

      希望这会对你有所帮助。

      【讨论】:

      • 不,我们需要在特定项目之间插入分隔线,而不是在所有项目之间。
      【解决方案6】:

      对我来说非常简单的解决方案:

      为背景定义一个可绘制对象:

      <?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>
      

      然后在 Styles 中使用背景:

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

      【讨论】:

      • 我没有尝试过,但我认为它会在每个项目之间画一个分隔线,而这篇文章更多的是关于在每次不低于的特定位置设置分隔线。
      • 我认为它不适用于“如何在特定菜单项之间添加分隔符?”的问题,但是对于需要为每个项目设置分隔符的人来说,它会起作用
      【解决方案7】:

      从 SDK 版本 28 开始,您可以使用 menu.setGroupDividerEnabled(boolean)。如果您使用的是 ContextMenu,这仅在 SDK 28+ 上受支持,但在 onCreateOptionsMenu() 中使用时,MenuCompat 提供向后兼容性。

      这将在每个不同的groupId 的操作之间添加一个分隔符,如下面的 0 和 1 所示:

      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);
      

      此处的文档:https://developer.android.com/reference/android/view/Menu#setGroupDividerEnabled(boolean)


      编辑:提问者要求的示例代码:

      这是我目前在我的应用程序中使用的代码,位于 RecyclerView 适配器中。它也应该适用于您的菜单实现。由于您是通过 XML 定义菜单,因此只要您引用菜单资源,下面的内容也适用于您。结果如下所示:

      覆盖onCreateContextMenu 或您菜单的相关onCreate.. 方法,如下所示:

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

      【讨论】:

      • 等等,现在有官方的方法了吗?你能分享一个 Github 示例吗?还有它的外观截图?还有没有办法自定义分隔线?
      • @androiddeveloper 我已经用我的应用中的示例更新了我的答案。请注意,这仅在 Android 9.0 及更高版本上兼容。我不相信有直接的方法来自定义分隔线,尽管您可以查看更多选项,例如子菜单。
      • 似乎也适用于onCreateOptionsMenu。在 XML 中是否也有可能,而不仅仅是在代码中?另外,为什么 setHeaderTitle 不能用于 onCreateOptionsMenu ?如果您使用支持库,我认为它不仅适用于 Android 9 及更高版本。至少根据我的测试,您可以使用 MenuCompat.setGroupDividerEnabled(menu,true)
      • @androiddeveloper 我快速浏览了菜单资源文档,似乎唯一的方法是引用菜单资源 R.menu.filename 并添加充气器中的标志以及 XML 中的 &lt;group&gt; 标签。此外,setHeaderTitle 仅适用于 ContextMenu,因为它没有附加到工具栏/应用栏(可能是 Google 的设计选择)。感谢您提供有关 MenuCompat 的信息,我已将其添加到答案中。
      • 我不明白。你说不可能通过 XML 做到这一点?另外,关于 MenuCompat,我没有看到您更新了答案的代码。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-14
      • 2016-04-04
      • 2016-03-04
      • 1970-01-01
      • 1970-01-01
      • 2015-12-08
      相关资源
      最近更新 更多