Android: Erste App (Währungsrechner) – Teil 2

Den ersten Teil des Workshops finden Sie unter „Android: Erste App (Währungsrechner) – Teil 1„.

Unsere App ist bereits lauffähig. Aber es fehlt noch ein entscheidender Punkt, bevor wir uns der Programmlogik annehmen. Die beiden Auswahlboxen sind momentan ohne Inhalt und somit zwecklos. Das wollen wir natürlich ändern.

Die Auswahlboxen können als Inhalt Daten aus einer Datenbank oder aus einem Array (Auflistung) entgegennehmen. Da wir später noch unsere App mehrsprachig machen wollen, werden wir die Währungen als Ressourcen ablegen.

Dafür befolgen Sie die folgenden Schritte:

  1. Klicken Sie mit der rechten Maustaste auf das Projekt in der Package-Explorer Ansicht.
  2. Wählen Sie den Punkt „Android Tools“ (ganz unten im Kontextmenü).
  3. Wählen Sie im Untermenü „New Resource File ...„.
  4. Geben Sie als File „arrays“ ein.
  5. Wählen Sie als Typ „Values“ aus.
  6. Klicken Sie auf „Finish„.

Damit legen wir eine neue Ressource-Datei, in der wir Auflistungen speichern werden. Wir benötigen insgesamt 2 Auflistungen. In der neuen Datei legen Sie entweder über die graphische Oberfläche über den „Add“ Knopf ein neues StringArray (siehe Bilder) mit dem Namen „waehrung„, oder Sie geben die Werte direkt als XML-Code ein. Die XML-Darstellungen sehen Sie weiter unten.

0.1. array.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="waehrung">
        <item>EUR - Euro</item>
        <item>USD - Amerikanische Dollar</item>
        <item>AUD - Australische Dollar</item>
        <item>RUB - Russische Rubel</item>
        <item>CUD - Kanadische Dollar</item>
        <item>GBP - Englische Pfund</item>
    </string-array>
    <string-array name="waehrung_kurs">
        <item>1</item>
        <item>1.3795</item>
        <item>1.3314</item>
        <item>42.1941</item>
        <item>1.3495</item>
        <item>0.8738</item>
    </string-array>
</resources>

Nun können wir die beiden Auswahllisten mit dem Inhalt der ersten Auslistung füllen. Die zweite Liste benötigen wir später im Code. Es sind die Währungskurse in Bezug auf Euro, die wir für die Umrechnung benötigen.

Um die Auswahlliste an eine Auflistung zu binden, müssen wir nur in unseren Layout-Datei „main.xml“ bei den beiden Auswahllisten (Spinner) folgende Zeile (Zeile Nr. 6) hinzufügen.

<Spinner
	android:layout_height="wrap_content"
	android:layout_width="0dp"
	android:layout_weight="1"
	android:id="@+id/selWaehrungStart"
	android:entries="@array/waehrung">
</Spinner>

Wenn Sie jetzt die App starten, können Sie bereits die Ausgangs- und Zielwährung wählen (siehe Bild).

Nun können wir uns den Java-Quellcode widmen. Als Erstes sollte unser „Berechnen“ Knopf auf das Klicken reagieren. Dazu registrieren wir einen Listener (Zuhörer), der praktisch darauf wartet, bis der Benutzer eine Aktion ausführt. Dazu müssen wir als Erstes die Instanz des Knopfes ermitteln. Nun kommt uns zugute, dass wir unsere Views mit einer eindeutigen ID versehen haben.

