Posts mit dem Label App werden angezeigt. Alle Posts anzeigen
Posts mit dem Label App werden angezeigt. Alle Posts anzeigen

26. Dezember 2013

GNOME: Chrome Web Apps in App-Launcher anzeigen

In diesem Artikel soll eine Möglichkeit vorgestellt werden, wie sogenannte "Google Chrome Apps" im App-Launcher der GNOME 3 Shell, der innerhalb des Aktivitäten-Menüs zur Verfügung steht, als Favoriten hinzugefügt werden können. Dadurch ist es möglich, Apps, wie z.B. Google Keep, Google Kalender oder Google Drive, unabhängig Google Chrome zu starten, wobei der Browser jedoch trotzdem auf dem System installiert sein muss. Der unten dargestellte Screenshot zeigt das Resultat der, im weiteren Verlauf dieses Artikels beschriebenen, Schritte.

Die Chrome-Apps Google Drive, Google Kalender und Google Keep im App-Launcher der GNOME-Shell (Klicken zum Vergrößern)

Die folgenden Schritte sind notwendig, um Google Chrome Apps im App-Launcher der GNOME-Shell als Favoriten anzuzeigen. Als Beispiel hierfür wird die App "Google Keep" herangezogen, die es erlaubt, in der Google-Cloud gespeicherte, Notizen zu erstellen.
  • Zunächst ist es notwendig die App, die zukünftig über den App-Launcher der GNOME-Shell unabhängig von Google Chrome gestartet werden soll, innerhalb des Browsers zu installieren.Verfügbare Chrome-Apps können in "Chrome Web Store", der unter dieser Adresse verfügbar ist, gefunden und installiert werden. Google Keep ist beispielweise innerhalb des Stores unter diesem Direktlink verfügbar und kann auf der entsprechenden Seite einfach per Klick auf die dementsprechende Schaltfläche installiert werden.
Die Chrome Web App "Google Keep" innerhalb des Google Web Stores (Klicken zum Vergrößern)
  • Alle installierten Chrome-Apps werden auf der Startseite des Chrome-Browsers angezeigt und können über die entsprechenden Icons innerhalb des Browsers gestartet werden. Um stattdessen eine Desktop-Verknüpfung anzulegen, über die die App außerhalb des Browsers gestartet werden kann, muss der Menüeintrag "Verknüpfung anlegen..." des Kontextmenüs gewählt werden, das über einen Rechtsklick auf ein solches Icon verfügbar ist. 
Kontextmenü einer Chrome-App innerhalb der Startseite des Browsers
  • In dem daraufhin angezeigten Dialog sollte die Option "Desktop" ausgewählt sein, die dafür sorgt, dass nach einem Klick auf die Schaltfläche "Erstellen" eine Datei mit der Endung .desktop in Verzeichnis, das den Desktop des aktuellen Benutzers repräsentiert, erstellt wird. In diesem Beispiel handelt es sich hierbei um das Verzeichnis "Schreibtisch" innerhalb des Home-Verzeichnisses.
Dialog zum Erstellen einer Desktop-Verknüpfung zu einer Chrome Web App (Klicken zum Vergrößern)
  • Der Dateiname der auf diese Weise erstellten Verknüpfung enthält eine unleserliche Kombination von Buchstaben und Zahlen und sollte daher umbenannt werden. Der Dateiname kann hierbei beliebig gewählt werden, wobei jedoch die Dateiendung .desktop erhalten bleiben muss. Der folgende Befehl zeigt, wie eine solche Datei mit Hilfe der Konsole umbenannt werden kann:
     $ mv ~/Schreibtisch/chrome-hmjkmjkepdijhoojdojkdfohbdgmmhki-Default.desktop ~/Schreibtisch/google-keep.desktop  
    
  • Anschließend muss die umbenannte Datei in das Verzeichnis ~/.local/share/applications/ verschoben werden, was über den folgenden Konsolenbefehl bewerkstelligt werden kann:
     $ mv ~/Schreibtisch/google-keep.desktop ~/.local/share/applications/   
  • Um diese Verknüpfungen als Icons innerhalb des App-Launchers der GNOME-Shell anzuzeigen, muss in diesem letzten Schritt der Konfigurationseditor "dconf" genutzt werden. Hierzu müssen die Dateinamen der zuvor erstellten Datei zu dem Wert favorite-apps, der sich unter dem Eintrag org/gnome/shell befindet, hinzugefügt werden. In diesem Beispiel handelt es sich hierbei um den Dateinamen google-keep.desktop, der durch ein Komma getrennt und in Anführungsstrichen ('...') innerhalb der eckigen Klammern ([...]) angegeben werden muss. Der neue Eintrag sollte anschließend unmittelbar in dem App-Launcher der Shell sichtbar werden.
    Der Eintrag "org/gnome/shell/favorite-apps" im dconf-Editor (Klicken zum Vergrößern)
Die hier beschriebene Möglichkeit hat außerdem den positiven Nebeneffekt, dass alle Verknüpfungen zu Chrome-Apps, die sich innerhalb des Verzeichnisses ~/.local/share/applications/ befinden, auch über die Suche der GNOME-Shell auffindbar sind, wie es durch den unten stehenden Screenshot gezeigt wird. 

Chrome Web Apps als Suchergebnisse innerhalb der GNOME-Shell (Klicken zum Vergrößern)

Wird eine Chrome-App wie Google Keep über eine solche Verknüpfung, entweder über den App-Launcher oder die GNOME-Suche, gestartet, wird diese unabhängig von eventuell geöffneten Instanzen des Chrome-Browsers angezeigt. Es somit also nicht weiterhin nötig, den Browser zu starten um den entsprechenden Service nutzen zu können. Der unten stehende Screenshot zeigt das Erscheinungsbild von Google Keep, wenn es über eine solche Verknüpfung gestartet wurde.

Google Keep als eigenständige Anwendung (Klicken zum Vergößern)

Weiterführende Links:

23. Juni 2013

Android: WLAN-Konnektivität prüfen

Innerhalb einer Android-App kann es sinnvoll sein, zu überprüfen, ob das Gerät im Moment über eine aktive WLAN-Verbindung verfügt. Dies ist beispielsweise sinnvoll wenn Netzwerkzugriffe mit erhöhtem Datendurchsatz nicht über die mobile Datenverbindung ausgeführt werden sollen. Im Folgenden soll daher gezeigt werden, wie aus einer Activity heraus überprüft werden kann, ob eine WLAN-Verbindung verfügbar ist.

