Jackery IFA Fireplace

Java Android: Custom CursorAdapter, falsches Verhalten der ersten Zeile

metzgore

Ensign
Registriert
Sep. 2011
Beiträge
179
Hallo an alle,

ich programmiere zur Zeit eine Notiz-App. Es gibt eine Funktion, um eine ausgewählte Notiz zu markieren / zu highlighten. Dafür habe ich in der SQLite-Datenbank ein Integer-Feld, das entweder mit 0 (nicht highlighten) oder 1 (highlighten) belegt sein kann. Über die Markierungsfunktion werden die Werte auch korrekt in der DB geändert.
Allerdings spielt die Anzeige nicht mit. Die Notizen werden in einer Liste angezeigt. Um die Markierung nun anzeigen zu lassen, habe ich mir einen eigenen CursorAdapter geschrieben. Hier mal der relevante Code, anhand dessen entschieden wird, ob eine Notiz markiert wird, oder nicht:

Code:
@Override
	public void bindView(View view, Context context, Cursor cursor) {
		TextView title = (TextView) view.findViewById(R.id.titleRow);
		String titleText = cursor.getString(ciTitle);

		TextView date = (TextView) view.findViewById(R.id.date);
		String dateText = cursor.getString(ciDate);

		String formattedDate = formatDateTime(context, dateText);

		boolean noteHighlighted = (cursor.getInt(ciNoteHighlighted) == 1) ? true
				: false;

		title.setText(titleText);
		date.setText(formattedDate);
		if (noteHighlighted) {
			title.setTypeface(null, Typeface.BOLD);
			title.setTextColor(title.getContext().getResources()
					.getColor(R.color.highlightColor));
			date.setTypeface(null, Typeface.BOLD);
			date.setTextColor(date.getContext().getResources()
					.getColor(R.color.highlightColor));
		}
	}

Der Code sollte auch ohne Probleme funktionieren, die ausgewählte Zeile wird markiert. Allerdings wird auch die erste Zeile markiert, obwohl in der Datenbank eine 0 für die erste Zeile eingetragen ist.

Im Beispielbild habe ich Zeile 3 über das Kontextmenü markiert und gleichzeitig wurde Zeile 1 auch markiert.

Ich kann den Fehler einfach nicht finden. Habt ihr vielleicht einen Tip?
 

Anhänge

  • Unbenannt.PNG
    Unbenannt.PNG
    29,3 KB · Aufrufe: 254
Zuletzt bearbeitet:
Verstehe ich dich richtig?:
Du möchtest eine Notitz quasi als Favorite markieren, der dann immer hervorgehoben werden soll und das speicherst du in einer DB?

Da du mir verschweigst, wo die Variable ciNoteHighlighted gesetzt wird, würde ich mal behaupten, dass deren Wert nicht 2 bzw. 3 ist sondern einmal 1 oder 0, je nachdem wo die nochmal anfangen zu zählen...
 
Ja, das hast du richtig verstanden.
Die Variable wird im Konstruktor gesetzt:
Code:
public NotesAdapter(Context context, Cursor c) {
		super(context, c);
		inflator = LayoutInflater.from(context);
		ciTitle = c.getColumnIndex(NotesDbAdapter.KEY_TITLE);
		ciDate = c.getColumnIndex(NotesDbAdapter.KEY_DATECREATED);
		ciNoteHighlighted = c
				.getColumnIndex(NotesDbAdapter.KEY_NOTEHIGHLIGHTED);
}

ciNoteHighlighted (ein Integer) ist der Spaltenindex der Spalte in der Datenbank, in dem entweder 0 oder 1 für die Markierung /Favorisierung gespeichert wird.

In der bindView-Methode sollte also an der Stelle
Code:
boolean noteHighlighted = (cursor.getInt(ciNoteHighlighted) == 1) ? true
				: false;
das Integer für die jeweilige Zeile geholt und dann überprüft werden, ob es 1 ist. Aber an dieser Stelle hakts irgendwie.

Der Aufruf cursor.getInt(ciNoteHighlighted) kann nur entweder 0 oder 1 zurückgeben. In Zeile 1 der DB (entspricht Zeile 1 im obigen Bild) befindet sich in der entsprechenden Spalte eine 0, wodurch noteHighlighted auf false gesetzt und die Hervorhebung in der GUI übersprungen werden sollte. Trotzdem wird diese Zeile hervorgehoben.