Aus dem Java Code zu einer Activity könne wir jederzeit eine Referenz auf ein View mit einer ID ermitteln. Dafür stellt die Activity die Methode findViewById(ID). Da die Methode die Basisklasse „View“ zurückliefert, müssen wir den Rückgabewert noch auf das zu erwartende View (in unserem Fall Button) kasten (Button cmdBerechnen = (Button)findViewById(R.id.cmdBerechnen);). Wie an diesem Beispiel zu sehen ist, greifen wir auf die automatisch generierte „R„-Klasse, die uns die Referenzen auf unsere Ressourcen zur Verfügung stellt. Dabei sind folgende oft verwendete Referenzen wichtig:

  • R.id : Referenzen auf die Views der Layouts. Im Layout sind das die android:id="" Attribute
  • R.layout : Referenzen auf die Layout-Dateien
  • R.string : Referenzen auf unsere definierten Texte
  • R.array : Referenzen auf unsere definierte Auflistungen
  • R.color : Referenzen auf die Farben
  • R.drawable : Referenzen auf die Bilder
  • und einige weitere. Weitere Infos dazu finden Sie unter: developer.android.com

Den Listener fügen Sie mit der folgen Zeile hinzu:

cmdBerechnen.setOnClickListener(new OnClickListener() {
	public void onClick(View v) {
		kursBerechnen();
	}
});

Wir rufen in diesem unsere eigene Methode auf, die die Berechnung und die Ausgabe übernimmt. Das Ganze passiert in der onCreate Methode, die beim Erstellen einer Activity als Erstes aufgerufen wird (Lebenszyklen einer Activity wird in einem anderen Workshop erklärt). Schauen wir uns diese genauer an.

0.2. HauptActivity.java

package de.webducer.android.wrechner;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

public class HauptActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        Init();
    }

    /**
     * Initialisierung des Menüs (wird nur ein mal aufgerufen)
     */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
    	MenuInflater inflater = new MenuInflater(this);
    	inflater.inflate(R.menu.menu, menu);
    	return super.onCreateOptionsMenu(menu);
    }

    /**
     * Reagieren auf ein Klick auf ein Menüeintrag
     */
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
    	switch (item.getItemId()) {
    	// Ergebnisliste löschen
		case R.id.optLoeschen:
			TextView ergebnisse = (TextView)findViewById(R.id.txtErgebnisse);
			ergebnisse.setText("");

			break;

		// Programm schließen
		case R.id.optSchliessen:
			this.finish();

			break;

		default:
			break;
		}
    	return true;
    }

    /**
     * Initialisierung der Oberfläche
     */
    private void Init(){
    	// Button mit einem Listener verbinden
    	Button cmdBerechnen = (Button)findViewById(R.id.cmdBerechnen);
    	cmdBerechnen.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				kursBerechnen();
			}
		});
    }

    /**
     * Berechnung und Ausgabe des Ergebnisses
     */
    private void kursBerechnen(){
    	// Elemente auslesen
    	EditText txtBetrag = (EditText)findViewById(R.id.txtBetrag);
    	TextView txtErgebnis = (TextView)findViewById(R.id.txtErgebnisse);
    	Spinner selWaehrungStart = (Spinner)findViewById(R.id.selWaehrungStart);
    	Spinner selWaehrungEnde = (Spinner)findViewById(R.id.selWaehrungEnde);

    	Boolean failure = false;

    	// Uwandlung des Betrages in eine Zahl
    	String betragText = txtBetrag.getText().toString();
    	Double betrag = 0d;
    	if (betragText != null && betragText != "") {
    		try {
    			betrag = Double.parseDouble(betragText);
			} catch (NumberFormatException e) {
				Toast.makeText(this, R.string.err_betarg_format, Toast.LENGTH_LONG).show();
				failure = true;
			}
		}

    	// Bestimmen der Ausgangs- und Zielwährung
    	int wahrungStart = selWaehrungStart.getSelectedItemPosition();
    	int waehrungEnde = selWaehrungEnde.getSelectedItemPosition();

    	// Bestimmen des jeweiligen Wechselkurses
    	String kursStartText = getResources().getStringArray(R.array.waehrung_kurs)[wahrungStart];
    	String kursEndeText = getResources().getStringArray(R.array.waehrung_kurs)[waehrungEnde];

    	// Umwandlung des Kurses in eine Zahl
    	Double kursStart = 0d;
    	Double kursEnde = 0d;
    	if (kursStartText != null && kursStartText != "" && !failure) {
			try {
				kursStart = Double.parseDouble(kursStartText);
			} catch (NumberFormatException e) {
				Toast.makeText(this, R.string.err_kurs_format, Toast.LENGTH_LONG).show();
				failure = true;
			}
		}
    	if (kursEndeText != null && kursEndeText != "" && !failure) {
			try {
				kursEnde = Double.parseDouble(kursEndeText);
			} catch (NumberFormatException e) {
				Toast.makeText(this, R.string.err_kurs_format, Toast.LENGTH_LONG).show();
				failure = true;
			}
		}

    	if(!failure){
	    	// Umrechnung
	    	Double ergebnis = betrag / kursStart * kursEnde;

	    	// Ergebnis ausgeben
	    	StringBuilder ergebnisText = new StringBuilder();
	    	ergebnisText.append(getResources().getStringArray(R.array.waehrung)[wahrungStart].subSequence(0, 3))
	    		.append(" ")
	    		.append(String.format("%.2f", betrag))
	    		.append(" = ")
	    		.append(getResources().getStringArray(R.array.waehrung)[waehrungEnde].substring(0, 3))
	    		.append(" ")
	    		.append(String.format("%.2f", ergebnis))
	    		.append("\n");
	    	txtErgebnis.setText(txtErgebnis.getText() + ergebnisText.toString());
    	}
    }
}

