How to avoid setting inline onClickListner in getView() - android

I have got ListView with a custom Adapter. Every row contains clickable buttons and texts.
At the moment onClickListeners are set in the body of getView(), this is quite insane idea because this method is called very frequently. Inside every onClick function, I need access to the private data to call new activity with the bundle.
How can I move the onClick definition outside getView() routine?
When onClick will be called I need info about the position of the list element (to access private data) and which View was clicked (to start the correct Activity).
public View getView(int position, View convertView, ViewGroup parent) {
if(convertview == null) {
convertView = ((Activity) getContext()).getLayoutInflater().inflate(R.layout.photo_tweet_row, null);
}
TextView userTweet = (TextView) v.findViewById(R.id.feed_user_tweet_body);
RepliesButton repliesBtn = (RepliesButton) v.findViewById(R.id.feed_replies_button);
Status twitterDataItem = getItem(position);
if (twitterDataItem != null) {
if (userTweet != null) {
userTweet.setText(twitterDataItem.getText());
}
if (repliesBtn != null) {
repliesBtn.setText(" replies");
}
userTweet.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent(getContext(), ProfileActivity.class);
intent.putExtra(getContext().getString(R.string.serializable_user), twitterDataItem.getUser());
getContext().startActivity(intent);
}
});
repliesBtn.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
if (!twitterDataItem.getComments().contentEquals("0")) {
Intent myIntent = new Intent(getContext(), RepliesToFeedActivity.class);
myIntent.putExtra("photoTweet", twitterDataItem);
getContext().startActivity(myIntent);
}
}
});
}
return convertView;
}

Your activitiy has to implement the OnClickListener, and move the implemented method up to the activity level.
Based on the view parameter of the method you are able to detect from which UI object the event came from (button, textview).
As for how to detect for which record/row in a listview. You have to set a custom tag on the buttons and textviews in the getView method, that you will read in the event via getTag this tag can be a custom object too if string is not enough. Probably/recommended way is to be the position in the adapter.

I am afraid that Pentium10 answer is not correct. If the checkbox is to be clickable and checkable independently from the list item itself, (say it is defined in the list item xml layout), then it will intercept the click event, and the list item (the list's OnItemClickListener) will not receive the click at all.
Therefore you must implement the onClickListener in the adapter itself - either in getView(), or have the adapter implement onClickListener - in this case the items must be tagged for the listener to know which item it is operating on.

Related

Update Listview row UI from custom Dialog

I am working on to update image of one button in a row inside custom listview. I have created a custom adapter class for the listview. On click of the button inside one row of listview I have open a custom dialog. Onclick of the OK button of dialog now I want t change the image of the button inside that listview.
Currently I have just made the static button inside the listview and passed the object of that button to the custom dialog method. I am using the simple listview not the fragments.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final ItemInfoViewHolder viewHolder;
Log.d("Inside Get View", ""+position);
if (convertView == null) {
convertView = listInflater.inflate(R.layout.iteminfo_view_row, null);
viewHolder = new ItemInfoViewHolder();
viewHolder.itemSelection = (Button)convertView.findViewById(R.id.itemselectionTextview);
convertView.setTag(viewHolder);
}else{
viewHolder = (ItemInfoViewHolder)convertView.getTag();
}
try{
viewHolder.itemSelection.setId(itemsInfo.get(position).getId());
);
viewHolder.itemSelection.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
System.out.println("Item Id"+v.getId());
Toast.makeText(mContext, "Item Button Clicked", Toast.LENGTH_SHORT).show();
int itemId = v.getId();
SelectPopup selectPopup = new SelectPopup(mContext, R.style.myCoolDialog);
selectPopup.selectQuantity(itemsInfo,itemId,viewHolder.itemSelection);
}
});
}catch(Exception e){
e.printStackTrace();
}
return convertView;
}
Here viewHolder.itemSelection is the object of that button which I am passing to the custom dialog method. By this the image is update randomly on the listview on any row.
Please suggest me some better idea to do that.
Brief explanation:
An Adapter acts as a controller of your datamodel, in your case a List<itemsInfo>, and updates its assigned ListView accordingly inside the getView()-method.
Your goal is to change the visual state of the ListView whenever you do a certain action, a click. This is simply done by changing the datamodel behind the Adapter of your ListView whenever you perform your action and ask your ListView to redraw itself with help of the Adapter.
In code:
Add a field to your itemsInfo that will hold a certain state. This can be in any format or form but for now, let's use a boolean called isPressed.
In your getView(), your View will initialize itself depending on this pressed state.
viewHolder.itemSelection.whatevermethodyouwant(itemsInfo.get(position).isPressed)
When you want to change the state of your row, simply change the datamodel at the given position and update the ListView again.
viewHolder.itemSelection.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
itemsInfo.get(position).setPressed(whatever);
notifyDataSetChanged(); // This is a call for your Adapter and will refill the ListView
}
});
This is just pseudo code to explain the flow of events. Hope it will help you to improve your implementation.