Den notwendigen Zugriff auf den WLAN-Adapter erlangt man über die Verwendung des sogenannten ConnectivityManager. Eine Referenz auf diesen erhält man durch den Aufruf der getSystemService-Methode der Klasse Context, indem man als Parameter die Konstante Context.CONNECTIVITY_SERVICE übergibt und den Rückgabewert entsprechend castet. Über einen anschließenden Aufruf der getNetworkInfo-Methode des eben referenzierten Services, wobei hierbei der Parameter ConnectivityManager.TYPE_WIFI übergeben werden muss, erhält man dann ein Objekt der Klasse NetworkInfo, über das Informationen über den WLAN-Adapter bezogen werden können. Dessen Konnektivität kann über die isConnected-Methode abgefragt werden.
 ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);  
 NetworkInfo wifiNetworkInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);  
 boolean wlanConnectionAvailable = wifiNetworkInfo.isConnected();  
Um den Zugriff auf den WLAN-Adapter zu erhalten muss außerdem, wie folgendermaßen dargestellt, eine entsprechende Permission in der AndroidManifest.xml definiert werden.
 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />  

21. Juni 2013

Android: Drawable skalieren

Der folgende Code zeigt, auf welche Weise eine Grafik, die als Drawable-Objekt vorliegt, in einer Android-App auf eine bestimmte Größe skaliert werden kann. Dazu muss das Drawable-Objekt über die Methode getBitmap():Bitmap zunächst in ein Bitmap-Objekt konvertiert werden. Dieses kann anschließend über die statische createScaledBitmap-Methode der Klasse Bitmap skaliert werden, indem die Objektreferenz, sowie die erwünschte Breite und Höhe des skalierten Bildes in Pixeln übergeben wird. Die Methode gibt als Rückgabewert ein skaliertes Duplikat des übergebenen Bitmap-Objektes zurück. Als letzter Schritt muss dann dieses Bitmap-Objekt zurück in ein Drawable-Objekt konvertiert werden, was über den Konstruktur der Klasse BitmapDrawable möglich ist.
 Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();  
 Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, 50, 50, false);  
 Drawable scaledDrawable = new BitmapDrawable(resources, resizedBitmap);  
Bei dem Objekt drawable im obigen Beispielcode handelt es sich um das zu konvertierende Drawable-Objekt, welches auf 50 x 50 Pixel skaliert werden soll. Das Objekt scaledDrawable enthält am Ende das skalierte Bild.

16. Juni 2013

In eigener Sache: <netz.kinder/> ist nun bei Google Currents verfügbar

Dieser Blog ist von nun an auch über Googles News-Dienst "Currents" verfügbar. Google Currents ist als App für Android-Smartphones und -Tablets, sowie für die iOS-Plattform, also für Apples iPhone und iPad, verfügbar. Der Dienst erlaubt dabei verschiedene News-Kanäle, die über Currents angeboten werden zu abonnieren um geräteübergreifend auf die jeweils neuesten Artikel zugreifen zu können. Weitere Informationen sind unter diesem Link verfügbar.

Die Artikelübersicht dieses Blogs in der Google Currents-App unter Android (Klicken zum Vergrößern)

19. April 2013

Android: Erkennen wenn App zum ersten Mal gestartet wird

Manchmal kann es bei der Entwicklung einer Android-App hilfreich sein, erkennen zu können wenn diese nach ihrer Installation zum ersten Mal gestartet wird. Diese Erkennung kann man dann beispielsweise nutzen um die Datenbank mit standardmäßigen Einträgen zu füllen oder dem User hilfreiche Tipps einzublenden. Im Folgenden soll kurz aufgezeigt werden, wie so etwas erreicht werden kann.

Die Erkennung sollte in der Activity stattfinden, die beim Starten der App als Erste angezeigt wird. Für die Umsetzung genügt folgender Code:
 public class StartActivity extends Activity {  
   
   SharedPreferences sharedPreferences;  
   
   @Override  
   public void onCreate(Bundle savedInstanceState) {  
     super.onCreate(savedInstanceState);  
     sharedPreferences = getSharedPreferences("preferenceName", MODE_PRIVATE);  
   }  
   
   @Override  
   public void onResume() {  
     super.onResume();  
   
     if (sharedPreferences.getBoolean("firstrun", true)) {  
       sharedPreferences.edit().putBoolean("firstrun", false).commit();  
       // Hier Code einfügen der beim ersten Start einmalig ausgeführt werden soll  
     }  
   }  
   
 }  
Der Code basiert auf der Verwendung von SharedPreferences, die es erlauben, primitive Datentypen zu speichern und zu erhalten, selbst wenn die App beendet wird. In der onCreate-Methode wird zunächst eine solche SharedPreferences-Instanz über einen Namen instanziiert. In der onResume-Methode, die dem Activity-Lifecycle nach erst nach der onCreate-Methode aufgerufen wird, wird versucht einen boolschen Wert mit dem Key "firstrun" aus den SharedPreferences zu laden. Für den Fall, dass dies nicht gelingt, also genau dann, wenn die App zum ersten Mal gestartet wird, wird der Default-Wert "true" angegeben. In diesem Fall wird der Code innerhalb der if-Bedingung ausgeführt, wo nun der Boolean-Wert mit dem Key "firstrun" auf "false" gesetzt werden muss, so dass bei den folgenden Durchläufen die if-Bedingung nie wieder erfüllt wird. Außerdem kann an dieser Stelle derjenige Code eingefügt werden, der beim ersten Start der App ausgeführt werden soll.

7. April 2013

Android: Komplexe Objekte in Bundle speichern

In Android ist es nötig, gewisse Zustände einer Activity oder eines Fragments zu speichern und wiederherzustellen, insofern die Werte nicht verloren gehen sollen, wenn die Activity bzw. das Fragment zerstört und neu gestartet wird, wie es z.B. bei einem Orientierungswechsel des Geräts geschieht. Die prinzipielle Vorgehensweise zum Sichern solcher Werte wurde bereits in dem Post "Die Activity-Methoden onSaveInstanceState und onRestoreInstanceState" für Activities und in dem Post "Die Fragment-Methoden onSaveInstanceState und onActivityCreated" für Fragments erklärt.

Meistens handelt es sich bei den Werten, die dabei in dem Bundle-Objekt gespeichert werden um primitive Datentypen. Hierfür bietet die Bundle-Klasse jeweils Methoden um Werte hinzuzufügen, bzw. später wieder auszulesen. Es besteht jedoch darüber hinaus auch die Möglichkeit, komplexe Objekte zu sichern, indem die Methode putSerializable(String key, Serializable value):void zum Ablegen eines Objekts in dem entsprechenden Bundle, bzw. die Methode getSerializable(String key):Serializable zum referenzieren eines zuvor gespeicherten Objekts aus dem Bundle, genutzt wird. Das zu sichernde Objekt muss demnach die das Interface java.io.Serializable implementieren.