In der Zeile 21 wird durch den Aufruf der Methode setContentView(R.layout.main); wiesen wir unseren Activity HauptActivity.java ein Layout zu, den wir wieder über die generierte Klasse „R“ ansprechen.

Der Aufruf unserer eigenen Methode Init() in der Zeile 23 setzt den Listener für unseren Berechnungs-Knopf. Nun wenden wir uns der eigentlichen Berechnung zu (Zeilen 75 bis 142).

Als Erstes holen wir uns die Referenzen zu allen Views, die wir für unsere Berechnung benötigen (Betrag, Ausgangswährung, Endwährung und Ergebnisfeld).

In der Zeile 87 erhalten wir durch den Aufruf der Methode getText() den Betrag für die Umrechnung. Das ist allerdings ein Textfeld und wir benötigen eine Zahl. Aus diesem Grund konvertieren wir den erhaltenen Text in ein Double-Format (mit der vorherigen Prüfung, ob der Text überhaupt eingegeben wurde, Zeile 89).

In den Zeilen 99 und 100 erhalten wir den Index des ausgewählten Eintrages aus den Auswahlboxen der Währung. Jetzt kommt uns der zweite Array, in dem wir die Kurse definiert haben, zugute. Dieser ist genauso von der Reihenfolge her aufgebaut, wie das Währungs-Array. Nun können wir durch den Index aus den Zeilen 99 und 100 die Kurse ermitteln (Zeilen 103 und 104). Diese sind wiederum Texte und müssen zuerst in Double konvertiert werden, wie bereit beim Betrag.

Wenn alle Konvertierungen ohne Fehler durchgelaufen sind, berechnen wir nun in der Zeile 128 den umgerechneten Betrag. Um eine schöne Anzeige zu erhalten, formatieren wir nur ein wenig unsere Ausgabe. Die Anzeige soll dann in etwa folgendermassen aussehen:

EUR 125,00 = USD 172,44

Damit wir auch eine Geschichte unserer Berechnungen erhalten, hängen wir in der Zeile 140 unser Ergebnis an den vorhandenen Text an.

Um dem Benutzer zu signalisieren, dass mit der Berechnung etwas schief gelaufen ist, erzeugen wir bei einem Konvertierungsfehler einen Toast (kleine Benachrichtigung, die kurz auf dem Bildschirm erscheint). Das erfolgt in den Zeilen 93, 113 und 121. Wenn Sie einen solchen Fehler provozieren wollen, tauschen Sie einfach in der array.xml-Datei in dem Werte-Array die Punkte durch die Kommas aus (erzeugt Fehler beim Konvertieren).

1. Lokalisierung