Hier wird dieses Problem auch beschrieben. Zwar unter etwas anderen Voraussetzungen, aber vom Prinzip her ist es das selbe Problem.
 
Zuletzt bearbeitet:
Ich kann mir zwar eigentlich nicht so recht vorstellen, dass das Folgende der Grund für dein Problem ist, ein "Fehler" wird es aber bei größeren Listen verursachen. (Teste es aber trotzdem mal vielleicht liegt es ja doch daran):

Android verwendet nicht mehr sichtbare Views erneut und befüllt diese lediglich mit neuem Inhalt (indem deine überschriebene Methode aufgerufen wird). War der vorherige Inhalt hervorgehoben, so ist es der neue auch. Du musst also im "false" Fall die Hervorhebung rückgängig machen.
 
Leider ist es das auch nicht. Ich hab die Datenbank mal neu angelegt, direkt zwei Notizen erstellt und danach Notiz 2 hervorgehoben. Notiz 1 wird auch sofort hervorgehoben, obwohl sie es bis dahin noch nicht war. Dennoch ein guter Hinweis für später.

Edit: Ok, ich musste das jetzt einfach nochmal ausprobieren und hab den else-Fall dran gehangen. Siehe da, es funktioniert. Ich verstehe es allerdings nicht, das Verhalten ist ganz und gar unlogisch. o.O

Edit2: Kommando zurück, irgendwas funktioniert doch nicht richtig. Sieht der else-Fall so aus:
Code:
if (noteHighlighted) {
	title.setTypeface(null, Typeface.BOLD);
	title.setTextColor(title.getContext().getResources()
			.getColor(R.color.highlightColor));
	date.setTypeface(null, Typeface.BOLD);
	date.setTextColor(date.getContext().getResources()
			.getColor(R.color.highlightColor));
} else {
	title.setTypeface(null, Typeface.NORMAL);
	title.setTextColor(Color.RED);
	date.setTypeface(null, Typeface.NORMAL);
	date.setTextColor(Color.RED);
}
geht es. Setze ich statt rot die default Color der TextView ein:
Code:
if (noteHighlighted) {
	title.setTypeface(null, Typeface.BOLD);
	title.setTextColor(title.getContext().getResources()
			.getColor(R.color.highlightColor));
	date.setTypeface(null, Typeface.BOLD);
	date.setTextColor(date.getContext().getResources()
			.getColor(R.color.highlightColor));
} else {
	title.setTypeface(null, Typeface.NORMAL);
	title.setTextColor(title.getTextColors().getDefaultColor());
	date.setTypeface(null, Typeface.NORMAL);
	date.setTextColor(date.getTextColors().getDefaultColor());
}
dann wird die Schrift zwar von dickgedruckt auf normal geändert, die blaue Textfarbe bleibt aber. Ich nehme mal an, die default Textcolor ist nicht immer gleich (in meinem Fall weiß)?
 
Zuletzt bearbeitet:
Ich hab die Datenbank mal neu angelegt, direkt zwei Notizen erstellt und danach Notiz 2 hervorgehoben. Notiz 1 wird auch sofort hervorgehoben ...
Das von mir oben genannte Verhalten ändern nichts an der Datenbank, sondern ist nur ein anzeigetechnisches Problem.

Siehe da, es funktioniert. Ich verstehe es allerdings nicht, das Verhalten ist ganz und gar unlogisch. o.O
Nein ist es nicht. Offenbar ist es so, dass für das erste Listenelement ein View wiederverwendet wird. Ist dies nun genau ein View, indem vorher der Text hervorgehoben war, so bleibt er es auch wenn du den Text änderst.
Da ich bisher davon ausgegangen bin, dass nur Views wiederverwendet werden, die nicht mehr sichtbar sind (was allerdings wohl so nicht stimmen kann) wäre es interessant, wenn jemand erläutern würde, wann es genau zur Wiederverwendung von Views kommt.

..., die blaue Textfarbe bleibt aber
Die Farbe bleibt blau, da sie ja vorher in blau geändert wurde (siehe oben). "getTextColors().getDefaultColor()" liefert nämlich lediglich die aktuelle default Farbe des Textes zurück. D.h. wenn er nicht gedrückt usw. ist.
 