Sollen allerdings Objektinstanzen von Klassen gespeichert werden, die dieses Interface nicht implementieren und die nicht angepasst werden können, weil sie beispielsweise Klassen der Java-API oder eines Frameworks sind, steht man vor dem Problem, dass solche Klassen nicht ohne Weiteres an ein Bundle übergeben werden können. Eine Möglichkeit, wie dieses Problem umgangen werden kann, soll nun im Folgenden aufgezeigt werden.

Die Lösung besteht darin, eine Wrapper-Klasse zu schreiben, die das Serializable-Interface implementiert und eine Instanz des zu sichernden Objekttyps aufnimmt. Dabei sind grundsätzlich zwei Ansätze denkbar: Entweder man entscheidet sich für einen sehr einfachen Wrapper, der lediglich eine Getter- und eine Setter-Methode auf das gekapselte Objekt anbietet, oder man setzt auf eine komplexere, aber eleganter zu verwendende Implementierung, die zudem als Proxy fungiert, indem sie neben dem Serializable-Interface auch die Schnittstelle des zu sichernden Objekttyps implementiert und alle Methodenaufrufe an das gekapselte Objekt weiterleitet. Die hat den Vorteil, dass sich Instanzen dieser Klasse exakt wie das Original handhaben lassen. Eventuell bereits vorhandener Code muss auf diese Weise kaum angepasst werden. Auf eine mögliche Implementierung dieser Variante soll nun näher eingegangen werden. Als Code-Beispiel ist im Folgenden ein Wrapper für das Interface java.util.List, das als Schnittstelle für alle Listenimplementierungen der Java-Standardbibliothek dient, zu sehen. Als Szenario für die Sicherung einer solchen Datenstruktur wäre beispielsweise ein Listenadapter denkbar, der Suchergebnisse in einer ListView darstellt. Wenn bei einem Orientierungswechsel der entsprechenden Activity der zeitaufwendige Netzwerkzugriff der Suchanfrage nicht erneut durchgeführt werden soll, muss die zugrunde liegende Datenstruktur zwischengespeichert werden.
 public class SerializableList<Type> implements List<Type>, Serializable {  
   
     private static final long serialVersionUID = 1L;  
   
     private List<Type> list;  
   
     public SerializableList(final List<Type> list) {  
         this.list = list;  
     }  
   
     public final boolean add(final Type object) {  
         return list.add(object);  
     }  
   
     public final void add(final int location, final Type object) {  
         list.add(location, object);  
     }  
   
     public final boolean addAll(final Collection<? extends Type> collection) {  
         return list.addAll(collection);  
     }  
   
     public final boolean addAll(final int index,  
             final Collection<? extends Type> collection) {  
         return list.addAll(index, collection);  
     }  
   
     public final void clear() {  
         list.clear();  
     }  
   
     public final boolean contains(final Object object) {  
         return list.contains(object);  
     }  
   
     public final boolean containsAll(final Collection<?> collection) {  
         return list.containsAll(collection);  
     }  
   
     public final Type get(final int location) {  
         return list.get(location);  
     }  
   
     public final int indexOf(final Object object) {  
         return list.indexOf(object);  
     }  
   
     public final boolean isEmpty() {  
         return list.isEmpty();  
     }  
   
     public final Iterator<Type> iterator() {  
         return list.iterator();  
     }  
   
     public final int lastIndexOf(final Object object) {  
         return list.lastIndexOf(object);  
     }  
   
     public final ListIterator<Type> listIterator() {  
         return list.listIterator();  
     }  
   
     public final ListIterator<Type> listIterator(final int location) {  
         return list.listIterator(location);  
     }  
   
     public final Type remove(final int location) {  
         return list.remove(location);  
     }  
   
     public final boolean remove(final Object object) {  
         return list.remove(object);  
     }  
   
     public final boolean removeAll(final Collection<?> collection) {  
         return list.removeAll(collection);  
     }  
   
     public final boolean retainAll(final Collection<?> collection) {  
         return list.retainAll(collection);  
     }  
   
     public final Type set(final int location, final Type object) {  
         return list.set(location, object);  
     }  
   
     public final int size() {  
         return list.size();  
     }  
   
     public final List<Type> subList(final int start, final int end) {  
         return list.subList(start, end);  
     }  
   
     public final Object[] toArray() {  
         return list.toArray();  
     }  
   
     public final <T> T[] toArray(final T[] array) {  
         return list.toArray(array);  
     }  
   
     @Override  
     public final int hashCode() {  
         final int prime = 31;  
         int result = 1;  
         result = prime * result + ((list == null) ? 0 : list.hashCode());  
         return result;  
     }  
   
     @Override  
     public final boolean equals(final Object obj) {  
         if (this == obj)  
             return true;  
         if (obj == null)  
             return false;  
         if (getClass() != obj.getClass())  
             return false;  
         SerializableList<?> other = (SerializableList<?>) obj;  
         if (list == null) {  
             if (other.list != null)  
                 return false;  
         } else if (!list.equals(other.list))  
             return false;  
         return true;  
     }  
   
 }  
Die Klasse implementiert sowohl das Interface java.io.Serializable, als auch das Interface java.util.List. Außerdem handelt es sich um eine generische Klasse, die mit dem Typ der Objekte, die die gekapselte Liste aufnehmen soll, typisiert wird. Über den Konstruktor wird diese Liste übergeben und in dem entsprechenden Attribut gespeichert. Dieses wird dazu verwendet, um alle Aufrufe der Methoden, die in dem List-Interface definiert sind, unverändert an die gekapselte Liste weiter zu leiten. Darüber hinaus wurde auch jeweils eine Implementierung der hashCode- bzw. equals-Methode hinzugefügt, damit sich Klasseninstanzen tatsächlich so verhalten, wie man es von einer Liste erwarten würde.

Das Abspeichern der oben gezeigten Listenimplementierung in einem Bundle könnte beispielsweise wie folgt aussehen:
 public final void onSaveInstanceState(final Bundle outState) {  
   SerializableList<String> serializableList = new SerializableList<String>(new ArrayList<String>());  
   serializableList.add("elem1");  
   serializableList.add("elem2");  
   outState.putSerializable("list", serializableList);  
 }  
