Identifying the view selected in a ContextMenu (in onContextItemSelected() method)
Posted / Публикувана 2011-07-12 in category / в категория: Android
|
Warning: count(): Parameter must be an array or an object that implements Countable in /home/bolyarco/www-ikratko/ogrelab/wp-content/plugins/microkids-related-posts/microkids-related-posts.php on line 645
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.
|
March 27th, 2012 at 07:52:16
This has got to be the only website I've found in the past few days that so much as mentions this problem never mind has a working solution and an explanation for why it happens.
Thanks for taking the time to make this.
April 10th, 2012 at 05:24:56
Thank you a lot! You have save my time!
May 13th, 2012 at 09:38:50
thanks a lot for the solution.Can you please guide how can i use it for table row. I have a table and i want to have the Context menu for each table row. how it that look like.appreciate your help. cheers!!
May 13th, 2012 at 09:45:27
@Zolf
I guess that you should subclass TableRow as I did with the ImageView above and then use your subclass in the layout.
BTW, probably you have good reason to use table but I must suggest that you use ListView instead of table if you need to click on rows. ListView will give you additional functionality too like scrolling, etc…
May 13th, 2012 at 10:13:09
thanks for your feedbacks. In my table I have 4 cols -- sr.no. (TextView),name(TextView),qty(TextView),delete (button).now the reason for using a context menu was to get rid of the delete button from the table col and put it into the context menu. hence give more room to the other 3 cols. But as you recommended to use ListView in place of Table,now my question is can i have 3 cols in a list view and let users add rows.if you know any tutorial on this can you please refer me to it
May 13th, 2012 at 10:38:16
Here you can find basic example:
http://stackoverflow.com/questions/4407865/how-to-customize-listview-row-android
Generally, you need to create separate layout file for your ListView row, then subclass BaseAdapter or ArrayAdapter and override getView() method (where you inflate and populate your row layout).
For more detailed tutorial(s) I strongly recommend this book: The Busy Coder's Guide to Android Development (http://commonsware.com/Android/). IMHO it is the best android book I've seen.
May 13th, 2012 at 10:49:41
cheers !!
May 31st, 2013 at 01:19:28
Pavan @(pavanh)
Very nice tutorial with efficent and detailed explaination, you can also check
this one context menu
April 15th, 2014 at 05:03:06
I searched a solution for a similar problem for hours. But I always ended on those ListViews…
Thanks for this great article !