Die Lokalisierung ist unter Android relativ einfach gelöst. Die Ressourcen, die keinen Länder-Postfox haben, gehören zu der Standardsprache (in unseren Fall Deutsch). Die Standardsprache muss immer vollständig sein. Weitere Sprachen dagegen nicht. Wird eine spezielle Übersetzung nicht gefunden, wird die der Standard-Übersetzung genommen.

In unserer App müssen die Daten aus der strings.xml und arrays.xml Dateien übersetzt werden. Dafür klicken Sie mit der rechten Maustaste auf das Projekt und wählen Sie Android Tools -> New Ressource File... . Wie im Bild zu sehen ist, muss für neue Sprache nun unter „Available Qualifiers“ „Language“ auswählen und auf der rechten Seite den zweistelligen Ländercode eingeben („en“ für Englisch). Nun kann der Inhalt der beiden Dateien in die neuen kopiert werden und die Werte übersetzt werden. Die neuen Dateien landen im Ordner „res/values-en„.

1.1. strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
	<!-- Globale Strings -->
	<string name="app_name">Currency Calculator</string>

	<!-- View Texte -->
    <string name="betrag_eingeben">Please type the value to calculate!</string>
    <string name="cmd_berechnen">Calculate</string>

    <!-- Menü -->
    <string name="opt_loeschen">Clear Result List</string>
    <string name="opt_loeschen_kurz">Clear</string>
    <string name="opt_schliessen">Close App</string>
    <string name="opt_schliessen_kurz">Close</string>

    <!-- Fehlertexte -->
    <string name="err_betarg_format">The value has wrong format!</string>
    <string name="err_kurs_format">The currency value has wrong format!</string>
</resources>

1.2. arrays.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="waehrung">
        <item>EUR - Euro</item>
        <item>USD - American Dollar</item>
        <item>AUD - Australian Dollar</item>
        <item>RUB - Russian Rubel</item>
        <item>CUD - Canadian Dollar</item>
        <item>GBP - Great Britain Pound</item>
    </string-array>
</resources>

2. Menü

Nun gehen wir an das Menü. Dafür erstellen wir wieder eine neue Android Ressource-Datei von Typ Menü. Diese XML-Datei landet im Ordner „res/menu„.

<?xml version="1.0" encoding="utf-8"?>
<menu
  xmlns:android="http://schemas.android.com/apk/res/android">
    <item
    	android:title="@string/opt_loeschen"
    	android:titleCondensed="@string/opt_loeschen_kurz"
    	android:id="@+id/optLoeschen"
    	android:icon="@drawable/opt_clear" />
    <item
    	android:title="@string/opt_schliessen"
    	android:titleCondensed="@string/opt_schliessen_kurz"
    	android:id="@+id/optSchliessen"
    	android:icon="@drawable/opt_close" />
</menu>

In unseren Activity-Java-Datei müssen nur noch wenige Zeilen hinzugefügt werden, um dem Menü ein Leben einzuhauchen. In den Zeilen 30 bis 34 wir das Menü initialisiert (ähnlich, wie das Layout in der onCreate-Methode). Dafür dient die überschriebene Methode onCreateOptionsMenu(). Unter Optionsmenü versteht man unter Android das Menü, das durch das Betätigen der Menütaste erscheint.

Die Auswertung, welches der Menüpunkte nun ausgewählt wurde, erfolgt in der überschriebenen Methode onOptionsItemSelected(). Über die Item-ID können wir bestimmen, was nun ausgewählt wurde und abhängig davon unseren Code ausführen (hier ist ein CASE-Konstrukt sehr gut geeignet, um die Menüpunkte zu unterscheiden und den Code übersichtlich zu halten). Für das Schließen unseren App benötigen wir nur eine Zeile mit this.finish() (Zeile 51). Um die Ergebnisse zurückzusetzen, müssen wir wieder die Referenz auf das View holen und deren Text auf einen leeren Text setzen (Zeilen 44 und 45).

Damit ist unsere App so weit funktionsfähig und einsatzbereit. Die Kurse können sehr einfach in der XML-Datei angepasst und erweitert werden.