How can I handle onListItemClick(...) based on what view inside the row was clicked?

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

ListViewAdapter - when listeners, attached to view are collected?

I am using Custom ListViewAdapter for displaying, well, a list.
Each row in the list has 3 buttons, i.e. listeners attached.
But I am finding it very disturbing, that during each scroll the new OnClickListeners are being created, even for those rows, where convertView exists, as a non-null value.
// The most common approach to convert view, as I understand:
public View getView(int position, View convertView, ViewGroup parent) {
View view = null;
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.listview_item, parent, false);
} else {
view = convertView;
}
final TextView textView = (TextView) view.findViewById(R.id.txtProduct);
textView.setText(name);
textView.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
...
}
});
... two more listeners, with the same approach
return view;
as per my experience with Java, Spider-sense "ting-a-lings" - seems that creating and throwing away the same listener approach is garbage-collector abusing.
I am not sure when the old listener had been collected, if it had...
Is there a way to use the old listener, instead of creating a new one? (some kind of cache data structure)
You can set your List Adapter class to inherit from the View.OnClickListener interface. Then, simply set
textView.setOnClickListener(this);
And handle the click in your adapter class' onClick method. To know, for example, which row is clicked, add this line prior to the one above:
textView.setTag(position);
Then, in onClick, you can know which position in the list you are handling by getting this tag:
public void onClick(View v) {
Object item = myList.get((Integer) v.getTag());
//handle click event
}

ExpandableListView, changing group view on click

I'm using an ExpandableListView widget. When a group view is clicked, in addition to displaying the child views, I also want to make a change to the group view. I have added an OnClickListener to the group views in the adapter's getGroupView() method, but when one of them is clicked, the changes to the group view are made but the child views don't display - it's as if the OnClickListener overrides the showing of the child views. Is there a call I can add to the OnClickListener to display the children? Any other thoughts are welcome...
EDIT: Here is the code for the getGroupView() method, with the OnClickListener added. I don't think any of the other code is relevant given that it works fine when the OnClickListener is commented out
#Override
public View getGroupView(int position, boolean arg1, View convertView,
ViewGroup arg3) {
if (convertView == null){
convertView = getLayoutInflater().inflate(R.layout.questionbox, null);
}
TextView summary = (TextView)convertView.findViewById(R.id.questionHl);
final TextView asker = (TextView)convertView.findViewById(R.id.asker);
TextView numviews = (TextView)convertView.findViewById(R.id.numViews);
TextView numAnswers = (TextView)convertView.findViewById(R.id.numAnswers);
final TextView fullText = (TextView)convertView.findViewById(R.id.fullText);
summary.setText(q.get(position).summary);
asker.setText("by " + q.get(position).author);
numviews.setText(Integer.toString(q.get(position).views));
numAnswers.setText(Integer.toString(q.get(position).numAnswers));
fullText.setText(q.get(position).fullText);
convertView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0) {
if (fullText.getVisibility() == 8){
fullText.setVisibility(1);;
}
else if (fullText.getVisibility() == 0){
fullText.setVisibility(8);
}
}
});
return convertView;
}
I think it's because your onclicklistener returns true when it should return false. True means that you fully handled the event, so then it is not forwarded back to the expandable list view. But I'm taking a blind guess here, since you didn't show any code :D
Edit: Some onclick methods return a boolean, some don't, so I might be completely wrong. Could you show us your code?

Can i have Different Clickable Views in one single ListView Item?

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

Categories

Resources