Um die, auf diese Weise im Bundle gespeicherte, Liste anschließend wiederherzustellen, sind dann folgende Zeilen nötig:
 public final void onRestoreInstanceState(final Bundle savedInstanceState) {  
   SerializableList<String> serializableList = (SerializableList<String>) savedInstanceState.getSerializable("list");  
 }  
Dabei ist ein Cast vom Interface Serializable auf die Serializable-List implementierung nötig.

24. März 2013

Android: HTML in TextViews

Die Android-API bietet eine konfortable Möglichkeit, durch HTML-Tags formatierte, Inhalte in einer TextView darzustellen. Dies ist über die statische Methode fromHtml(String html):Spanned der Klasse android.text.Html möglich. Folgender Beispielcode soll deren Verwendung verdeutlichen:
 String exampleHtml = "<b>Some bold text</b>";  
 Spanned spanned = Html.fromHtml(exampleHtml);  
 TextView textView.setText(spanned);  
Der String enthält hierbei ein HTML-Tag um den Text fettgedruckt darzustellen. Über die bereits beschriebene Methode wird zunächst ein Spanned-Objekt erzeugt, das anschließend über die Methode setText(CharSequence text):void in einer TextView dargestellt werden kann.

Leider enhält die offizielle API-Dokumentation bisher keine Hinweise darauf, welche HTML-Tags unterstützt werden und welche eventuell nicht. Jedoch scheinen seit Android 2.1 folgende Tags auf diese Art darstellbar zu sein:
  • <a href="...">
  • <b>
  • <big>
  • <blockquote>
  • <br>
  • <cite>
  • <dfn>
  • <div align="...">
  • <em>
  • <font size="..." color="..." face="...">
  • <h1>
  • <h2>
  • <h3>
  • <h4>
  • <h5>
  • <h6>
  • <i>
  • <img src="...">
  • <p>
  • <small>
  • <strike>
  • <strong>
  • <sub>
  • <sup>
  • <tt>
  • <u>

23. März 2013

Android: Exception-Handling in AsyncTasks

Die sogenannten AsyncTasks können genutzt werden um zeitaufwändige Operationen, wie z.B. Netzwerkzugriffe, einer Android-App vom GUI-Thread zu entkoppeln. Dies ist notwendig, da ansonsten die Benutzeroberfläche der App während der Operation einfrieren und nicht mehr auf Benutzereingaben reagieren würde. Die offizielle API-Dokumentation der entsprechenden Klasse android.os.AsyncTask ist unter diesem Link zu finden.

Die eigentliche Ausführung der entsprechenden Operation, die von dem Task asynchron ausgeführt werden soll, geschieht nach diesem Pattern in der Methode doInBackground(Params... p):Result, die anschließende Synchronization mit dem GUI-Thread ist über die Methode void onPostExecute(Result r):void möglich. Die Klasse AsyncTask ist hierbei eine generische Klasse. Bei dem Objekttyp Params handelt es sich um die Klasse, deren Instanzen als Parameter verwendet werden sollen, der Objekttyp Result definiert die Klasse, als deren Instanz das Ergebnis des Threads zurückgeliefert werden soll.

Da AsyncTasks oftmals fehleranfällige Operationen wie Netzwerk- oder Speicherzugriffe ausführen, liegt es nahe über ein mögliches Exception-Handling nachzudenken, bei dem die Fehler nicht nur innerhalb des Tasks abgefangen werden können, sondern auch Exceptions nach außen geworfen werden können, um beispielsweise eine entsprechende Ausgabe auf der GUI zu ermöglichen. Eine Möglichkeit, eine solche Anforderung zu realisieren, soll im Folgenden erläutert werden:

Die Grundlage hierzu bietet eine generische Klasse, die als Rückgabewert des entsprechenden AsyncTasks genutzt wird. Sie kann entweder ein gültiges Ergebnis variablen Typs oder eine Exception-Instanz beinhalten. Eine beispielhafte Implementierung einer solchen Klasse ist im Folgenden zu sehen:
 public class TaskResult<Type> {  
   
     private Exception exception;  
   
     private Type result;  
   
     public TaskResult(final Type result) {  
         this.result = result;  
     }  
   
     public TaskResult(final Exception exception) {  
         this.exception = exception;  
     }  
   
     public final boolean isValid() {  
         return exception == null;  
     }  
   
     public final Exception getException() {  
         return exception;  
     }  
   
     public final Type getResult() {  
         return result;  
     }  
 }  
Die Klasse besitzt hierbei jeweils einen Konstruktor, um ein Task-Ergebnis mit einem gültigen Rückgabewert oder einer Exception zu erzeugen. Auf diese kann anschließend mit Hilfe von Getter-Methoden zugegriffen werden, wobei stets eine der beiden Methoden den Wert null zurückliefert. Außerdem bietet die Klasse noch eine weitere Methode isValid():boolean an, die es erlaubt, zu überprüfen ob das Task-Ergebnis einen gültigen Wert oder einen Fehlerfall repräsentiert.

Der folgende Code soll nun beispielhaft veranschaulichen, wie eine solche Klasse als Rückgabewert eines AsyncTasks genutzt werden kann:
 public class Task extends AsyncTask<Float, Integer, TaskResult<Float>> {

     private Activity activity;

     public Task(Activity activity) {
         this.activity = activity;
     }

     @Override
     protected final TaskResult<Float> doInBackground(final Float... params) {
         try {
             float a = params[0];
             float b = params[1];
             float result = a / b;
             return new TaskResult<Float>(result);
         } catch (ArithmeticException e) {
             return new TaskResult<Float>(e);
         }
     }

     @Override
     protected final void onPostExecute(final TaskResult<Float> result) {
         TextView textView = activity.findViewById(R.id.textView);

         if (result.isValid()) {
             textView.setText(Float.toString(result.getResult()));
         } else {
             textView.setText("Division by zero!");
         }
     }

 } 
Der AsyncTask wurde hierbei so typisiert, dass er Parameter vom Typ Float entgegennimmt, der aktuelle Fortschritt des Tasks von einem Integer-Wert repräsentiert wird, was in diesem Beispiel jedoch nicht genutzt wird, und das Ergebnis ein Objekt der zuvor vorgestellten TaskResult-Klasse ist, das ebenfalls mit dem Datentyp Float typisiert wird.

