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.
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.
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).
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.
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;
}
Overriding getView is the right approach, and you're on the right track thinking that recycling views will complicate things (if you are in fact recycling views).
Since there's no getOnClickListener methods you'll just have to set a new OnClickListener for the recycled views as well as the uninflated ones (if you don't want to make a custom view that implements getOnClickListener).