Have you ever needed to popup a ContextMenu by longclicking on some of the UI element (different than item in ListView) for example an ImageView instance? At first glance it seems that this can be acomplished quite easy but in reality there is one big problem: you have only one onContextItemSelected()
method in your activity that is called every time a context menu item is selected no matter which View requested the ContextMenu
via openContextMenu()
. This situation is not a problem if just one View
is poping a ContextMenu
but sometimes you need to popup ContextMenu
from several different views. Let me show an example from a real app:
In this layout I have 4 ImageViews. img1 is called "primary" and all other "secondary". By specification I had to create a functionality for secondary images that allows them to set them as primary. Also each image needed to have option to be deleted so I ended up to open ContextMenu
on long click for each of the images like this:
When user clicks on menu item onContextItemSelected(MenuItem item)
is called. The problem is that there is no way to determine which view opened the menu so you don't know to which element to apply the desired action. When you open context menu from ListView + Adapter there is the following method to obtain such info:
AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); ListView lv = (ListView) menuInfo.targetView.getParent();
but there is no way to do something similar when you use View that is not AdapterView
(like ImageView, TextView, etc…). There is no way to determine which View opened the menu because item.getMenuInfo()
returns null. It turns out that Android developers intentionally did not provided such information in order to discourage opening of ContextMenu
by arbitrary UI elements (more about this later in the post). Luckily there is a way to circumvent this limitation. If for example we want to have ImageView
with ContextMenuInfo
we have to:
1. Subclass ImageView
and override getContextMenuInfo() in order to provide additional info:
import android.content.Context; import android.util.AttributeSet; import android.view.ContextMenu; import android.view.View; import android.view.ContextMenu.ContextMenuInfo; import android.widget.ImageView; public class ImageViewWithContextMenuInfo extends ImageView { public ImageViewWithContextMenuInfo(Context context) { super(context); } @Override protected ContextMenuInfo getContextMenuInfo() { return new ImageViewContextMenuInfo(this); } public ImageViewWithContextMenuInfo(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public ImageViewWithContextMenuInfo(Context context, AttributeSet attrs) { super(context, attrs); } public static class ImageViewContextMenuInfo implements ContextMenu.ContextMenuInfo { public ImageViewContextMenuInfo(View targetView) { this.targetView = (ImageView) targetView; } public ImageView targetView; } }
Important things in above code are:
- overriding
getContextMenuInfo()
in order to return object of typeImageViewContextMenuInfo
- definition of
ImageViewContextMenuInfo
itself which essentially is just a container for reference to the View itself.
2. Change your layout XML to use your new view class:
You have to replace your ImageViews (only those which open context menu) with ImageViewWithContextMenuInfo using the full path to the class like
<com.ikratko.ogrelab.android.ImageViewWithContextMenuInfo android:layout_width="128dp" android:layout_height="128dp" android:id="@+id/img1" />
3. Get your view in onContextItemSelected():
public boolean onContextItemSelected(MenuItem item) { ImageViewWithContextMenuInfo.ImageViewContextMenuInfo menuInfo = (ImageViewWithContextMenuInfo.ImageViewContextMenuInfo) item.getMenuInfo(); ImageViewWithContextMenuInfo img = (ImageViewWithContextMenuInfo) menuInfo.targetView;
Now you can use img.getTag()
in order to get additional information (that you previously set with setTag()
).
I have prepared sample project that demonstrates how to wire the things: ContextMenu4All
It has simple layout: 3 image "slots" showing different images. On long click context menu is open that allows you to select new image.
- As some people suggest it is not good idea to open context menus using arbitrary views because there is no visual cue that there is such functionality attached. Please read the comment from CommonsWare. However although I totally agree with him I think that there are some cases that using such approach is totally justified. For example in my real app I used it because that it is an inhouse app that will be used by users trained to work with it so there will be no confusion how to use it. If I had to provide additional UI elements for "delete" and "make primary" I would run out of space -- there is simply not enough screen real estate to put additional elements. Also if the missing cue is the problem -- developers can provide it. Small icon of taping finger on top of the image in the bottom right corner (using FrameLayout) will be sufficient. Even better -- it can be animated.