In der doInBackground-Methode wird als Beispiel eine Division von zwei Float-Werten durchgeführt, die als Parameter erwartet werden. Die try-catch-Schleife fängt dabei mögliche Divisionen durch null ab. Erfolgt die Division erfolgreich, wird ein TaskResult-Objekt zurückgegeben, dass mit dem entsprechenden Ergebniswert initialisiert wird, wird dagegen eine Exception geworfen, wird ein TaskResult-Objekt zurückgegeben, das die entsprechende ArithmeticException enthält.

Nach Abarbeitung der doInBackground-Methode wird impizit die onPostExecute-Methode aufgerufen, die dazu dient, das Ergebnis auf der Benutzeroberfläche auszugeben. Hierzu wird in diesem Beispiel eine TextView referenziert um das Ergebnis der oben beschriebenen Berechnung als Text auszugeben. Um die, in einem XML Layout File definierte View zu referenzieren, ist eine Referenz auf die zugehörige Activity nötig, die dem AsyncTask über den Konstruktor übergeben wurde. Falls das TaskResult-Objekt ein gültiges Ergebnis beinhaltet, wird der entsprechende Wert in der TextView ausgegeben, anderenfalls wird ein Text ausgegeben, der auf eine Division durch null hinweist.

20. Februar 2013

Android: Die Fragment-Methoden onSaveInstanceState und onActivityCreated

Der Inhalt dieses Posts soll eine kleine Ergänzung zu diesem kurzen Tutorial, wie in Android Activities ihre Eigenschaften behalten können, auch wenn sie, beispielsweise durch das Drehen des Geräts, neu erzeugt werden, darstellen. Die selbe Problemstellung existiert nämlich auch für Fragments, wobei diese keine onRestoreInstanceState-Methode anbieten. Stattdessen muss in diesem Fall die onActivityCreated-Methode verwendet, die im Lifecylce aufgerufen wird, nachdem die zugehörige Activity erzeugt wurde. 

Der Fragment-Lifecycle (Bildquelle: www.developer.android.com, Klicken zum Vergrößern)

Ausgehend von dem, in dem oben beschriebenen, vergangenen Post, verwendeten Beispiel, soll die Verwendung der entsprechenden Methoden zur Zustandsspeicherung von Fragments nun kurz erläutert werden. Das XML-Layout, das die Oberfläche beschreibt, bleibt dabei das selbe: 
 <?xml version="1.0" encoding="utf-8"/>   
  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"   
   android:orientation="vertical"   
   android:layout_width="fill_parent"   
   android:layout_height="fill_parent" >   
     
   <EditText   
    android:id="@+id/edit"   
    android:layout_width="fill_parent"   
    android:layout_height="wrap_content" />   
     
 </LinearLayout>
