I have an Android ListView with a bunch of rows. Each row contains an image and some text. If I click on the image of a row, I want to perform action A, if I click on any other place in the row, I want to perform action B. Both of these actions need the id of the ListView item to work.
So far, for action B, I simply used listview's onItemClick, where I got the id from the parameter. However, as far as I know, the only way to find out on which view in the row itself the user clicked, is to add an onClick event handler to the ImageView that triggers action A. In that case, I only get the View that was clicked, though - not the id of the row that the ImageView is in.
My question is - how do I go from a View object to getting the id of the parent row within the ListView?
The onItemClickListener method has an onClick interface already with the view and position passed into it: You can get check which view has been clicked by doing a switch statement and performing a dedicated action when the specific view has been clicked.
E.g.
listView.setOnItemClickListener(new android.widget.AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view,int position, long id) {
String item = listView.getItemAtPosition(position);
Toast.makeText(this,"You selected : " + item,Toast.LENGTH_SHORT).show();
}
});
as shown in How to create listview onItemclicklistener
You'll see that in the onItemClick interface it has the View view as a parameter and int position as a parameter, perform the action inside the listener using both of those parameters. Like i mentioned before you can use a switch statement.
listView.setOnItemClickListener(new android.widget.AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view,int position, long id) {
String item = listView.getItemAtPosition(position);
switch(view.getId()) {
case R.id.imageView:
//perform action for imageView using the data from Item (if you have used an MVC pattern)
break;
// you can do this for any widgets you wish to listen for click actions
}
}
});
Update: All credit for this goes to wwfloyd in How to know which view inside a specific ListView item that was clicked
#Override
public View getView(final int position, View convertView, final ViewGroup parent) {
// Check if an existing view is being reused, otherwise inflate the view
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.one_line, parent, false);
}
// This chunk added to get textview click to register in Fragment's onItemClick()
// Had to make position and parent 'final' in method definition
convertView.findViewById(R.id.someName).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
((ListView) parent).performItemClick(v, position, 0);
}
});
// do stuff...
}
in your adapter apply a click listener to the element and then trigger the interface method, so that you can identify the registered widget that has been clicked and then in your onItemClick do:
public void onItemClick(AdapterView adapterView, View view, int position, long id) {
long viewId = view.getId();
switch (viewId) {
case R.id.someName:
//widget has been clicked
break;
default:
//row has been clicked
break;
}
}
if you aren't using tag, then you can
int itemId=...;
OnClickListener customOnClick=...;
imageView.setTag(itemId);
imageView.setOnClickListener(customOnClick);
and inside your custom OnClickListener
int itemId = (Integer) view.getTag();
note that you can set "any" Object as tag, so you may even set your whole custom object, not only id
#Override
public void onItemClick(AdapterView<?> arg0, View arg1,
int position, long id)
in this method View arg1 gives you Item view and you can find the item content by this arg1.findViewById(R.id.content);. Here position gives you the item position of list view
You can set click listeners for each view in a row from the getView() method in the adapter.
You can impliment it in getView() method of adapter of listview.
Related
Well, I have a horizontal LinearLayout, with items of a custom class.
What I want: Change background color of the item i've clicked (like if it's selected). If user clicks other item, the first one changes again to original color and the new one changes to "selected color"
What is my problem: This works fine when all items of the listView fit in the screen ( in this case 3 items). If there are more items, let's say 7, if user clicks item number 0, it changes color, but if user scrolls to the last item and clicks item number 6, it changes color, but item 0 doesn't change. If both items are visible, it works fine.
this is the code of my onItemClickListener.
listView.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(it.sephiroth.android.library.widget.AdapterView<?> parent, View view, int position, long id) {
for(int i=0;i<listView.getCount();i++){
try{
getViewByPosition(i, listView).setBackgroundResource(R.drawable.skin);
listView.getChildAt(i).setBackgroundResource(R.drawable.skin);
}
catch(Exception e){
System.out.println(e.toString());
}
}
view.setBackgroundResource(R.drawable.skin_selected);
}
});
why not use the adapter for the modification?I presume you are using a custom adapter?
in You Adapter
public int selectedItem;
.
//Rest of the stuff
.
.
#Override
public View getView(final int position, View convertView, final ViewGroup parent) {
if (null == convertView) {
LayoutInflater inflater = (LayoutInflater) _context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.list_item, parent, false);
}
if(selectedItem==position){
convertView.setBackgroundResource(R.drawable.skin_selected);
}else{
convertView.setBackgroundResource(R.drawable.skin);
}
return convertView;
}
and your onclick listener becomes :
listView.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(it.sephiroth.android.library.widget.AdapterView<?> parent, View view, int position, long id) {
((NameOfYouAdpater)listView.getAdapter()).selectedItem=position;
((NameOfYouAdpater)listView.getAdapter()).notifyDataSetChanged();
}
});
And I noticed you are using a custom list view in itself.Maybe look into the implementation of the library to check for interference?
EDIT:the cause of your issue is most probably dude to view recycling.The background colour does not get updated because everytime the list item is outside your ViewPort,the view is recycled.
I am trying to figure out the solution for the following listed problem. I have a Listview generated using Simpleadapter. When I click on a row in the listview I want to make a Layout with an id colorful as visible. I am able to do this. But my problem starts here. When I click on another row say row number 5 the colorful layout is visible, but the layout is also visible for the previously clicked row too. What I want to do is make the layout colorful visible for only the clicked row (i.e it should be visible for only one row at a time i.e is currently clicked row and hidden for all the remaining rows) and the layout should get invisible for the previously clicked rows. I tried doing with viewholder but it doesn't help. My code snippet is below. Guide me step by step as I am very new to Android.
final BaseAdapter k=new SimpleAdapter(getActivity(),val,R.layout.mytaskdata,new String[]{"sname","heading","desc","id","path","receiver","sender"},new int[]{R.id.textView1,R.id.textView2,R.id.textView3,R.id.hide1,R.id.hide2,R.id.hide3,R.id.hide4})
{
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
final View v = super.getView(position, convertView, parent);
TextView myname=(TextView)v.findViewById(R.id.textView1);
TextView mydes=(TextView)v.findViewById(R.id.textView2);
TextView mytopic=(TextView)v.findViewById(R.id.textView3);
ImageView edit=(ImageView)v.findViewById(R.id.ImageView03);
sent.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> arg0, View arg1,
int arg2, long arg3) {
// TODO Auto-generated method stub
RelativeLayout r=(RelativeLayout)arg1.findViewById(R.id.colorful);
// r.setVisibility(arg1.VISIBLE);
int temp=sent.getCheckedItemPosition();
Log.i("itemposition",""+temp);
Toast.makeText(getActivity(),"pos"+arg2+"hii"+positionThatSaysHi,1000).show();
if(arg2!=positionThatSaysHi)
{
r.setVisibility(arg1.VISIBLE);
positionThatSaysHi = arg2;
notifyDataSetChanged();
}
else
{
r.setVisibility(arg1.GONE);
notifyDataSetChanged();
}
});
I would suggest modifying the OnClickListener to just record the selected row, then call notifyDataSetChanged(). This will cause the ListView to redraw its items by calling the adapter. Therefore, you just need to check this value in getView() to determine whether the "colorful" view should be visible or not.
So the updated code would be something like:
#Override
public View getView(final int position, View convertView, ViewGroup parent)
{
final View v = super.getView(position, convertView, parent);
TextView myname=(TextView)v.findViewById(R.id.textView1);
TextView mydes=(TextView)v.findViewById(R.id.textView2);
TextView mytopic=(TextView)v.findViewById(R.id.textView3);
ImageView edit=(ImageView)v.findViewById(R.id.ImageView03);
RelativeLayout r = (RelativeLayout)v.findViewById(R.id.colorful)
r.setVisibility(position == positionThatSaysHi ? View.VISIBLE : View.GONE);
sent.setOnItemClickListener(new OnItemClickListener()
{
#Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3)
{
positionThatSaysHi = arg2;
notifyDataSetChanged();
}
});
}
That way, you ensure that only one view will be highlighted (while also simplifying your code).
Take advantage of choice mode in ListView ( see setChoiceMode()), and set it to CHOICE_MODE_SINGLE.
Use the selection state to toggle the visibility of your colorful layout. If possible, the easiest way to do this is to have the colorful bits in a selector drawable, with selected=true. That way it will show automatically, and you don't have to worry about hiding and showing views.
Question
I have a listView inside a DialogFragment and I want to fire certain callbacks only when certain particular items inside a row are fired. How can I do that?
Basically, I want to do something like this
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final int viewId = view.getId();
if ((viewId == R.id.textView1) || (viewId == R.id.textView2)) {
// do something...
}
which I can't. Read further if you don't know why.
What I tried
I tried to look into the documentation, but the OnItemClickListener callback doesn't offer as a parameter the exact clicked view (the View you can see in the signature is the whole row).
Also, I tried to set a simple onClick callback on the single view in the adapter, but this overrides the listSelector and other behavior a list should have. Reading in the documentation, I found it's explicitly written that we should set callbacks via the onListItemClick(...) method (not via onClick(...)), so I'm looking for a way to do that, using this method, not to override any default list behavior.
I was trying to get this done by working on the xml. To my surprise, I found that if I set a view android:clickable property to true, the onListItemClick callback won't fire (I thought it was the opposite),
so a partial solution would be to set to android:clickable=true every view in the row apart from the one I want to fire the callback, but that is not a solution because if the user clicks where there is padding or white space, the callback will fire. Also, I found that if I set the parent of the row's view to android:clickable=true and the child views I want to handle with the callback to android:clickable=false, this won't work, because apparently the property is not overwritten.
EDIT Sorry for the really bad title this question had before, I didn't even noticed I submitted the question.
new Answer, hope I understood now :)
In your adapters getView, attach an OnClickListener to any view in your layout you want to fire. (more pseudocode)
public class Adapter extends ArrayAdapter<XYZ> {
private int resource;
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView==null) convertView = ((LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(this.resource, parent, false);
((Button)convertView.findViewById(R.id.YOUR_BUTTON_IN_LAYOUT)).setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
DOSTUFF();
}
});
return convertView ;
}
}
old Answer:
The position indicates where you are in the list (pseudocode).
listView.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> arg0, View arg1, final int position,long arg3) {
YOUR_ITEM_BACKED_BY_ADAPTER item = listView.getItemAtPosition(position);
if(item==THE_FIRST_ITEM_IN_LIST) doSomething();
else if(item == THE_LAST_ITEM_IN_LIST) doSomethingElse();
}
});
You can set listeners for other views inside the adapter's getView
public class MyAdapter extends ArrayAdapter<MyItem> implements View.OnClickListener {
public View getView(int position, View convertView, ViewGroup parent) {
// setup the converView inflating it, for simplicity I've removed that code
MyItem item = getItem(position);
text1 = (TextView)convertView.findViewById(R.id.text1);
text2 = (TextView)convertView.findViewById(R.id.text2);
text1.setOnClickListener(this);
// pass the item to use when clicked
text1.setTag(item);
text2.setOnClickListener(this);
text2.setTag(item);
}
public void onClick(View v) {
MyItem item = v.getTag();
switch (v.getId()) {
case R.id.text1:
download(item);
break;
case R.id.text2:
upload(item);
break;
}
}
}
Instead of hardcoding action (eg download) inside the adapter you can pass to it an interface and for example the calling activity can implement that interface
I have 2 ListView on a single fragment and I wonder if I can set for both same class that implements AdapterView.OnItemClickListener.
I mean, Android Documentation says:
public abstract void onItemClick (AdapterView<?> parent, View view, int position, long id)
Added in API level 1
Callback method to be invoked when an item in this AdapterView has been clicked.
Implementers can call getItemAtPosition(position) if they need to access the data associated with the selected item.
Parameters
parent The AdapterView where the click happened.
view The view within the AdapterView that was clicked (this will be a view provided by the adapter)
position The position of the view in the adapter.
id The row id of the item that was clicked.
So I suppose that I can choose different ListView that invoked onClick by Parent (AdapterView where click happened)..
Now how can I identify ListView? is there a way to swicth / case? or I need to create different class (can also be anonymous I know) that implements onItemClickListener and set to differents ListView differents AdapterView.onItemClickListener?
Ok I resolved:
private class myClass implements AdapterView.OnItemClickListener {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
// TODO Auto-generated method stub
switch(parent.getId()) {
case R.id.listview1:
break;
case R.id.listview2:
break;
}
}
}
As the title says I want to know the exact position of the item when I click on a view that is inside the item.
Suppose I have the following code within the getView() method from ArrayAdapter:
...
holder = new ViewHolder ();
holder.iconAction = (ImageView)convertView.findViewById (R.id.download_item_iconAction);
holder.iconAction.setOnClickListener (new View.OnClickListener(){
#Override
public void onClick (View v){
//Item X is clicked
}
});
...
Within onClick() I know the view that is clicked, v, but I don't know the position of the item.
Let's do a trick. I'm going to save the position in a ViewHolder when getView() creates the view:
public View getView (int position, View convertView, ViewGroup parent){
ViewHolder holder;
if (convertView == null){
holder = new ViewHolder ();
holder.iconAction = (ImageView)convertView.findViewById (id);
holder.iconAction.setOnClickListener (new View.OnClickListener(){
#Override
public void onClick (View v){
int pos = (Integer)v.getTag ();
}
});
holder.iconWait.setTag (position);
...
}else{
holder = (ViewHolder)convertView.getTag ();
}
...
}
This code works... but not always. If you have to scroll the list to see all the items the views are recycled. Suppose that the list has 10 elements and only 5 are visible (visible means: if we see 1 pixel line of an item, then this item is visible). Now, if we scroll down we will see the sixth element but the first (0) is still visible. We scroll a little more and the first element will be hidden and we will see that the seventh element appears, BUT the view of this new element is the view of the first element (0). So I'm saving the positions: 0, 1, 2, 3, 4 and 5. The seventh element (6) will have saved the position 0: Wrong.
Another way to get a click callback is using the ListView's OnItemClickListener listener:
listView = getListView ();
listView.setOnItemClickListener (new AdapterView.OnItemClickListener(){
#Override
public void onItemClick (AdapterView<?> parentView, View childView, int position, long id){
...
}
});
If I scroll down I get the exact position, but with this way I receive callbacks when the item is clicked, no matter the clicked child view.
Thanks.
You're very nearly there, all you need to do is set the tag after your if/else clause. This is to make sure the tag is updated when the view is recycled as well as when it is created from new.
e.g
if (convertView == null){
holder = new ViewHolder ();
...
}else{
holder = (ViewHolder)convertView.getTag ();
}
holder.iconWait.setTag (position);
Local anonymous classes can reference final local variables.
Make position final by changing your getView() method to read:
public View getView (final int position, View convertView, ViewGroup parent) {
and then you can reference position from within the OnClickListener:
holder.iconAction.setOnClickListener (new View.OnClickListener(){
#Override
public void onClick (View v){
... use position here ...
}
});
Your second answer (setting a tag on an element) would work fine if you moved holder.iconWait.setTag (position) outside of the if/then statement -- that is, you should set the tag on recycled rows too, not just on newly-inflated ones.
I also needed to add other data to pass to onClick and it worked with strings too.
holder.iconWait.setTag ("String data here");