I have a list view and in each list item i have for image views and three text view, three of these image view are to act like an image button i.e respond to on click events and so are two of the text view. I have tried using ItemOnClickListeneri mean like this
#Override
public void onItemClick(AdapterView<?> arg0, View convertView, int pos,
long arg3) {
// TODO Auto-generated method stub
bomb = (ImageView) convertView.findViewById(R.id.bomb);
Log.i("Item Clicked", "Item was clicked at pos" + position);
bomb.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
// Perform action on click
//Run what ever task neccessary
}
});
}
But this has a problem it only responds on the second click. i know it has something to do with the parent and child focus but i haven't been able to get around that.
I also tried using the
static class View Holder except i got the implementation wrong it does not respond at all even after two clicks.
Also am using a custom adapter, i used to do it directly from the getView overide method but i found out the hard way that is isn't the best ways to implement what i want to do.
Please i need something that would work for me cause i tried a coupleof thing other than the above mentioned but they have failed.
Get View Codes
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
pos = position;
if(convertView == null)
{
convertView = inflater.inflate(R.layout.singlepost, parent, false);
holder = new ViewHolder();
holder.bomb = (ImageView) convertView.findViewById(R.id.bomb);
holder.bomb.setOnClickListener(bomb_listener);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder)convertView.getTag();
holder.bomb.setOnClickListener(bomb_listener);
}
return convertView;
}
private OnClickListener bomb_listener = new OnClickListener()
{
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
Log.i("HOMEADAPTER", "BOOMB WAS CLICKED AT POSITON" + pos);
holder.bomb.setImageResource(R.drawable.redheart);
}
};
static class ViewHolder {
TextView reporter;
TextView shell;
TextView sheller;
TextView likesnum;
TextView favsnum;
TextView comnum;
ImageView bomb;
ImageView star;
ImageView comment;
}
With this new getview implementation i still don't get the exact item i intend to click
Add the clicklistener code block inside getView() i.e where you create the view,
getView(...) {
if (view == null) {
viewHolder = new ViewHolder();
view = ...inflateView code...
View bomb = view.findViewById(R.id.bomb);
bomb.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
// Perform action on click
//Run what ever task neccessary
}
});
view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder)view.getTag();
}
}
Views respond to events in a bottom-up fashion. Meaning events start with the child view and are passed up to the parent views. If a view cannot or does not respond to an event, it is passed up the chain. When you first click an image, the image view has no OnClickListener associated with it, and therefore cannot respond to the event. On that first click though, you are setting a listener to it. So the next time you click that image, it now has a listener and can respond to the event. This is why it is not responding as expected on the first click. As 66CLSjY suggested, you probably want to override getView (int position, View convertView, ViewGroup parent) in your list adapter to set the listener when the image is added to the list instead of when you click on it.
In response to your comment on 66's answer, keep in mind that ListViews reuse views as much as possible. So even if convertView is not null, you still need to either set a new OnClickListener to it or account for the reuse in some way or it will basically be like you clicked a different image.
After complaining about this issue and trying to get a work around it. Personally i think androids API of the list view is terrible and should be improved upon so that it is easy to use and implement. I think its a bad idea to call your listeners in the override get view method cause you can't always trust the android system to return to you the exact view in which you are requesting due to performance reasons. I am quoting for the developers of the list view. using the View Holder static class only helps to hold the views and access them faster but it doesn't assist in handling event listeners for specific items view within the list view item and honestly i haven't seen any reasonable solution on the internet or Google developer site.
Okay after two nights of racking my brains and endless testing i finally have a solution.
Using the setTagand the ViewHolder implementation but only differently, turn your ViewHolder static implementation into a View what i mean is this
static class ViewHolder extends View implements OnClickListener {
public ViewHolder(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
ImageView bomb;
public void assignList(){
if(bomb != null)
bomb.setOnClickListener(this);
}
public int position = -1;
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
Log.i("HOMEADAPTER", "OUR OWN STUFF");
}
}
Once you have done that in your activity where you implement your OnItemClickListener
all you need to do is get the tag you set like so
ViewHolder holder = (ViewHolder) view.getTag();
and thats all baby! you are 90% done the final 10% thing you need to do is what ever you wish to do with your gotten view.
Related
What do I want to achieve?
In the below SS, when user touches 'vote' button, these vertical progress bars (custom) will be set according to the voting percentages retrieved from server for that particular row.
What is the obstacle?
I have onClickListener inside getView of the CustomAdapter, and when I manipulate the ProgressBar instance (which is in ViewHolder Class), supposingly I want to see the updated ProgressBar on ONLY the one row of the listview that has triggered that action, but, I see every once 3 rows that I scroll down.
Example: I clicked first row, so first row has updated its progress bar, but 4th, 7th, 10th... rows are also updated EVEN IF I don't touch 'vote button'.
My Guessing
I think this problem is related to recycling the view, the weird number is 3 in this case but when I make rows smaller it goes '4', so that is the only clue I have.
SS & Codes
ScreenShot: bit.ly/sofscreenshot
Code:
public View getView(int position, View convertView, ViewGroup parent) {
final ViewHolder holder;
LayoutInflater mInflater = (LayoutInflater) context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
if (convertView == null) {
convertView = mInflater.inflate(layout, null);
holder = new ViewHolder();
//some more initialization
holder.pb1 = (ProgressBar) convertView.findViewById(R.id.leftProgress);
holder.pb2 = (ProgressBar) convertView.findViewById(R.id.rightProgress);
holder.leftVoteButton = (Button) convertView.findViewById(R.id.leftButton);
holder.rightVoteButton = (Button) convertView.findViewById(R.id.rightButton);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.leftVoteButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
holder.pb1.setVisibility(View.VISIBLE);
holder.pb2.setVisibility(View.VISIBLE);
/Some codes...
holder.pb1.setProgress(50);
holder.pb2.setProgress(50);
}
});
}
private class ViewHolder {
//some more objects
ProgressBar pb1;
ProgressBar pb2;
Button leftVoteButton;
Button rightVoteButton;
}
All the answers and comments are appreciated, have a great day and thank you.
You're doing it wrong.
The problem is that you need to have a Model somewhere, and change its status. Then the view is updated regarding the model status.
For example, let's say that this is a "StackOverflow" app, and you have a list of answers. The user upvote the second answer. This means that the second element of the List is upvoted.
Now what?
When the adapter is going through your list of object it will "fire" the getView method for that position. Then you have to update that position according to your model. So, if the position is 1, the adapter is trying to show the second Answer, and you have to set the button to "upvoted". Otherwise you have to set it as "normal".
private List<Answer> answers;
public View getView(int position, View convertView, ViewGroup parent) {
// here get your view (or initialize it)
// get the matching answer
Answer answer = answers.get(position);
if(answer.isUpvoted()) {
holder.pb1.setVisibility(View.VISIBLE);
holder.pb2.setVisibility(View.VISIBLE);
} else {
holder.pb1.setVisibility(View.INVISIBLE);
holder.pb2.setVisibility(View.INVISIBLE);
}
holder.leftVoteButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
holder.pb1.setVisibility(View.VISIBLE);
holder.pb2.setVisibility(View.VISIBLE);
// not sure on how to get this answer here
// you probably have to go "upper" and manage the click from the ListView
answer.setUpvoted(true);
}
});
}
I have a ListView with lots of icons which may be clicked to change the state of their respective row items. I realise that I can create onClick handlers for all of them but I would like to a generic means of identifying which icon (View) has been clicked. (e.g. determine View at touch coords x,y?
Any idea how I can do that?
I am getting row clicks with the below handler:
lvClickListener = new AdapterView.OnItemClickListener()
{
#Override
public void onItemClick( AdapterView<?> arg0, View arg1, int position, long arg3 )
...
On each row I have 5 ImageViews.
I assume all rows are being inflated with the same layout and you are using the holder pattern (http://developer.android.com/training/improving-layouts/smooth-scrolling.html).
This way, you can have a single OnClickListener to "listen" for any of your ImageView (or whatever they are). You only need to set the listener in the momento you actually inflate the view (not when you reuse it), and then set for all ImageView in the same row the row position as the Tag, so, then in the onClick method, you can chech to which row they belong to (by the tag) and which ImageView is it (by the id)
class MyAdapter extends BaseAdapter implements OnClickListener
[...]
#Override
public View getView (int position, View convertView, ViewGroup parent)
{
MyHolder holder = null;
if(convertView != null)
{
holder = new MyHolder();
convertView = inflater.inflate(...)
holder.imageView1 = (ImageView)convertView.findViewById(R.id.[...] )
holder.imageView1.setOnClickListener(this);
holder.imageView2 = (ImageView)convertView.findViewById(R.id.[...] )
holder.imageView2.setOnClickListener(this);
[...]
convertView.setTag(holder);
}
else
{
holder = (HyHolder)convertView.getTag();
[...]
}
holder.imageView1.setTag(position);
holder.imageView2.setTag(position);
[...]
return convertView;
}
#Override
public void onClick(View v)
{
int id = v.getId();
Integer position = (Integer)v.getTag();
[...]
}
Of course you can then optimize this, put the views in an array or whatever or put the OnClickListener outside the Adapter (in the activity, or an instance variable), but this is the basic idea.
As a note, you should check what happens with the onItemClickListener, I'm not pretty sure, but it may cause problems intercepting touches (or it may no, I don't know), but if you're not going to use the whole row click, then you can remove it
You can do this in multiple ways, You can tag each ImageView to your AsyncTask(or whatever) you use. Or, refer https://github.com/square/picasso
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 a Custom ListView which has an ImageView and a TextView. and i implemented ListView.setOnItemSelectedListener();
But is these a way to make both the ImageView and TextView Clickable (Separately), I mean Click on ImageView must call ActivityA and Click on TextView must call ActivityB?
Yes you can do that inside the Adapter class itself. Just set the click listeners for ImageView and Textview in the Adapter class.
Yes ofcourse!
In your custom ListAdapter, you can set onClickListener like below:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
if( row == null ){
LayoutInflater vi = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = vi.inflate(this.textViewResourceId, null);
}
row.findViewById(R.id.image_item).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
}
});
row.findViewById(R.id.text_item).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
}
});
}
Yes ofcourse you can achieve that. You can set onClickListener on them separately inside the adapter class and then set these buttons or textviews as not focusable if you want a different action to be done on clicking the whole list item, using onItemClickListener.
yourButton.setFocusable(false);
yourButton.setFocusableInTouchMode(false);
there are lots of example for the same
like this
point should keep
You need set the listener to each view in getView (don't create in
each time in get view just pass already created one or can pass this
and implement the listener in same adapter class)
make the view (like TextView ) clickable true
You 'll also required the row position so can use different logic
like get & Set tag or at view parant as in this link
I have a ListView with some items. I have toggleButton in each row of the ListView. Assume none of the toggleButtons are selected. The scroll works fine. But when I check the toogleButton, and then scroll my listView, when the selected toggleButton's row moves up, the last toggleButton(which is unchecked) gets checked automatically. And this pattern goes on. I think it has something to do with the reusing the rows of the listItems.
I have added the adapter class below, where the list item loads
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
View rowview = convertView;
if (null == rowview) {
rowview = inflator.inflate(R.layout.groupsettinglistitem, null);
SettingsGroupListItem viewholder=new SettingsGroupListItem();
viewholder.gpname=(TextView) rowview.findViewById(R.id.textView1);
viewholder.status=(ToggleButton) rowview.findViewById(R.id.ToggleButton1);
viewholder.status.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// TODO Auto-generated method stub
Toast.makeText(v.getContext(), "click", Toast.LENGTH_SHORT);
}
});
rowview.setTag(viewholder);
}
SettingsGroupListItem holder=(SettingsGroupListItem) rowview.getTag();
holder.gpname.setText(items[position].getGpname().getText().toString());
rowview.setTag(holder);
return rowview;
}
This two Method add in your BaseAdapter class.
#Override
public int getViewTypeCount() {
//Count=Size of ArrayList.
return Count;
}
#Override
public int getItemViewType(int position) {
return position;
}
Your are correct, you will need to keep track of what state each button have outside the list element since they do get recycled.
Creating a ArrayList for example and put the state for each button when clicked in the ArrayList, and in your getView you can look at ArrayList.get(possition) to determine if the buttons state should be up or down.
I believe--correct me if I'm wrong--that this is a duplicate of Force Listview not to reuse views (Checkbox).
You need to set the state of the Checkbox when you create it in getView to whatever it should be based on your data model.
Important
Users "Chirag Patel" mentioned on answer that method
public int getViewTypeCount() and public int getItemViewType(int position) fix like Tooglebutton automaticly enable state check true on scrolling..that is big wrong .If you dont want automatic enable on scrool just do
toogleButton.setChecked(false);
on getView override method.