Reputation: 3225
Guys I'm having a strange problem:
In a chat application I have a ListView and an Adapter to deal with it. If the user sends an image from gallery I will show a thumbnail on the bubble speech and add a onTouchListener to open this image on full screen. The problem is: When I'm adding the event listener it is adding to all my ImageViews! So if I click on the last imageView it is showing the right image on the full screen, but all other's before are showing the same image. I reviewd the code a lot of times already and I can't see the issue. Can somebody please help?
My adapter:
public class ConversationArrayAdapter extends ArrayAdapter<Message> {
private TextView messageView;
private TextView timeView;
private TextView confView;
private ImageView imageView;
private LinearLayout timeConfView;
private List<Message> messages = new ArrayList<Message>();
private WindowManager wm;
private Display display;
private RelativeLayout.LayoutParams params;
private Context context;
private Message message;
public ConversationArrayAdapter(Context context, int textViewResourceId) {
super(context, textViewResourceId);
this.context = context;
wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
display = wm.getDefaultDisplay();
}
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
if (row == null) {
LayoutInflater inflater = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = inflater.inflate(R.layout.list_item_message, parent, false);
}
message = getItem(position);
messageView = (TextView) row.findViewById(R.id.message);
messageView.setText(message.message);
messageView.setBackgroundResource(message.left ? R.drawable.anonymous : R.drawable.me);
timeView = (TextView) row.findViewById(R.id.time);
timeView.setText(message.time);
confView = (TextView) row.findViewById(R.id.confirmation);
confView.setText(message.conf);
timeConfView = (LinearLayout) row.findViewById(R.id.timeConfWrapper);
imageView = (ImageView) row.findViewById(R.id.imageUploaded);
messageView.setMaxWidth(display.getWidth() - (30*display.getWidth()/100));
imageView.setMaxWidth(display.getWidth() - (30*display.getWidth()/100));
imageView.setMaxHeight(display.getHeight() - (70*display.getHeight()/100));
params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE);
params.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);
messageView.setLayoutParams(params);
messageView.setPadding(
(int)(10 * context.getResources().getDisplayMetrics().density),
(int)(5 * context.getResources().getDisplayMetrics().density),
(int)(15 * context.getResources().getDisplayMetrics().density),
(int)(6 * context.getResources().getDisplayMetrics().density));
messageView.requestFocus();
if (!message.image) {
params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.LEFT_OF, R.id.message);
params.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);
timeConfView.setLayoutParams(params);
timeConfView.setPadding(0, 0, (int)(10 * context.getResources().getDisplayMetrics().density), 0);
} else {
imageView.setPadding(
(int)(5 * context.getResources().getDisplayMetrics().density),
(int)(5 * context.getResources().getDisplayMetrics().density),
(int)(10 * context.getResources().getDisplayMetrics().density),
(int)(5 * context.getResources().getDisplayMetrics().density));
imageView.requestFocus();
messageView.setVisibility(TextView.INVISIBLE);
imageView.setVisibility(ImageView.VISIBLE);
params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.LEFT_OF, R.id.imageUploaded);
params.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);
timeConfView.setLayoutParams(params);
timeConfView.setPadding(0, 0, (int)(10 * context.getResources().getDisplayMetrics().density), 0);
imageView.setImageBitmap(message.bitImage);
imageView.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Uri imgUri = Uri.parse("file://" + message.imagePath);
Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
intent.setData(imgUri);
intent.setDataAndType(imgUri, "image/*");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
return true;
}
});
confView.setVisibility(TextView.VISIBLE);
}
return row;
}
@Override
public void add(Message object) {
messages.add(object);
super.add(object);
}
How I use it on the activity:
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// Checking the request for gallery and a lot of stuff and generating a bitmap with the filePath
Message msg = new Message(false, message.getText().toString(), DateFormat.getTimeInstance().format(new Date()).substring(0,5), true, bitmap, filePath);
adapter.add(msg);
}
And my massage class:
package br.com.unichat.classes;
import android.graphics.Bitmap;
public class Message {
public boolean left;
public boolean image;
public String message;
public String time;
public String conf;
public String imagePath;
public Bitmap bitImage;
public Message(boolean left, String comment, String time) {
super();
this.left = left;
this.message = comment;
this.time = time;
this.conf = "·";
this.image = false;
this.bitImage = null;
}
public Message(boolean left, String comment, String time, boolean image, Bitmap bitImage, String path) {
super();
this.left = left;
this.message = comment;
this.time = time;
this.conf = "·";
this.image = image;
this.bitImage = bitImage;
this.imagePath = path;
}
}
Any ideas? Thanks in advance
Upvotes: 0
Views: 970
Reputation: 4840
Add imageView.setOnTouchListener(null);
right after initializing imageView
. This will ensure that only the rows you want to have a listener attached will have it. As RobP said, your views are getting reused as they come off the screen so a previous OnTouchListener
may still be attached.
You also need to keep track of the message to be used when the user clicks on the image. To do that you can use setTag();
. Your resulting code should look something like this:
imageView.setTag(message);
imageView.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Message messageForThisRow = (Message)v.getTag();
Uri imgUri = Uri.parse("file://" + messageForThisRow.imagePath);
Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
intent.setData(imgUri);
intent.setDataAndType(imgUri, "image/*");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
return true;
}
});
Upvotes: 1
Reputation: 9522
Two issues: 1) Your OnTouchListener is an inner class accessing the message property of the outer class, which keeps changing as different rows are handled. By the time you handle the touch event, the adapter has dealt with the last row of the data, and parked there. It's not like JavaScript closures. Each imageView needs to be given a different OnTouchListener that will show the correct item on touch. You can either do this by making a subclass of OnTouchListener that you initialize with a property to persist the correct image path to display, or you can do a simple hack like this: set a tag on each imageView, and in the OnTouchListener use that tag to figure out which row was tapped and look up the right image that way (use the tag as an array index, or SQLiteCursor cursor position to move to, or what have you).
2) Remember that the rows/views in your table get re-used. So in your adapter, if you are in the if-statement branch for a row that should not have a listener, you need to remove it in case it was previously added when that row/view was bound to a different entry in your backing data.
Upvotes: 1