Wednesday, July 13, 2011

Listview Highlight fix for Android bug

There's a weird oddity in my program that, when the user clicks on an item in the ListView, you can't just use the postion passed in - you have to use the inverse of it. E.g. if the user selected the first item in a list of four, you need to get the child in the fourth entry on the list, i.e, getChildAt(3), and if the user selected the second item, you need to getChildAt(2) (i.e. the third postion since it's a zero-based list).

Here's some partial code that calculates that.

@Override
protected void onListItemClick(ListView listView, View listItemView,
int position, long id) {

int childCount = listView.getChildCount();

int lastViewPos = childCount - 1;

View selView = listView.getChildAt(lastViewPos - position);

However, if the view wasn't selected, i.e. the timeout counter ran to zero, then this reversal doesn't take effect:


private void displayCorrectAnswer(ListView listView) {


if (mAppState.chosenAnswer.equalsIgnoreCase(TIMEOUT_ANSWER)) {
correctRow = listView.getChildAt(i);
} else {
correctRow = listView.getChildAt(lastViewPos - i);
}


Now, I'm running into a problem when the user gets all the questions right at the current level. In that case an kind of "Congratulations" activity is displayed. If not, a Toast message is displayed, In both case, the activity finishes. However, in the first case, with the congrats display, when I get to it by pressing one of the items in the list (I find it easier than pressing the little "next" button), it goes through the logic again. But this time, although the item was pressed, it doesn't do the reverse-stack that was the impetus for the above-listed workaround.

So for now, I'm going to have to do some kind of ugly flag or counter or something to see if this is the *second* time pressing the the selection. If there was some way to detect if an item were already highlighted, No, actually, it's worse than that. It's only when the new congrats activity is started when this happens. Hmm. What if I were to just call displayCorrect answer with an extra flag, so that I don't have to track the state? It's an awkward api to be sure. But, it wold be some kind of override of the regular logic.

How about:

// see method for explanation
boolean overrideToNonStack = true;

this.hilightCorrectRow(this.mSelectedListView, overrideToNonStack);

to call this:

private void hilightCorrectRow(ListView listView, boolean overrideToNonStack) {
int childCount = listView.getChildCount();
Log.d(TAG, ">>>>>>>>>> childCount: " + childCount);
int lastViewPos = childCount - 1;

// This if statement is a workaround/hack to address
// the issue that when the a list view item has been
// selected, it seems to reverse the order of the views!
// On a timeout, that doesn't happen because no item has been selected

View correctRow = null;

int i = findCorrectViewAnswerPosition(listView, childCount);

nope not difference...is it not happening, or happening too late?

Let's debug.

First, we'll get rid of the above call, since it didn't make any difference.

Well, the debug doesn't shed any light. Actually, I was assuming that it was going through the highlighting routine twice, but, it isn't. It's completely bypassing all that logic, and going straight to check for completion. Then, it just kicks off the activity and finishes. This one has me baffled.

I tried just breakpointing the highlightCorrectRow, and it is only called once, on the first select, as expected.



if (mAppState.chosenAnswer.equalsIgnoreCase(TIMEOUT_ANSWER) || (overrideToNonStack)) {
correctRow = listView.getChildAt(i);
} else {
correctRow = listView.getChildAt(lastViewPos - i);
}

correctRow
.setBackgroundResource(R.drawable.hilight_correct_selector);
}



Ok, I set a single breakpoint at the very last line of the logic, the finish(s) statement - and it happens *after* that! So, what is happening is the stack issue I described earlier is coming back to bite me. In it's dying moments, the activity is redisplaying the list with the view now in it's proper postion.

I've already tried resetting the highlight to the restacked item, but that didn't take - maybe because it's till in the wrong order or something. What would happen if I just set them all to highlighted? Let's see if it does anything:


int childCount = listView.getChildCount();
int i = 0;
for (i = 0; i < childCount; i++) {
View view = l.getChildAt(i);
view.setBackgroundResource(R.drawable.hilight_correct_selector);
}
}

Ah, yes. All green. Sweeter than sweet. So. Now, it's just a question of choosing the correct one - which is exactly what I tried before. Maybe I did something wrong. But, what if I just created a new selector, and just mad them all transparent? That way, it would disappear, but at least it wouldn't be highlighting the wrong one.

Still, I should be able to pick out the right one. Let's try it one more time.

int childCount = this.mSelectedListView.getChildCount();
int i = 0;
for (i = 0; i < childCount; i++) {
View view = mSelectedListView.getChildAt(i);
String str = (String) ((TextView) view).getText();
if (str.equals(mQuestion.hiragana)) {
view.setBackgroundResource(R.drawable.hilight_correct_selector);
break;
}

}

This highlights the wrong one again after the finish.

Try this:

int childCount = this.mSelectedListView.getChildCount();
int i = 0;
for (i = 0; i < childCount; i++) {
View view = mSelectedListView.getChildAt(i);
String str = (String) ((TextView) view).getText();
if (str.equals(mQuestion.hiragana)) {
int j = childCount - i;
view = mSelectedListView.getChildAt(j);
view.setBackgroundResource(R.drawable.hilight_correct_selector);
break;
}

}

This sets the right one - but the wrong one also gets higlighted. Owwwch. That means even if I set them all to transparent, that old highlight might still be on there. No, maybe not. What if I set them all to transparent? That will overwrite the previous assignment. Then, the correct one will be set? Maybe? No, because it will just reverse it again coming out. I just have to set them all to transparent.

Here's the hilight_transparent_drawable:

android:shape="rectangle">





And the hilight_transparent_selector:


android:drawable="@drawable/hilight_transparent_drawable" />
android:drawable="@drawable/hilight_transparent_drawable" />






int childCount = this.mSelectedListView.getChildCount();
int i = 0;

for (i = 0; i < childCount; i++) {
View view = mSelectedListView.getChildAt(i);
view.setBackgroundResource(R.drawable.hilight_transparent_selector);
}

Ah, yes. Free at last. It goes to transparent and looks just find. Actually, I don't need a selector.

for (i = 0; i < childCount; i++) {
View view = mSelectedListView.getChildAt(i);
view.setBackgroundResource(R.drawable.hilight_transparent_drawable);
}

Delete the hilight_transparent_selector, and extract the loop to a method, and we're good go. That's wrap for today!

No comments:

Post a Comment