15

Types of Menu

Let me start by outlining the distinction between Contextual Menus and Popup Menus (taken from here):

  • A PopupMenu is a modal menu anchored to a View

  • A contextual menu [I'm talking specifically about floating context menus] offers actions that affect a specific item or context frame in the UI. You can provide a context menu for any view, but they are most often used for items in a ListView, GridView, or other view collections in which the user can perform direct actions on each item.

As guidance, the docs distinguish between their uses: - PopupMenu: "Providing an overflow-style menu for actions that relate to specific content (such as Gmail's email headers..." - ContextualMenu: "For actions that affect selected content..."

I'm after the visual look and interaction of the PopupMenu.

My setup (and goal)

I have a ListView where each item has a button on the right.

--------------------
| Item 1        [ ]|
--------------------
--------------------
| Item ...      [ ]|
--------------------
--------------------
| Item n        [ ]|
--------------------

while retaining the onItemClick for each ListItem, I'd like to leverage the button to trigger a PopupMenu.

The actions in the PopupMenu are listed in my_menu.xml, so the only difference between each instance of the menu is the item that it was clicked for.

What I've tried

I've tried overriding getView() in my ListAdapter to add an OnClickListener for each button, as per this post. The result was inconsistent; not all the clicks were registering (perhaps due to recycling - I haven't tried this fix yet) but in general the approach seemed squiffy, considering the ease of specifying a context menu for a listview.

I've also tried adding a contextual menu, which was dead easy, except that it's only triggered when the list item is long-pressed.

Is the getView() method the way to go or is there something easier?

In my listview, I've a mini overflow button for each item, similar to each track in playlist for Play Music. They display a PopupMenu for each item there, when you click on the overflow button. Long-press does nothing.

I'd like that but not sure how? The menu page only elaborates how to enable a popup menu for individual views.

registerForContextMenu(ListView lv) is the only thing I've seen that I can apply to an entire ListView, but that seems to only work on long-presses of the entire list. Is there a way to hook that event to a click of a specific view in a list item (whilst still maintaining the list item click for the rest of the list item)?

I've tried setting an onClickListener in getView() as described here but it feels squiffy for PopupMenu, and not all clicks were registering every time (it was consistently inconsistent).

Update - with getView() overridden again

When I create the StacksCursorAdapter, I pass an instance of StackViewFragment which is stored as a field (so it can be assigned to views as the click listener).

ViewHolder is a class with public final ImageView miniOverflow

StacksCursorAdapter (extends SimpleCursorAdapter):

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    View row = super.getView(position, convertView, parent);
    ViewHolder holder = (ViewHolder) row.getTag();

    if (holder == null) {
        holder = new ViewHolder(row);
        row.setTag(holder);
    }
    holder.miniOverflow.setTag(R.id.tag_stack_position, position);
    holder.miniOverflow.setOnClickListener(listener);

    return row;
}

StackViewFragment (extends ListFragment, implements View.OnClickListener):

@Override
public void onClick(View v) {
    Log.d(TAG, "mini overflow clicked");
    Log.d(TAG, "position:::::" + v.getTag(R.id.tag_stack_position));

    PopupMenu popup = new PopupMenu(getActivity(), v);
    MenuInflater inflater = popup.getMenuInflater();
    inflater.inflate(R.menu.menu_stackview_listitem, popup.getMenu());
    popup.show();
}

stack_list_item.xml:

<?xml version="1.0" encoding="utf-8"?>
<!-- a single row item for a Stacks listview -->
<RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:descendantFocusability="blocksDescendants"
        >

    ...
    <other views>
    ...


    <ImageView
            android:id="@+id/moreoverflow_btn"
            android:focusable="false"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:alpha="0.4"
            android:scaleType="fitCenter"
            android:src="@drawable/ic_menu_moreoverflow_black"
            android:layout_alignParentRight="true"
            />
</RelativeLayout>

The issue at this junction is that I have to click several times occasionally to get the click to register (on the button). This is without scrolling, so it's not related to view recycling.

Concluding (solution by eskimoapps.com marked as correct)

So there was no issue with the multiple clicks except that my touch target was not in the place I was expecting - the image asset I was using for the mini overflow is weighted to the right (such that the three dots are near the right edge of the touch target) meaning that I was just missing it half the time (facepalm).

Overriding getView() as I showed above seems to work fine, and in fact, getView() can be modified even further, by setting the OnClickListener only if it's needed (but definitely, updating the tag needs to be done for every view, not just the newly created ones):

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    View row = super.getView(position, convertView, parent);
    ViewHolder holder = (ViewHolder) row.getTag();

    if (holder == null) {
        holder = new ViewHolder(row);
        row.setTag(holder);
        holder.miniOverflow.setOnClickListener(listener);
    }
    holder.miniOverflow.setTag(R.id.tag_stack_position, position);

    return row;
}
4

2 回答 2

3

覆盖 getView 是正确的方法,并且您认为回收视图会使事情复杂化(如果您实际上正在回收视图)。

由于没有 getOnClickListener 方法,您只需为回收视图和未膨胀视图设置一个新的 OnClickListener(如果您不想创建实现 getOnClickListener 的自定义视图)。

于 2013-06-30T21:40:53.330 回答
1

如果我今天想这样做,我会使用HolderView模式。

HolderView 模式需要一个自定义视图/视图组(一个扩展视图/视图组的类,它是列表/网格项目布局的根,具有 3 个标准视图构造函数)。

大意是适配器不负责直接更新视图的内容;相反,它将为视图提供更新自身所需的所有信息:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        convertView = createView(parent);
    }

    updateView((GalleryItemView) convertView, position);

    return convertView;
}

private View createView(ViewGroup parent) {
    LayoutInflater inflater = LayoutInflater.from(parent.getContext());

    return inflater.inflate(R.layout.gallery_item_view, null, false);
}

private void updateView(GalleryItemView view, int position) {
    GalleryElement item = getItem(position);

    view.updateWith(position, item, listener);
}

在我看来:

public class GalleryItemView extends ImageView {

    public GalleryItemView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    void updateWith(int position, final GalleryElement item, final GalleryAdapter.GalleryItemListener listener) {
        if (position % 2 == 0) {
            setBackgroundColor(getColor(android.R.color.holo_orange_dark));
        } else {
            setBackgroundColor(getColor(android.R.color.holo_orange_light));
        }

        setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                listener.onGalleryItemClicked(item.id);
            }
        });
    }

    @ColorInt
    private int getColor(@ColorRes int colorRes) {
        return getResources().getColor(colorRes);
    }

}
于 2013-12-23T09:04:52.857 回答