Der Code des Fragments würde dagegen folgendermaßen aussehen:
 public class ExampleFragment extends Fragment {  
   
   EditText edit;  
   
   @Override  
   public View onCreateView(LayoutInflater inflater, Viewgroup container,Bundle savedInstanceState) {  
     View view = inflater.inflate(R.layout.main, container, false);
     edit = (EditText) view.findViewById(R.id.edit);
     return view; 
   }  
   
   @Override  
   public void onSaveInstanceState(Bundle outState) {  
     super.onSaveInstanceState(outState);  
     outState.putString("text", (String) text.getText());  
   }  
   
   @Override  
   public void onActivityCreated(Bundle savedInstanceState) {  
     super.onOnActivityCreated(savedInstanceState);  
     if (savedInstanceState != null) {  
       edit.setText(savedInstanceState.getString("text"); 
     }
   }  
    
 }  
Die onSaveInstanceState-Methode bleibt dabei exakt die selbe, während die Funktionalität zum Wiederherstellen der Werte aus dem gepeicherten Bundle in diesem Fall von der Methode onActivityCreated übernommen wird. Auch diese bekommt als Parameter das zwischengespeicherte Bundle übergeben aus dem dann der Text ausgelesen werden kann. Als Besonderheit ist jedoch zu beachten, dass unbedingt eine Null-Prüfung auf das Bundle-Objekt erfolgen muss, da dieses null ist, wenn das Fragment erzeugt wird, ohne dass zuvor die onSaveInstanceState-Methode aufgerufen worden ist (also beim erstmaligen Erzeugen des Fragments).

9. Februar 2013

Android: Die Activity-Methoden onSaveInstanceState und onRestoreInstanceState

Standardmäßig verlieren die Activities einer Android-App temporäre Werte, wie Eingaben in Textfelder, die Auswahl von Checkboxen oder ähnliches, wenn sie neu erzeugt werden. Dies kommt beispielsweise vor, wenn vom Portrait- in den Landscape-Modus gewechselt wird oder umgekehrt und ist in so einem Fall oft unerwünscht. Deshalb bietet die Android-API die beiden Methoden onSaveInstanceState(Bundle outState): void und onRestoreInstanceState(Bundle savedInstanceState): void an, die es ermöglichen, zu erhaltene Zustände zwischenzuspeichern und anschließend wiederherzustellen. Die beiden Methoden werden dabei, dem Activity-Lifecycle entsprechend automatisch aufgerufen. Das folgende kurze Beispiel soll die Verwendung der beiden Methoden demonstrieren.

Der Activity-Lifecycle (Bildquelle: www.developer.android.com, Klicken zum Vergrößern)

Das folgende XML Layout File definiert die Oberfläche einer Activity, die ein Textfeld enthält. Ist dort ein Text eingetragen, geht dieser verloren, wenn vom Portrait- in den Landscape-Modus gewechselt wird, oder umgekehrt. Dieser unerwünschte Effekt soll im Folgenden behoben werden.

 <?xml version="1.0" encoding="utf-8">   
  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"   
   android:orientation="vertical"   
   android:layout_width="fill_parent"   
   android:layout_height="fill_parent" >   
     
   <EditText   
    android:id="@+id/edit"   
    android:layout_width="fill_parent"   
    android:layout_height="wrap_content" >   
     
  </LinearLayout>   
Der Code der dazugehörigen Activity, inklusive der beiden oben genannten Methoden zum Speichern und Wiederherstellen des eingegebenen Textes, sieht dabei folgendermaßen aus:
 public class ExampleActivity extends Activity {  
   
   EditText edit;  
   
   @Override  
   public void onCreate(Bundle savedInstanceState) {  
     super.onCreate(savedInstanceState);  
     setContentView(R.layout.main);  
      
     edit = (EditText)findViewById(R.id.edit);  
   }  
   
   @Override  
   public void onSaveInstanceState(Bundle outState) {  
    super.onSaveInstanceState(outState);  
    outState.putString("text", (String) text.getText());  
   }  
   
   @Override  
   public void onRestoreInstanceState(Bundle savedInstanceState) {  
    super.onRestoreInstanceState(savedInstanceState);  
    text.setText(savedInstanceState.getString("text"));  
   }  
    
 }  
Beim Erzeugen der Activity durch die onCreate-Methode wird das, im XML-File definierte, Textfeld zunächst anhand seiner ID referenziert. Bevor die Activity, beispielsweise durch ein Rotieren des Bildschirms, zerstört wird, wird automatisch die onSaveInstanceState-Methode aufgerufen, in der alle zu sichernden Werte zusammen mit einem String als Key in einem Bundle abgelegt werden können, das als Parameter übergeben wird. Das selbe Bundle wird dann an die onRestoreInstanceState-Methode übergeben, sobald die Activity neu erzeugt wurde. Die gespeicherten Werte können dann über den zuvor angegebenen Schlüssel aus dem Bundle gelesen und zur Wiederherstellung des vorherigen Zustandes verwendet werden.

Nachtrag vom 20. Februar 2013: Unter diesem Post ist von nun an die analoge Vorgehensweise zur Zwischenspeicherung von Zuständen in Fragments beschrieben. 

8. Februar 2013

Android: Gestenerkennung mit GestureOverlayView

In einem vergangenen Post wurde bereits die Erkennung von einfachen Gesten in einer Android-App vorgestellt. Das Problem bei der dort vorgestellten Vorgehensweise, die auf der Verwendung der Klasse "GestureDetector" basierte, war, dass nur diejenigen Gesten erkannt wurden, die direkt auf der entsprechenden Activity, bzw. View ausgeführt wurden. Wenn diese  von anderen Komponenten, wie z.B. Buttons verdeckt wurde, schlug die Erkennung fehl. Diese Problematik lässt sich durch die Komponente "GestureOverlayView" umgehen. Diese Möglichkeit der Gestenerkennung  soll in diesem Post vorgestellt werden. Der ursprüngliche Post findet sich hier. Das Beispiel in diesem Post basiert auf dessen Original-Beispiel.

Zunächst einmal muss eine GestureOverlayKomponente in das entsprechende XML Layout File integriert werden. In diesem Beispiel soll die Komponente über allen anderen Komponenten und über die komplette Bildschirmfläche gelegt werden. Der Aufbau der resultierenden XML-Datei sieht hierbei wie folgt aus:
 <?xml version="1.0" encoding="utf-8"?>  
 <android.gesture.GestureOverlayView xmlns:android="http://schemas.android.com/apk/res/android"  
   android:id="@+id/gestureOverlayView"  
   android:layout_width="fill_parent"  
   android:layout_height="fill_parent"  
   android:eventsInterceptionEnabled="true"  
   android:fadeEnabled="false"  
   android:fadeOffset="0"  
   android:gestureColor="#00000000"  
   android:gestureStrokeType="multiple"  
   android:orientation="vertical"  
   android:uncertainGestureColor="#00000000" >  
   
   <LinearLayout  
     android:layout_width="match_parent"  
     android:layout_height="match_parent"  
     android:orientation="vertical" >  
   
     <TextView  
       android:id="@+id/textView"  
       android:layout_width="wrap_content"  
       android:layout_height="wrap_content"  
       android:text="no gesture detected" />  
   </LinearLayout>  
   
 </android.gesture.GestureOverlayView>  

Standardmäßig werden die ausgeführten Gesten auf der Komponente visuell dargestellt, sollen diese jedoch ausschließlich erkannt werden, so ist diese Darstellung unerwünscht und kann vermieden werden, in dem man die zur Darstellung benutzen Farben über die Attribute android:gestureColor="#00000000" und android:uncertainGestureColor="#00000000" auf transparent stellt. Über die Attribute android:fadeOffset="0" und android:fadeEnabled="false" lässt sich die störende Verzögerung zwischen Gestenerkennung und daraus resultierender Aktion elimieren, die dadurch entsteht, das die Visualisierung, wenn vorhanden, zunächst langsam ausgeblendet wird, bevor die Aktion ausgeführt wird.

Im Gegensatz zur Verwendung der Klasse "GestureDetector", bietet die Verwendung einer GestureOverlayView weitreichendere Möglichkeiten, da beliebige Gesten über eine sogenannte Gesture-Library definiert werden können. Dazu bietet das Android-SDK ein Tool namens "GestureBuilder", das im Android-Emulator bereits als App vorhanden ist.Mithilfe dieser App lassen sich beliebige Gesten durch Zeichnen definieren, mit einem Namen versehen und in einer Datei exportieren. Dazu muss der Emulator so konfiguriert sein, dass er eine virtuelle SD-Karte benutzt (dies wurde bereits in diesem Post beschrieben). Die dadurch erzeugte Datei lässt sich beispielsweise über die Eclipse-View "File Explorer" aus dem Emulator in das reguläre Dateisystem übertragen (die Datei ist auf diese Weise unter dem Pfad mnt/sdcard/gesture zu finden). Die so erhaltene Gesture-Library muss imjeweiligen Projekt im Unterverzeichnis res/raw abgelegt werden.

Im GestureBuilder erstellte Gesten (Klicken zum Vergrößern)

Um die im XML-Layout definierte GestureOverlayView-Komponente zu benutzen um, in der Gesture-Library abgelegte, Gesten zu erkennen, muss anschließen der Code der entsprechenden Activity angepasst werden. Diese muss dazu das Interface android.gesture.GestureOverlayView.OnGesturePerformedListener implementieren. Der resultierende Code könnte etwa wie folgt aussehen:
 public class DashboardActivity extends Activity implements  
         OnGesturePerformedListener {  
   
     private GestureLibrary gestureLibrary;  
   
     @Override  
     public void onCreate(Bundle savedInstanceState) {  
         super.onCreate(savedInstanceState);  
         setContentView(R.layout.dashboard_activity);  
   
         gestureLibrary = GestureLibraries.fromRawResource(this, R.raw.gestures);  
   
         if (!gestureLibrary.load()) {  
             finish();  
         }  
   
         GestureOverlayView gestures = (GestureOverlayView) findViewById(R.id.gestureOverlayView);  
         gestures.addOnGesturePerformedListener(this);  
     }  
   
     public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture) {  
         ArrayList<Prediction> predictions = gestureLibrary.recognize(gesture);  
   
         if (predictions.size() > 0) {  
             Prediction prediction = predictions.get(0);  
   
             if (prediction.score > 1.0) {  
                 if (prediction.name.equalsIgnoreCase("swipe_right")) {  
                     TextView textView = (TextView) findById(R.id.textView);  
                     textView.setText("detected swipe right gesture");  
                 } else if (prediction.name.equalsIgnoreCase("swipe_left")) {  
                     TextView textView = (TextView) findById(R.id.textView);  
                     textView.setText("detected swipe left gesture");  
                 }  
             }  
         }  
     }  
   
 }  
