tags:

views:

136

answers:

2

Hi,

I'm trying to set up a large number OnClickListeners through a for loop. Each view should run a method when clicked; view 0 should run the method with the value 0, view 1 with the value 1, etc. However, if I declare the OnClickListener using a variable which increments through the for loops, the OnClickListener remembers the reference to the variable rather than the value of the variable at the time it was declared.

That's probably not very clear - here's the code:

for (int i = 0; i < 20; i++) {
    cells[i] = (ImageView) findViewById(cellIDs[i]);
    cells[cellnumber++].setOnClickListener(new OnClickListener() {
    public void onClick(View v){
          cellClicked(cellnumber, v);
       }
    });
}

That correctly sets up the OnClickListeners for each cell, but whichever one is clicked, it always runs calls cellClicked with value of 21. I know I can manually set up the OnClickListeners using cell[0] ... cellClicked(0, v), but is a way to do it so that the ClickListener is passed the value of the variable rather than a reference to the variable in the for loop?

(I saw a few pages which said that Java normally operates with passing values and not references, but I assume the Android libraries somehow do it differently and hence my difficulties here.)

Thanks for your help! Steve

PS: Sorry if this question goes up twice; the page appeared to freeze the first time I tried posting.

A: 

Your OnClickListener is not storing any state, so you are always getting the same value of cellnumber. You probably want to create a custom OnClickListener extension that takes a cellnumber via the constructor and stores it as a member variable for later, this way each listener will have a different copy of the cellnumber value rather than all referencing the same one.

Rob Oxspring
+4  A: 

There's nothing "special" about the Java semantics in Android.

cellClicked isn't actually called when you create each listener, and cellnumber isn't local to the anonymous class you're creating each time, so each listener is referring to the same variable and thus gets the same value.

Use the View.setTag() method to attach the cell ID to each View. Then you only need one instance of the listener which, in its onClick method, calls v.getTag() to get the cell ID.

Edit:
Here's some wholly untested code. I don't actually know off the top of my head if you can cast a boxed Integer like that. Anyway, it's a start:

OnClickListener listener = new OnClickListener() {
    public void onClick(View v) {
        int cellId = (int) v.getTag();
        cellClicked(cellId, v);
    }
}

View v;
for (int i = 0; i < 20; i++) {
    v = findViewById(cellIDs[i]);
    v.setOnClickListener(listener);
    v.setTag(i);
}
Christopher
Thanks, that's very helpful. I'll experiment a bit and see what I can get.
Steve H
Awesome, that was what I needed. It took a bit of fiddling since you can't cast from the object to int directly, but eventually I worked out that this would do it: "int cellID = Integer.valueOf(v.getTag().toString());". There might be a more direct way of doing it but that worked! Thanks again.
Steve H
Jolly good. `int cellId = (Integer) v.getTag()` should work.
Christopher
Why do you need a tag if you already have the id ?Just use v.getId() in the listener.
dtmilano
The cell IDs (0-19) are separate to the resource ID of the view.
Christopher
So what's the difference between "int cellId = (int) v.getTag()" and "int cellId = (Integer) v.getTag()"? The former brings up an error about invalid type casting, whereas the latter just works. Does (Integer) make it perform some interpretation of the data whereas (int) tries to strickly save the data as an int? If the answer is a bit lengthy, don't bother - you've helped a lot already!
Steve H
You can using autoboxing between primitive types (like `int`) and their object counterparts (in this case `Integer`). getTag() returns an `Object`, which you cast to `Integer`. As it's then being assigned to an `int`, it gets auto-unboxed. You can't directly cast from `Object` to a primitive, so the `Integer` cast is required.
Christopher