5

Background

Out automatic tests use views' ids to be able to click on them, so we add ids whenever possible.

The problem

For popup menus, sometimes it's needed to populate them dynamically, but as I've found, even when I do add id for each item, the id isn't found, and can't be used. Even using DDMS's feature "dump view hierarchy for UI automator" shows that no view in the popup menu has an id.

What I've tried

This is a sample code of what I use, to try to set an id for the a single menu item.

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

Note that the id is declared in "ids.xml" file, as such:

<item name="myMenuItem" type="id"/>

And this is what DDMS tool shows me :

enter image description here

The question

How come this code doesn't work as expected (meaning have an id for the menu item view) ? What can I do to make the views in it to have ids? What is the correct way to add ids for menu items that are created dynamically ?

android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • Can you please elaborate more on "doesn't work as expected"? – LightYearsBehind Mar 16 '16 at 09:47
  • @haike00 As I wrote : it doesn't have an id , even though I've set it to have an id. I will now put better code and screenshot – android developer Mar 16 '16 at 10:55
  • I am afraid I still don't get what you are trying to do. From what I understand, you are trying to set `R.id.myMenuItem` as your `MenuItem`'s id. Therefore, in your `OnMenuItemClickListener()` listener, when you do a `item.getItemId()` and you would be able to retrieve an id equivalent to `R.id.myMenuItem`. Isn't that what you want? – LightYearsBehind Mar 17 '16 at 00:28
  • @haike00 As I wrote in the "background" section, I need the view itself to have this id, so that the automatic test would be able to find the view and click on it. This works fine for toolbar menu items, but not for popup menus. I don't need the id on the app itself because I already have a reference to the menu item. I need it for the automatic testing. – android developer Mar 17 '16 at 06:22
  • I just did a test, even `Toolbar` won't give me a proper resource-id. Anyway, the second argument you are passing into `Menu.add(0, x, 0, 0)` is meant for the item ID. If needed, you have to forcefully loop through the menu item views and set the item ID as its view ID. An alternative is to consider using the index ID to achieve what you want to do. – LightYearsBehind Mar 17 '16 at 07:57
  • For the toolbar test, you have to make sure the views are shown. Try setting showAsAction to "always". About the popup menu, please show an example of what you mean. – android developer Mar 17 '16 at 09:46

1 Answers1

1

Alright, this is by no means an answer to the problem described in the question. Look at it as an alternative to replace PopupMenu in order to achieve what was asked.

No PopupMenu

After digging through the documents for PopupMenu and its source code, I finally come to understand that PopupMenu is not an implementation that would allow customization (my apology to the PO for the misconception in the comments).

An alternative

As an alternative, a ListPopupWindow is a preferred choice to create a popup menu with the following reasons:

  1. It shares the same parent with PopupMenu - ListView.
  2. It is flexible, allowing custom Adapter to be defined with custom layout.
  3. It also allows run-time creation like PopupMenu does, and allows attaching resource id to the item view.

Implementation

First of all, let's define a custom layout for the popup item (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>

Next, define a custom Adapter class to manipulate the layout.

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

Finally, replace the PopupMenu code with this.

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

Hope this helps.

LightYearsBehind
  • 1,686
  • 18
  • 24
  • This is interesting. Does it also have the ability to choose where to put the popup (on top or below the view ) ? Anyway, hope to check it out soon and then tick the answer. For now, have a +1 . :) – android developer Mar 18 '16 at 07:59
  • I think it is decides (by `ListPopupWindow`) during run-time, if it feels there is not enough room below the anchored `View`, it will just draw it on top of it. Try it out. =) – LightYearsBehind Mar 18 '16 at 08:54
  • Alright, you may be able to set it with `setDropDownGravity(gravity)`. – LightYearsBehind Mar 18 '16 at 09:16
  • Nope, not with the `setDropDownGravity(gravity)`. – LightYearsBehind Mar 18 '16 at 13:28
  • I'm not sure it will work. I meant this http://i.stack.imgur.com/kojaX.png vs this: http://i.stack.imgur.com/sGO84.png . one is below, and one is on top of the target view. – android developer Mar 18 '16 at 15:51
  • Perhaps `setVerticalOffset(offset)` or `setHorizontalOffset(offset)` suit you better in this case. You may refer to [`ListPopupWindow`](http://developer.android.com/reference/android/widget/ListPopupWindow.html) for more details. – LightYearsBehind Mar 19 '16 at 01:36