Die Activity erhält hierbei eine Instanz der Klasse android.gesture.GestureLibrary, in die, beim Aufruft der onCreate-Methode, die zuvor erstellte Gesture-Library aus der entsprechenden Datei geladen wird. Außerdem wird die in der XML-Datei definierte GestureOverlayView über ihre ID geladen und die Activity wird als Listener an ihr angemeldet.

Die eigentliche Erkennung findet nun in der onGesturePerformed-Methode statt, die automatisch bei der Erkennung einer definierten Geste aufgerufen wird. Es gilt dabei, zu überprüfen, welche Geste erkannt wurde, was über den, im GestureBuilder angegebenen, Namen möglich ist. In diesem Fall wurden zwei Swipe-Gesten definiert, deren Erkennung in einer TextView ausgegeben wird.

4. Februar 2013

Android: Gestenerkennung (Swipe)

Die Bedienung eines Smartphones oder Tablets fühlt sich besonders elegant an, wenn die jeweilige App Gesten zur Bedienung anbietet. Dieser Post soll die Gestenerkennung von Wischgesten unter Android rudimentär erklären. Eine Übersicht über alle, von den Android-Entwicklern offiziell vorgesehenen Gesten, findet man unter http://developer.android.com/design/patterns/gestures.html. Dieses Beispiel soll sich dabei um Swipe-Gesten drehen, also eine Wischbewegung in eine bestimmte Richtung, wie sie etwa genutzt werden kann, um zwischen mehreren Seiten zu blättern oder eine Sidebar einzublenden.

Die Gestenerkennung ist innerhalb einer Activity oder einer View möglich. Für die Erkennung bietet die Android-API die Klasse android.view.GestureDetector an. Außerdem muss das Interface android.view.GestureDetector.OnGestureListener implementiert werden, um auf erkannte Gesten reagieren zu können. Für eine beispielhafte Activity könnte die Erkennung einer Swipe-Geste (in diesem Fall von links nach rechts) wie folgt aussehen:
 public class MainActivity extends Activity implements OnGestureListener {  
     
   private GestureDetector gestureDetector;   
   
   @Override  
   public void onCreate(Bundle savedInstanceState) {  
     super.onCreate(savedInstanceState);  
     setContentView(R.layout.activity_main);  
       
     gestureDetector = new GestureDetector(this,this);  
   }  
   
   @Override   
   public boolean onTouchEvent (MotionEvent event) {   
     this.gestureDetector.onTouchEvent(event);  
     return super.onTouchEvent(event);  
   }  
   
   @Override  
   public boolean onDown(MotionEvent event) {   
     return false;  
   }  
   
   @Override  
   public boolean onFling(MotionEvent event1, MotionEvent event2,   
       float velocityX, float velocityY) {  
     if (event1.getX() < event2.getX()) {  
       TextView textView = (TextView) findById(R.id.textView);  
       textView.setText("detected swipe gesture");  
       return true;
     }  
     return false;  
   }  
   
   @Override  
   public void onLongPress(MotionEvent event) {  
        
   }  
   
   @Override  
   public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,  
       float distanceY) {  
     return false;  
   }  
   
   @Override  
   public void onShowPress(MotionEvent event) {  
       
   }  
   
   @Override  
   public boolean onSingleTapUp(MotionEvent event) {  
     return false;  
   }  
   
   @Override  
   public boolean onDoubleTap(MotionEvent event) {  
     return false;  
   }  
   
   @Override  
   public boolean onDoubleTapEvent(MotionEvent event) {  
     return false;  
   }  
   
   @Override  
   public boolean onSingleTapConfirmed(MotionEvent event) {  
     return false;  
   }  
   
 }  
Die Instanz des GestureDetectors wird hierbei in der onCreate-Methode der Activity erzeugt, indem der Context, in diesem Fall die Activity selbst, sowie die entsprechende Listener-Implementation, in diesem Fall auch die Activity selbst, übergeben wird.

Die Activity, die in diesem Fall die Schnittstelle OnGestureListener implementiert, wird dadurch um einige Methoden erweitert. In diesem Beispiel ist nur die onFling-Methode, die für die Erkennung von Wischbewegungen gedacht ist, relevant. Der Methode werden, neben zwei Werten für die horizontale und vertikale Wischgeschwindigkeit, auch jeweils ein MotionEvent für den Start und das Ende der Wischbewegung als Parameter übergeben. Über diese MotionEvents lässt sich die Wischrichtung ermitteln. Handelt es sich um eine horizontale Swipe-Geste von links nach rechts, so wird in diesem Beispiel der Text einer TextView geändert.

Außerdem ist zu beachten, dass die Activity die onTouchEvent-Methode überschreiben muss, um das jeweilige TouchEvent an den GestureDetector und die Superklasse weiterzuleiten.

Die zugehörige XML Layout Datei zu diesem Beispiel sieht wie folgt aus:
 <?xml version="1.0" encoding="utf-8"?>  
 <LinearLayout  
   android:layout_width="match_parent"  
   android:layout_height="match_parent"  
   android:orientation="vertical" >  
   
   <TextView  
     android:id="@+id/textView"  
     android:layout_width="wrap_content"  
     android:layout_height="wrap_content"  
     android:text="no gesture detected" />  
   
 </LinearLayout>  
Ein Problem der hier beschriebenen Vorgehensweise kann sein, dass Gesten nur erkannt werden, wenn sie auf dem Hintergrund der Activity ausgeführt werden. Beinhaltet die Activity viele Komponenten wie beispielsweise Buttons, so werden darauf ausgeführte Gesten nicht erkannt. Die Problematik kann durch die Verwendung einer so genannten GestureOverlayView gelöst werden, die transparent über alle anderen Views gelegt wird um die Eingaben abzufangen. Auf diese Möglichkeit soll in einem weiterführenden Post eingegangen werden.

Nachtrag vom 8. Februar 2013: Der Post über oben erwähnte Möglichkeit zur Verwendung einer "GestureOverlayView" ist von nun an hier zu finden.

3. Februar 2013

Android: Lagesensordaten verwenden

Jedes aktuelle Smartphone besitzt heute einen internen Lagesensor, der Daten über die Lage und Ausrichtung des Geräts im Raum erfasst. Wie alle Sensordaten des Geräts, lassen sich auch die Daten dieses so genannten "Accelerometers" auswerten und von Apps verwenden. Das folgende Beispiel soll beispielhaft zeigen, wie so etwas aussehen kann.

