如何为弹出菜单项设置ID,并将其作为视图ID找到?

6

背景

我们的自动化测试使用视图的ID来点击它们,因此我们尽可能地添加ID。

问题

对于弹出菜单,有时需要动态填充它们,但我发现即使我为每个项目添加了ID,也无法找到ID并使用。甚至使用DDMS的“dump view hierarchy for UI automator”功能,也显示弹出菜单中没有任何视图具有ID。

我尝试过的

这是我使用的示例代码,尝试为单个菜单项设置ID。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    View v=findViewById(R.id.button);
    v.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(final View v) {
            PopupMenu popupMenu = new PopupMenu(MainActivity.this, v);
            final Menu menu = popupMenu.getMenu();
            menu.add(0, R.id.myMenuItem, 0, R.string.app_name).setOnMenuItemClickListener(new OnMenuItemClickListener() {
                @Override
                public boolean onMenuItemClick(final MenuItem item) {
                    return false;
                }
            }) ;
            popupMenu.show();
        }
    });
}

请注意,id 在 "ids.xml" 文件中声明,如下所示:
<item name="myMenuItem" type="id"/>

这就是DDMS工具向我展示的内容:

enter image description here

问题

为什么这段代码不能按预期工作(即菜单项视图有一个id)?我该如何使其中的视图具有id?动态创建菜单项的正确方法是什么?


请问您能详细说明一下“不按预期工作”的意思吗? - LightYearsBehind
@haike00 我之前写过:即使我设定了id,它还是没有id。现在我会放上更好的代码和截图。 - android developer
很抱歉,我还是不明白你想做什么。根据我的理解,你想将R.id.myMenuItem设置为你的MenuItem的id。因此,在你的OnMenuItemClickListener()监听器中,当你调用item.getItemId()时,你将能够获取与R.id.myMenuItem等效的id。这难道不是你想要的吗? - LightYearsBehind
正如我在“背景”部分中所写的那样,我需要视图本身具有此ID,以便自动测试能够找到该视图并单击它。这对于工具栏菜单项很有效,但对于弹出式菜单则不是。我不需要应用程序本身上的ID,因为我已经有了对菜单项的引用。我需要它来进行自动化测试。 - android developer
我刚刚进行了测试,即使是 "Toolbar" 也无法给我一个合适的资源ID。不管怎样,您传递到 Menu.add(0, x, 0, 0) 的第二个参数是用于项目ID的。如果需要,您必须强制循环遍历菜单项视图并将项目ID设置为其视图ID。另一种选择是考虑使用索引ID来实现您想要做的事情。 - LightYearsBehind
对于工具栏测试,您必须确保视图是可见的。尝试将showAsAction设置为“always”。至于弹出式菜单,请展示一个您所说的例子。 - android developer
1个回答

1

好的,这并不是对问题的解答。可以将其视为替代方案,以取代PopupMenu以实现所要求的内容。

PopupMenu

在查阅了PopupMenu及其源代码的文档后,我终于明白PopupMenu不是一个允许自定义的实现(对于评论中的误解,向PO道歉)。

替代方案

ListPopupWindow作为一种替代方案,具有以下优点:

  1. 它与PopupMenu共享父级-ListView
  2. 它非常灵活,允许定义自定义布局的自定义适配器。
  3. 它还允许像PopupMenu一样在运行时创建,并允许将资源ID附加到项目视图上。

实现

首先,让我们为弹出项定义一个自定义布局(popup_item_view.xml)。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">
    <TextView
        android:id="@+id/popup_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

接下来,定义一个自定义的Adapter类来操作布局。
package com.example.popupmenu;

import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class PopupAdapter extends BaseAdapter {
    public static class Item {
        public final int id;
        public final String title;

        public Item(int id, @NonNull String title) {
            this.id = id;
            this.title = title;
        }
    }

    private List<Item> mItemList = new ArrayList<>();

    public PopupAdapter(@NonNull Item[] items) {
        mItemList.addAll(Arrays.asList(items));
    }

    @Override
    public int getCount() {
        return mItemList.size();
    }

    @Override
    public Item getItem(int position) {
        return mItemList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        final Item item = getItem(position);
        if (convertView == null) {
            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
            convertView = inflater.inflate(R.layout.popup_item_view, parent, false);
        }
        convertView.setId(item.id);
        TextView titleView = (TextView) convertView.findViewById(R.id.popup_text);
        titleView.setText(item.title);
        return convertView;
    }
}

最后,将 PopupMenu 代码替换为以下内容。
PopupAdapter.Item[] items = {
    new PopupAdapter.Item(R.id.popup_item_1, "item 1"),
    new PopupAdapter.Item(R.id.popup_item_2, "item 2")
};

ListPopupWindow popup = new ListPopupWindow(MainActivity.this);
popup.setAnchorView(view);
popup.setAdapter(new PopupAdapter(items));
popup.setModal(true);
popup.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        // do something
    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {
       // do something
    }
});
popup.show();

希望这有所帮助。

这很有趣。它是否也具有选择弹出窗口放置位置(在视图上方或下方)的能力?无论如何,希望很快就能检查它,然后打勾确认答案。现在,先点个赞。 :) - android developer
我认为它是在运行时由ListPopupWindow决定的,如果它感觉下方的锚定View没有足够的空间,它就会将其绘制在其上方。试一试。=) - LightYearsBehind
好的,你可以使用 setDropDownGravity(gravity) 来设置它。 - LightYearsBehind
不,不能使用 setDropDownGravity(gravity) - LightYearsBehind
我不确定它是否有效。我的意思是这个 http://i.stack.imgur.com/kojaX.png 和这个 http://i.stack.imgur.com/sGO84.png 。一个在下面,一个在目标视图的顶部。 - android developer
也许在这种情况下,setVerticalOffset(offset)或者setHorizontalOffset(offset)更适合您。您可以参考ListPopupWindow获取更多详细信息。 - LightYearsBehind

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接