Ich komme mit der Wiederverwendung noch nicht ganz klar. Nehmen wir an, ich starte die App zum aller ersten Mal, lege Notizen an und hebe eine Notiz hervor. Dann wird die erste Notiz auch hervorgehoben, aber es gab doch bis jetzt noch keine View, in der der Text hervorgehoben wurde. Da kann noch nichts wiederverwendet werden.

Ich muss nochmal alles ganz genau durchsehen. Ich könnte schwören, dass es vor zwei Tagen noch lief, aber seitdem habe ich das ein oder andere noch geändert.

Die Farbe bleibt blau, da sie ja vorher in blau geändert wurde (siehe oben). "getTextColors().getDefaultColor()" liefert nämlich lediglich die aktuelle default Farbe des Textes zurück. D.h. wenn er nicht gedrückt usw. ist.

getDefaultColor() ist schon richtig, beim Aufheben der Hervorhebung wird nun alles wieder ordnungsgemäß in weiß geändert. Auch der erste Eintrag wird auf einmal wieder auf weiß geändert. Aber wie gesagt, ich muss da nochmal genau durchgucken.
 
Zuletzt bearbeitet:
In deiner ListView sind mehre Einträge vorhanden. Für jeden dieser Einträge müsste theoretisch ein eigener View (ich nenne es mal rowView) erzeugt werden. Da solche Listen sehr lang sein können würde das bedeuten, dass entweder sehr viele rowViews erzeugt werden müssten (was viel Speicherverbrauch und eine lange Ladezeit verursachen würde) oder beim scrollen ständig neue rowViews erzeugt werden müssten (wodurch das scrollen recht viel Performance kosten würde).

In Android ist das Problem so gelöst, das nicht ständig neue rowViews erzeugt werden sondern nicht mehr sichtbare einfach mit neuem Inhalt befüllt werden und an einer neuen Stelle positioniert werden.

Bei deinem Programm scheint es so zu sein, dass für das erste Listenelement immer ein bereits verwendetes rowView verwendet wird (in welchen der Text hervorgehoben war). Warum dies beim ersten Listenelement so ist, kann ich dir allerdings auch nicht sagen.

getDefaultColor() ist schon richtig
Dann muss ich wohl die Dokumentation falsch verstanden haben...
 
Bei deinem Programm scheint es so zu sein, dass für das erste Listenelement immer ein bereits verwendetes rowView verwendet wird (in welchen der Text hervorgehoben war). Warum dies beim ersten Listenelement so ist, kann ich dir allerdings auch nicht sagen.

Was ist aber, wenn ich (wie oben schon mal geschrieben) die App das aller erste Mal nach der Installation starte, Notizen anlege, Notiz 3 hervorhebe und Notiz 1 dann auch hervorgehoben wird. Der Cursor fängt ja bei Notiz 1 an und stellt sie blau dar. Erst danach wird Notiz 3 blau dargestellt. Für Notiz 1 gab es also noch gar keine View, die wiederverwendet werden konnte.
 
Zuletzt bearbeitet:
Das kann ich dir leider auch nicht genau erklären, aber prinzipiell muss es ja nicht zwangsweise so sein (auch wenn es eigentlich logisch wäre), dass die rowViews von oben nach unten erstellt werden. Der Cursor wird auch nicht von Vorne nach Hinten durchlaufen, sondern in der Methode "getView" wird die Methode "moveToPosition(position)" vom Cursor aufgerufen.
 
Würde es dann eventuell Sinn machen, die getView-Methode zu überschreiben?

Edit: Habe das Problem jetzt lösen können. Danke nochmal an R3ddy, es lag genau an der Wiederverwendung der Views. Habe jetzt wieder den else-Fall dran gehangen, die Textfarben hole ich mir jetzt über android.R.color.primary_text_dark bzw. android.R.color.holo_blue_dark.

Der Thread kann damit geschlossen werden.
 
Zuletzt bearbeitet:
Dies würde zwar auch funktionieren, ich würde es aber lassen und stattdessen die bindView-Methode überschreiben. Läuft es denn damit immer noch nicht korrekt?
 
Zuletzt bearbeitet:
Ich habe die getView-Methode nicht überschrieben, sondern wie du in Post 4 vorgeschlagen hast, im false-Fall (der bindView-Methode) die Hervorhebung rückgängig gemacht. Es ging wirklich nicht anders.
 
Zurück
Oben