Die generelle Vorgehensweise im Umgang mit Sensoren bei der Android-Programmierung ist es, einen entsprechenden Listener, der das Interface android.hardware.SensorEventListener implementiert an einem sogenannten SensorManager zu registrieren. Dieser SensorManager ist ein SystemService, über den alle Sensoren des Geräts angesprochen werden können. Über folgenden Methodenaufruf erhält man eine Instanz auf den SensorManager innerhalb einer Activity:
 SensorManager sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);  
Um einen SensorEventListener an dieser SensorManager-Instanz zu registrieren reicht folgender Methodenaufruf:
 sensorManager.registerListener(listener, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_UI);  
Der Parameter "listener" entspricht dabei dem SensorEventListener, der zum Empfang der Sensordaten registriert werden soll und dessen Implementierung weiter unten noch folgen wird. Die Konstante Sensor.TYPE_ACCELEROMETER bezeichnet den Lagesensor, der in diesem Beispiel verwendet werden soll. Analog dazu funktioniert auch die Verwendung anderer Sensoren. Außerdem lässt sich über den dritten Parameter das Intervall steuern, in dem die Sensordaten aktualisiert werden. Folgende Werte sind hierbei möglich:
  • SENSOR_DELAY_FASTEST: Hierbei werden die Daten so schnell wie technisch möglich aktualisiert. 
  • SENSOR_DELAY_GAME: Das Intervall, das für die Verwendung in Spielen gedacht ist. 
  • SENSOR_DELAY_NORMAL: Entspricht der Standard-Einstellung, die beispielsweise für das Umschalten zwischen vertikaler Ausrichtung und Landscape-Modus genutz wird. 
  • SENSOR_DELAY_UI: Das langsamste Intervall, das für für die Verwendung zur Gestaltung von Benutzeroberflächen gedacht ist.
Der Code für die gesamte Activity, inklusive der Implementierung des SensorEventListeners könnte in etwa wie folgt aussehen:
     public class ExampleActivity extends Activity {  
       
         private SensorManager sensorManager;  
       
         private SensorEventListener accelerometerListener = new SensorEventListener {  
    
             public void onAccuracyChanged(Sensor sensor, int accuracy) {
                 return;
             }
    
             public void onSensorChanged(SensorEvent event) {
                 if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
                     float x = event.values[0];
                     float y = event.values[1];
                     float z = event.values[2];
    
                     TextView xCoor = (TextView) activity.findViewById(R.id.xcoor);
                     TextView yCoor = (TextView) activity.findViewById(R.id.ycoor);
                     TextView zCoor = (TextView) activity.findViewById(R.id.zcoor);
    
                     xCoor.setText("X: " + x);
                     yCoor.setText("Y: " + y);
                     zCoor.setText("Z: " + z);
                 }
             }
    
         };  
       
         @Override  
         public void onCreate(Bundle savedInstanceState) {  
             super.onCreate(savedInstanceState);  
       
             sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);  
             sensorManager.registerListener(accelerometerListener,  
                     sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),  
                     SensorManager.SENSOR_DELAY_UI);  
         }  
       
         @Override  
         public void onDestroy() {  
             super.onDestroy();  
             sensorManager.unregisterListener(accelerometerListener);  
         }  
       
     }  
    Zu beachten ist hierbei, dass die Registrierung am SensorManager beim Beenden der Activity wieder rückgängig gemacht werden muss, da ansonsten weiterhin Sensordaten empfangen werden und die Anwendung niemals komplett beendet werden würde. Dies geschieht über einen entsprechenden Methodenaufruf indem die onDestroy()-Methode der Activity überschrieben wird.

    Die Sensordaten des Lagesensors umfassen drei Werte, die den drei Achsen des dreidimensionalen Raumes entsprechen. In diesem Beispiel werden die erfassten Werte durch den SensorEventListener lediglich auf drei TextViews ausgegeben. Hierfür müssen die TextViews mit den entsprechenden IDs natürlich definiert sein, was über folgendes XML Layout File geschieht:
     <?xml version="1.0" encoding="utf-8"?>  
     <TableLayout xmlns:android="http://schemas.android.com/apk/res/android"  
       android:layout_width="fill_parent"  
       android:layout_height="fill_parent" >  
       
       <TableRow>  
       
         <TextView  
           android:id="@+id/xcoor"  
           android:layout_width="wrap_content"  
           android:layout_height="wrap_content"  
           android:text="X Coordinate: " />  
       </TableRow>  
       
       <TableRow>  
       
         <TextView  
           android:id="@+id/ycoor"  
           android:layout_width="wrap_content"  
           android:layout_height="wrap_content"  
           android:text="Y Coordinate: " />  
       </TableRow>  
       
       <TableRow>  
       
         <TextView  
           android:id="@+id/zcoor"  
           android:layout_width="wrap_content"  
           android:layout_height="wrap_content"  
           android:text="Z Coordinate: " />  
       </TableRow>  
       
     </TableLayout>  

    2. Februar 2013

    Android: Bild in ImageView drehen

    Um in einer Android-App ein Bild, das durch eine ImageView-Komponente dargestellt wird, um einen beliebigen Winkel zu drehen, bietet sich die folgende Lösung an, die auf der Verwendung einer Transformations-Matrix (die Klasse android.graphics.Matrix der Android-API) basiert.
     ImageView imageView = (ImageView) findViewById(R.id.imageView);  
     Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);  
       
     Matrix matrix = new Matrix();  
     matrix.postRotate(30.0f);  
       
     Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);  
       
     imageView.setImageBitmap(rotatedBitmap);  
    Zunächst wird die Instanz der entsprechenden ImageView-Komponente, die in diesem Fall per XML in einem Layout-File definiert wurde, über ihre ID angesprochen. Anschließend wird eine neue Bitmap (eine Instanz der Klasse android.graphics.Bitmap), die ein bestimmtes Bild beinhaltet erstellt. Dann wird die Rotationsmatrix instanziiert und der Drehwinkel, in diesem Fall 30°, über die Methode postRotate(float degree)gesetzt. Über die statische Methode createBitmap(..), die unter anderem eine solche Matrix als Parameter entgegen nimmt, lässt sich eine zweite Bitmap-Instanz erstellen, die das, mithilfe der Matrix gedrehte, Bild enthält. Dieses muss nun nur noch an die zuvor instanziierte ImageView übergeben werden.