在ListView项目内使用按钮触发弹出菜单

15

菜单类型

首先,让我概述一下上下文菜单和弹出菜单之间的区别(摘自这里):

  • 弹出菜单是模态菜单,固定在视图上。

  • 上下文菜单[特指浮动上下文菜单]提供影响UI中特定项目或上下文框架的操作。您可以为任何视图提供上下文菜单,但它们通常用于ListView、GridView或其他视图集合中,在这些视图集合中,用户可以对每个项目执行直接操作。

根据文档的指导,它们的用途有所区别: - 弹出菜单:"为与特定内容相关的操作(例如 Gmail 的电子邮件标题)提供溢出样式菜单..." - 上下文菜单:"用于影响所选内容的操作..."

我想要的是PopupMenu的可视外观和交互方式。

我的设置(和目标)

我有一个 ListView,其中每个项目都有一个位于右侧的按钮。

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

在保留每个ListItem的onItemClick的同时,我想利用按钮触发一个PopupMenu

PopupMenu中的操作在my_menu.xml中列出,因此菜单的每个实例之间唯一的区别是所点击的项目。

我尝试过的方法

我尝试在我的ListAdapter中覆盖getView(),为每个按钮添加一个OnClickListener,如这篇文章所述。结果不一致;并非所有点击都被注册(可能由于回收 - 我还没有尝试过这个修复),但总体上,考虑到为ListView指定上下文菜单的简便性,这种方法似乎有点奇怪。

我还尝试添加上下文菜单,这非常简单,只是它仅在长按列表项时触发。

getView()方法是正确的方法吗?还是有更简单的方法?

在我的listview中,我为每个item都有一个小溢出按钮,类似于Play Music中的每个播放列表中的每个曲目。当你点击溢出按钮时,它们会显示每个项目的PopupMenu。长按不起作用。

我想要那样,但不确定该怎么做?菜单页面只详细说明如何为单个视图启用弹出菜单。

registerForContextMenu(ListView lv)是我看到的唯一可以应用于整个ListView的东西,但它似乎仅在长按整个列表时起作用。有没有办法将该事件钩接到列表项中特定视图的单击事件上(同时仍保持列表项的其余部分的单击事件)?

我尝试在getView()中设置一个onClickListener,如此处所述,但对于PopupMenu来说感觉有点奇怪,而且并非每次都能注册所有点击(它始终不稳定)。

更新 - 再次覆盖getView()

当我创建StacksCursorAdapter时,我传递了一个StackViewFragment的实例,它被存储为一个字段(因此它可以分配给视图作为点击监听器)。

ViewHolder是一个具有public final ImageView miniOverflow的类

StacksCursorAdapter(扩展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(扩展自ListFragment,实现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>

目前问题在于我有时需要点击多次才能让点击(按钮)生效。这与视图回收无关,因为没有滚动。

结论(由eskimoapps.com提供的解决方案被标记为正确)

所以多次点击并没有问题,只是我的触摸目标不在我期望的位置-我用于迷你溢出的图像资产权重偏右(使得三个点靠近触摸目标的右边缘),这意味着我错过了一半的时间(面对掌声)。

如上所示,重写 getView() 似乎可以正常工作,实际上,getView() 可以进一步修改,仅在需要时设置 OnClickListener(但绝对需要更新标签对于每个视图,而不仅仅是新创建的视图):

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

你能描述一下你是如何添加监听器的吗?这里的监听器对象是什么?holder.miniOverflow.setOnClickListener(listener); - Mahdi Giveie
@mahdi 它将是类型为 View.OnClickListener - ataulm
2个回答

3

重写getView是正确的方法,如果你确实在回收视图,那么认为回收视图会使事情复杂化的想法是正确的。

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


如果一个视图已经被回收,我能假设它仍然有一个OnClickListener,所以只为新的视图设置它?这听起来像是一个我应该尝试的注释.. :P - ataulm
它仍然有一个 OnClickListener,只是不是正确的,这就是为什么你需要设置一个新的。 - eski
如果被点击的视图在位置上并不重要,那么就没有问题了,并且您无需为已回收的视图设置新的 OnClickListener。 - eski
我的答案中会注意到一种羞怯的认识,关于不一致的点击。感谢您的帮助 - 覆盖getView()函数正如我所希望的那样工作(现在我知道了另一个“问题”!) - ataulm
1
很好,我在寻找您的代码中的问题时遇到了困难。 - eski
显示剩余2条评论

1
如果我今天想要做到这一点,我会使用HolderView模式。
HolderView模式需要一个自定义的视图/视图组(一个扩展View/Viewgroup类的根列表/网格项布局,并带有三个标准的视图构造函数)。
总体思路是适配器不直接负责更新视图的内容;相反,它将为视图提供更新自身所需的所有必要信息:
@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);
    }

}

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