App Widgets für Android entwickeln

Große Chancen
Kommentare

App Widgets sind Miniapplikationen, die direkt auf dem Home Screen abgelegt werden können. Sie sind bei Android-Usern sehr beliebt, da sie wichtige Informationen schnell zugänglich machen. Während App Widgets für sich allein stehen können, machen gerade größere Applikationen wie Twitter oder Facebook oft von ihnen Gebrauch, um den Nutzer durch aktuelle Informationen in die eigentliche App zu locken. Dieser Artikel stellt Ihnen exemplarisch die Entwicklung eines App Widgets vor und behandelt dabei mehrere Android-Disziplinen: Besonders Android UIs, Intents und Services spielen eine große Rolle. An passender Stelle verweisen wir auf die Android-Dokumentation, um Ihnen die Möglichkeit zu geben, weitere Informationen abzurufen.

Das Ergebnis der hier vorgestellten Applikation sehen Sie in Abbildung 1 – wir werden ein Android App Widget erstellen, das das aktuelle Topprodukt eines fiktiven Web Stores anzeigt. Der Klick auf das Produkt könnte auf die mobile Website des Stores verzweigen oder eine Shopping App öffnen; in unserem Beispiel verlinken wir jedoch nur auf die Homepage von flickr.com, die Quelle der Creative-Commons-Bilder für drei frei erfundene Produkte. Das Widget aktualisiert seinen Inhalt auf Basis der später vorgestellten Konfiguration alle 15 Minuten. Durch einen Konfigurations-Screen kann der User die Farbe des Preistexts wählen. Um ein App Widget auf einem Home Screen zu platzieren, drückt der User den Home Screen für mehr als zwei Sekunden, um eine Auswahl der Möglichkeiten angezeigt zu bekommen. Nach der Auswahl von Widgets erstellt Android eine Liste aller verfügbaren App Widgets, und der User kann eins davon auswählen. Insofern auf dem gewählten Screen genug Platz vorhanden war, wird das Widget dann dort platziert.

Abb. 1: Das Ergebnis der App-Widget-Entwicklung

Aufmacherbild: Technology in the hands of businessmen von Shutterstock / Urheberrecht: violetkaipa

[ header = Grundlagen und Konfiguration ]

Grundlagen und Konfiguration

Die zentrale Java-Klasse bei der App-Widget-Entwicklung ist android.appwidget.AppWidgetProvider. Für eigene Widgets wird diese Klasse erweitert und notwendige Methoden (z. B. onUpdate) werden implementiert oder überschrieben. Konfiguriert wird ein AppWidgetProvider durch ein AppWidgetProviderInfo-Objekt, das die Metadaten des Widgets enthält. Diese Metadaten werden üblicherweise in XML definiert und von der Plattform dann geparst und in das AppWidgetProviderInfo-Objekt umgewandelt. In der Konfigurationsdatei AndroidManifest.xml wird für den WidgetProvider ein Broadcast Receiver per <receiver>-Tag erstellt. Der Intent-Filter dieses Receivers reagiert auf die Action android.appwidget.action.APPWIDGET_UPDATE und wird von der Plattform zum Update des Widgets versendet. Während in der AndroidManifest.xml-Datei nur die Konfiguration des Broadcast-Receivers (für den AppWidgetProvider) abgelegt wird, befindet sich die Konfiguration des Widgets in einer weiteren XML-Datei, die vom <receiver> referenziert wird. Ein Codebeispiel sagt mehr als tausend Worte, hier also zunächst ein Ausschnitt aus der AndroidManifest.xml zur Angabe des Receivers:

<application android:icon="@drawable/icon" android:label="@string/app_name">
  <receiver android:name=".PrettyWidget" >
      <intent-filter>
          <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
      </intent-filter>
      <meta-data android:name="android.appwidget.provider"
                 android:resource="@xml/pretty_widget" />
  </receiver>
</application>

In unserem Beispiel befindet sich die Konfiguration des PrettyWidgets in der XML-Datei pretty_widget.xml, die im Verzeichnis res/xml des Android-Projekts abgelegt ist. Falls dieses Verzeichnis noch nicht existiert, erstellen Sie es einfach. Werfen wir einen Blick auf die Widget-Konfigurationsdatei:

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http: //schemas.android.com/apk/res/android"
    android:minWidth="294dp"
    android:minHeight="72dp"
    android:updatePeriodMillis="900000"
    android:initialLayout="@layout/pretty">
</appwidget-provider>

Das appwidget-provider-Element enthält wichtige Metadaten, wie die Größe des Widgets, die Updatefrequenz oder die Layoutressource des Widgets:

• Die minWidth– und minHeight-Attribute definieren die Fläche, die vom Widget auf dem Home Screen vereinnahmt wird. Zur Berechnung dieser Werte muss man wissen, dass der Home Screen in ein 4×4-Raster aufgeteilt wird. Die Angaben sollten auf jeden Fall in dp, also Density-independent Pixels erfolgen, wodurch Android die Größenangaben automatisch auf die tatsächlich verfügbaren Pixel umrechnen kann. Zur konkreten Berechnung der Werte schlägt die Android-Dokumentation [1] vor, sowohl für die Breite als auch die Höhe einer Zelle von einer Größe von 74 dps auszugehen. Grund ist, dass je nach Portrait/Landscape-Modus zwar mehr Platz zur Verfügung steht, jedoch nicht garantiert werden kann. Insofern geht man vom Worst Case aus, in diesem Fall von einem Screen im Portrait-Format. Die vorgeschlagene Formel lautet: (number of cells * 74) – 2 Weiterhin werden 2 dp vom Ergebnis der Kalkulation abgezogen, um Rundungsfehlern gänzlich aus dem Weg zu gehen. Um die Maße eines Widgets wie in unserem Beispiel zu berechnen, gehen wir also wie folgt vor: Breite: 4 Zellen, also (4×74) – 2 = 294, Höhe: 1 Zelle, also (1×72) –2 0 72
• Das Attribut updatePeriodMillis legt die Updatefrequenz des Widgets in Millisekunden fest. Jedoch bedeutet das nicht, dass das Widget exakt zu diesem Intervall aktualisiert wird. Vielmehr werden die vorhandenen Widgets und Intervalle von der Plattform zusammengefasst und mehrere Widgets auf einmal aktualisisert. Somit können Ressourcen geschont werden, da beispielsweise die Internetverbindung nur einmal initialisiert werden muss.
• Durch initialLayout wird das Layoutfile des Widgets angegeben, das zunächst wie ein klassisches Layout definiert wird. Später gehen wir auf einige Besonderheiten ein. So sind beispielsweise nicht alle UI Widgets für App Widgets zugelassen, und auch die Referenzierung diese UI-Elemente ist etwas anders, da das UI nicht im gleichen Prozess abläuft (das UI wird im Prozess des Home Screens ausgeführt).

Weiterhin kann man durch das Attribut configure eine Aktivität angeben, die zur Konfiguration des Widgets verwendet werden kann. Darauf gehen wir später noch im Detail ein.

Erstellen des App-Widget-Layouts

Das Layout unseres Widgets befindet sich in res/layout/pretty.xml, wie in der Konfiguration des Widgets angegeben. Bei App-Widget-Layouts ist zu beachten, dass nicht alle UI Widgets zur Verfügung stehen. Genau genommen stehen nur diese Layouts und UI Widgets zur Verfügung.

Layouts:

• LinearLayout
• FrameLayout
• RelativeLayout

UI Widgets:

• AnalogClock
• Button
• Chronometer
• ImageButton
• ImageView
• ProgressBar
• TextView

Vielleicht sind Sie zunächst etwas geschockt, da diese Auswahl zugegeben doch recht beschränkt ist. Durch die Verwendung der RemoteViews für das App-Widget-Layout stehen jedoch keine weiteren UI-Elemente als die oben genannten zur Verfügung. Glücklicherweise ist mit dem RelativeLayout der flexibelste Layoutmanager mit dabei. Bei den UI Widgets sind die wesentlichsten Elemente wie TextView, ImageView und ImageButton und Button dabei. Mit etwas Kreativität kann man hier schon einiges anfangen. 

Um die exakten Farbwerte des Hintergrunds des App Widgets zu definieren, wurde zunächst ein Screenschot des Google Weather & News Widgets erstellt und die Farbwerte dann in Gimp ausgelesen. Hier ist die Liebe zum Detail wichtig, so ist der Hintergrund beispielsweise keine solide Farbe, sondern ein radialer Farbverlauf, der in horizontaler Mitte oben beginnt. Zudem sind die Farbwerte des Farbverlaufs auch leicht transparent, sodass der Hintergrund etwas durchscheint. Als Ressource selbst wurde ein Android Shape benutzt, das zusätzlich an zwei Ecken abgerundet wurde. Ein Rand mit 0.5 dp Breite und teilweiser Transparenz rundet das Erscheinungsbild der Hintergrundfläche ab. Hier das Android Shape, abgelegt unter /res/drawable/smooth.xml:

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:android="http: //schemas.android.com/apk/res/android"
    android:shape="rectangle">
  <!-- they mixed it up, bottomLeft is bottomRigth 🙂 -->
    <corners
        android:topLeftRadius="15dp"
        android:bottomLeftRadius="15dp" />
    <!-- F5 = 245 = alpha = fast opaque -->
    <gradient
        android:angle="0"
        android:centerX="0.5"
        android:centerY="0"
        android:startColor="#F5626262"
        android:endColor="#F51E1E1E"
        android:gradientRadius="200"
        android:type="radial"
         />
    <padding
        android:left="5dp"
        android:top="5dp"
        android:right="5dp"
        android:bottom="5dp" />
        
    <stroke
      android:width="0.5dp"
      android:color="#80707070"
    />
</shape>

[ header = App-Widget-Layouts ]

Fertig eingebunden als Hintergrund eines Layouts sieht unser Zwischenergebnis dann wie in Abbildung 2 aus (die Implementierung des AppWidgetProviders folgt). Es folgt nun die Platzierung der View-Elemente. Ziel ist es, Topprodukte darzustellen. Die notwendigen Daten hierfür werden wir später einer Bean entnehmen, die eine simple Fassade namens Remote zur Verfügung stellt. Es wird ein RelativeLayout innerhalb eines weiteren RelativeLayouts erstellt. Das äußere Layout nimmt den verfügbaren Platz ein, jedoch würde das nicht gut aussehen, da die Elemente bis zum äußeren Rand platziert werden würden. Ebenso wollen wir den Preis leicht über die Kante des Shapes hinauslaufen lassen, wodurch wir mit zwei Layouts arbeiten müssen. Da die Daten der Produkte zwar komplett von den Implementierung des Remote-Interfaces gefaket, aber die enthaltenen URLs der Bilder tatsächlich per Internet geladen werden, sollte man dem User anstatt eines leeren Widgets den Ladevorgang signalisieren. Deshalb befindet sich im Layout auch eine Progressbar, das indeterminate=true gesetzt hat. Alle anderen UI Widgets werden zunächst auf visibility=invisible gesetzt. Sobald die Daten verfügbar sind, werden diese UI-Elemente eingeblendet. Das Zwischenergebnis des Widgets mit ProgressBar sowie das Layout sehen Sie in Abbildung 3 und in Listing 1.

Abb. 2: Der Hintergrund des Widgets als Shape-Ressource

Abb. 3: Eine Progressbar wurde hinzugefügt, um den Ladevorgang zu signalisieren

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http: //schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
  <RelativeLayout
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_alignParentTop="true"
    android:background="@drawable/smooth"
    android:layout_marginTop="10dp"
    android:layout_marginBottom="18dp"
    android:layout_marginRight="10dp"
    android:layout_marginLeft="10dp"
    android:padding="5dp"
    >
    
  <ProgressBar
    android:id="@+id/progress"
    android:indeterminate="true"
    android:layout_centerInParent="true"
    android:layout_width="50dp"
    android:layout_height="50dp"
    />  
    
    
  <ImageView
    android:id="@+id/image"
    android:visibility="invisible"
    android:scaleType="centerCrop"
    android:layout_width="60dp"
    android:layout_height="60dp"
    android:layout_alignParentLeft="true"
    android:layout_centerVertical="true"
    />   
    
  <TextView
    android:id="@+id/text"
    android:visibility="invisible"
    android:textColor="#FFFFFF"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_toRightOf="@id/image"
    android:layout_marginLeft="5dp"
    android:layout_alignParentRight="true"  
    />
    </RelativeLayout>
  <TextView
    android:id="@+id/price"
    android:visibility="invisible"
    android:layout_width="160dp"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_alignParentRight="true"
    android:layout_marginRight="15dp"
    android:layout_marginBottom="0dp"
    android:singleLine="true"
    android:gravity="center"
    android:textSize="40dp"
    android:textStyle="bold|italic"
    android:textColor="#d9b921"
    android:shadowColor="#910e26"
    android:shadowRadius="7"
    />  
</RelativeLayout>

[ header = Implementierung des AppWidgetProviders ]

Implementierung des AppWidgetProviders

Nach all dieser Konfiguration und UI-Definition sind wir nun endlich bereit, am AppWidgetProvider des Widgets zu arbeiten. Dazu legen wir eine neue Klasse an, die von besagter Klasse erweitert wird. Ein AppWidgetProvider ist ein spezialisierter BroadcastReceiver, wordurch nun auch klar sein dürfte, wieso dieser in der AndroidManifest.xml Datei als <receiver> angelegt worden ist. Somit stehen auch sämtliche BroadCast-Receiver-Methoden wie onReceive bei Bedarf durch Überschreiben zur Verfügung. Betrachten wir jedoch zunächst nur die Lifecycle-Methoden des AppWidgetProviders selbst:

 onUpdate(context, appWidgetManager, appWidgetsIds) wird vom WidgetProvider auf Reaktion zum Broadcast ACTION_APPWIDGET_UPDATE aufgerufen. Die Implementierung sollt nun die RemoteViews der Widgets aktualisieren.
• onEnabled(context) wird aufgerufen, sobald das erste Widget dieses Providers platziert worden ist. Die Methode wird nur beim ersten Widget dieses Providers aufgerufen und eignet sich für generelle Initialisierungen (DB erstellen, etc.).
• onDisabled(context) wird aufgerufen, sobald das letzte Widget dieses Providers entfernt wurde.
• onDeleted(context, appWidgetIds) wird aufgerufen, sobald ein Widget vom Home Screen entfernt wird. Beispielsweise kann hier die Konfiguration einer Widget-Instanz gelöscht werden.

Die Implementierung der onUpdate-Methode unseres Beispiels aktualisiert jedoch das Widget-Layout nicht direkt (Listing 2). Wie jeder andere Broadcast-Receiver sollte auch die onUpdate-Methode des WidgetProviders nach spätestens zehn Sekunden beendet sein. Wenn das nicht der Fall ist, könnte der Prozess vorzeitig vom System beendet werden. Um dem zu entgehen, triggert unsere onUpdate-Methode nur einen IntentService, der die eigentliche Arbeit übernimmt. Der IntentService wird per startService-Methode des Contexts gestartet. Als Extra enthält der Intent die Widget-ID, sodass später die Daten richtig zugeordnet werden können. Der Ablauf ist wie folgt:

• Plattform sendet ACTION_APPWIDGET_UPDATE BroadCast, der vom Provider empfangen wird und in einem Aufruf der onUpdate-Methode resultiert.
• onUpdate verändert das UI, blendet die ProgressBar ein und alle anderen UI Widgets aus. Das Widget ist nun im Progresszustand. Ebenso startet onUpdate einen IntentService, der die Daten über das Internet lädt.
• Der IntentService lädt die Daten und versendet wiederum einen Intent, der vom AppWidgetProvider empfangen wird. Die onReceive-Methode filtert nach der verwendeten Action und aktualisiert nun das UI mit den Daten, die per Intent-Extras übergeben worden sind.

Da ein Widget mehrfach vom User auf dem Home Screen platziert werden kann, ist es auch notwendig, pro Widget-ID die RemoteViews zu aktualisieren sowie den IntentService zum Laden der neuen Daten anzustoßen. Beachten Sie auch den etwas umständlich aussehenden Umgang mit den UI Widgets:

RemoteViews views = new RemoteViews(context.getPackageName(),R.layout.pretty);
views.setViewVisibility(R.id.image, View.INVISIBLE);

Um auf die View zugreifen zu können, kann innerhalb eines Widgets nur RemoteViews verwendet werden. Eine direkte Referenz zum UI-Widget-Objekt per findViewById (wie in Aktivitäten) ist hier leider nicht möglich. Details zu RemoteViews und den zur Verfügung stehenden Methoden zur Veränderung von UI-Elementen entnehmen Sie bitte der Dokumentation der RemoteView [3].

@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
    int[] appWidgetIds) {

  for (int i = 0; i < appWidgetIds.length; i++) {
    int appWidgetId = appWidgetIds[i];

    RemoteViews views = new RemoteViews(context.getPackageName(),
        R.layout.pretty);
    
    views.setViewVisibility(R.id.image, View.INVISIBLE);
    views.setViewVisibility(R.id.text, View.INVISIBLE);
    views.setViewVisibility(R.id.price, View.INVISIBLE);
    views.setViewVisibility(R.id.progress, View.VISIBLE);
    
    
    Intent dataDownloadIntent = new Intent(context, RemoteIntentService.class);
    dataDownloadIntent.putExtra("WIDGET_ID", appWidgetId);
    context.startService(dataDownloadIntent);

    appWidgetManager.updateAppWidget(appWidgetId, views);
  }
}

Der verwendete IntentService (Listing 3), hier RemoteIntentService, muss in der AndroidManifest.xml registriert werden:

<service android:name=".RemoteIntentService" />

Ein IntentService ist ein äußerst praktischer Service, der automatisch die eingehenden „Requests“, also Intents, einer Queue hinzufügt und dann der Reihe nach in einem separaten Thread abarbeitet. Der IntentService unseres Beispiels in Listing 3 lädt nun die Produktdaten eines Topprodukts anhand einer Fakeimplementierung. Der im ProductData-Objekt enthaltene URL des Produktbilds wird jedoch, um dieses Beispiel möglichst nah an der Realität zu halten, tatsächlich über das Internet aufgelöst. Dabei kommen normale Java-Klassen wie URLConnection und BufferedInputStream zum Einsatz, die nicht weiter erwähnenswert und deshalb nicht im vorliegenden Quellcode vorhanden sind. Sobald das Produktbild abgerundet worden ist, werden die Produktdaten und das Bild in einem neuen Intent verpackt. Eine Custom Action de.flavor.android.widgetdemo.DATA_AVAILABLE wird hierbei verwendet. Diese Action muss zusätzlich zu der bereits vorhandenen APPWIDGET_UPDATE Action vom WidgetProvider gefiltert werden. Die neue Konfiguration des AppWidgetProviders in der AndroidManifest.xml sieht so aus:

<receiver android:name=".PrettyWidget" >
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
        <action android:name="de.flavor.android.widgetdemo.DATA_AVAILABLE"/>
    </intent-filter>
    <meta-data android:name="android.appwidget.provider"
               android:resource="@xml/pretty_widget" />
</receiver>

Da diese Action nicht standardmäßig vom AppWidgetProvider behandelt wird, müssen wir nun auch die onReceive-Methode überschreiben, um auf diese Action reagieren zu können (Listing 4). Insofern die Action des Intents nicht der DATA_AVAILABLE Action entspricht, wird der Aufruf einfach per super() an den AppWidgetProvider übergeben. Das ist der Fall, wenn die Plattform die Broadcast Intents zur Aktualisierung der Widgets aussendet. 

Insofern jedoch die Daten geladen worden sind, wird das UI aktualisiert. Die ProgressBar wird ausgeblendet, die anderen UI Widgets werden eingeblendet und ihre Daten aktualisiert. Die Widget-ID wurde vom RemoteIntentService wiederum in den Broadcast Intent an das Widget durchgeschleift, sodass nach getaner Arbeit das entsprechende Widget per appWidgetManager.updateAppWidget(appWidgetId, views) aktualisiert werden kann. Ebenso wird ein PendingIntent auf dem Produktbild registriert. Ein Klick wird später auf die Homepage von Flickr weiterleiten.

public class RemoteIntentService extends IntentService {
  public RemoteIntentService() {
    super("RemoteIntentService");
  }

  @Override
  protected void onHandleIntent(Intent intent) {
    ProductData product = new FakeRemoteImpl().getTopProduct();
    Bitmap productBitmap = getImage(product.imageURL); //lädt ein Bild via URL
    Bitmap roundedBitmap = null;
    if (productBitmap != null)
      roundedBitmap = ImageHelper.getRoundedCornerBitmap(productBitmap, 30);
    
    Intent dataAvailable = new Intent("de.flavor.android.widgetdemo.DATA_AVAILABLE");
    dataAvailable.putExtra("product", product);
    if (roundedBitmap != null)
      dataAvailable.putExtra("productBitmap", roundedBitmap);
    else
      dataAvailable.putExtra("productBitmap", BitmapFactory.decodeResource(getResources(), R.drawable.goggle));

    dataAvailable.putExtra("WIDGET_ID", intent.getIntExtra("WIDGET_ID", -1));
    sendBroadcast(dataAvailable);
  }
  ...  
}
@Override
public void onReceive(Context context, Intent intent) {
  if (!intent.getAction().equals("de.flavor.android.widgetdemo.DATA_AVAILABLE"))
    super.onReceive(context, intent);
  else
  {
    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
    
    int appWidgetId = intent.getIntExtra("WIDGET_ID", -1);

    RemoteViews views = new RemoteViews(context.getPackageName(),
        R.layout.pretty);
    
    views.setViewVisibility(R.id.image, View.VISIBLE);
    views.setViewVisibility(R.id.text, View.VISIBLE);
    views.setViewVisibility(R.id.price, View.VISIBLE);
    views.setViewVisibility(R.id.progress, View.INVISIBLE);
    
    ProductData product = (ProductData)intent.getSerializableExtra("product");
    views.setImageViewBitmap(R.id.image, (Bitmap)intent.getParcelableExtra("productBitmap"));
    views.setTextViewText(R.id.text, product.description);
    views.setTextViewText(R.id.price, product.formattedPrice);        
    
    //Intent to launch flickr.com, just for demo, just for image
    Intent i = new Intent(Intent.ACTION_VIEW);
    i.setData(Uri.parse("http: //www.flickr.com"));
    PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, i, 0);
    views.setOnClickPendingIntent(R.id.image, pendingIntent);

    //update
    appWidgetManager.updateAppWidget(appWidgetId, views);

  }    
}

[ header = Hinzufügen eines Config-Screens ]

Hinzufügen eines Config-Screens

Um zu demonstrieren, wie ein App-Widget-Konfigurations-Screen benutzt werden kann, um pro separatem Widget eine notwendige Konfiguration abzuspeichern, werden wir es dem User ermöglichen, die Farbe des Preises im Widget UI aus einer Liste auszuwählen. Ein Widget-Konfigurations-Screen ist eine normale Aktivität, die ebenso im AndroidManifest.xml definiert werden musss. Als einzige Besonderheit haben wir ein Custom Theme verwendet, das von dem Standard-Dialog-Theme abgewandelt wurde und das gleiche Shape als Hintergrund verwendet wie auch das Widget selbst.

<activity android:name=".ConfigureActivity" android:theme="@style/CustomDialog" android:label="@string/config_title">
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
    </intent-filter>
</activity>

Um dem Widget Host (dem Home Screen) mitzuteilen, dass ein Konfigurations-Screen gelauncht werden soll, wird dieser per configure-Attribut in der Widget-Konfiguration (hier /res/xml/pretty_widget.xml) eingetragen.

<appwidget-provider xmlns:android="http: //schemas.android.com/apk/res/android"
...
    android:configure="de.flavor.android.widgetdemo.ConfigureActivity" >
</appwidget-provider>

Sobald nun ein Widget zu einem Home Screen hinzugefügt wird, erscheint zunächst die ConfigureActivity in Gestalt eines Dialogs und der User kann eine Farbe auswählen (Abb. 4, 5). 

Diese Aktivität, die ein UI zur Konfiguration zur Verfügung stellt, bekommt die Widget-ID der zu konfigurierenden Widget-Instanz ebenso per Intent als Extra übermittelt. Dadurch kann der ausgewählte Farbwert pro Widget gespeichert werden. Zur Speicherung bieten sich für kleinere Einstellungen die SharedPreferences an, die applikationsweit zur Verfügung stehen. Der ausgewählte Farbwert wird unter Verwendung der SharedPreferences folgendermaßen abgespeichert:

Bundle extras = getIntent().getExtras();
int widgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);

AppWidgetManager.INVALID_APPWIDGET_ID);
SharedPreferences prefs = getApplicationContext().getSharedPreferences("settings",MODE_PRIVATE);

Editor editor = prefs.edit();
editor.putString("WIDGET_" + widgetId + "_COLOR", color); 
editor.commit();  

Abb. 4: Der Konfigurations-Screen

Abb. 5: Der User kann nun eine Farbe auswählen, die per SharedPreferences gespeichert wird

Abb. 6: Mehrere Widgets mit unterschiedlicher Konfiguration

Abb. 7: Ein Klick auf das Produktbild löst den PendingIntent aus, der die Flickr-Homepage im Browser lädt

[ header = Zusammenfassung ]

Später kann der AppWidgetProvider nun den Farbwert pro Widget wieder ganz einfach mit dem Key WIDGET_<id>_COLOR auslesen. Die Farbe wird dann durch einen Aufruf der RemoteViews gesetzt, beispielsweise:

String color = prefs.getString(key, "DEFAULT");

if (color.equals("RED"))
  views.setTextColor(R.id.price, Color.RED);

Sobald eine Aktivität zur Konfiguration eines Widgets deklariert worden ist, muss das erste Update der Widget RemoteViews ebenso von dieser Aktivität durchgeführt werden. Da ein Update in unserem Beispiel bedeutet, einen RemoteIntentService mit passender Widget-ID zu starten, ist das recht einfach durch Aufruf der startService-Methode zu bewerkstelligen, ähnlich wie in der onUpdate-Methode des AppWidgetProviders. Das Ergebnis, nachdem mehrere Widgets mit unterschiedlichen Farbeinstellungen hinzugefügt worden sind, sehen Sie in Abbildung 6. 

Um die pro Widget vergebene Konfiguration auch wieder zu löschen, sollte nun auch die onDelete-Methode des Widgets benutzt werden. Hier kann die Konfiguration eines Widgets in den SharedPreferences gelöscht werden:

public void onDeleted(Context context, int[] appWidgetIds) {
  super.onDeleted(context, appWidgetIds);
  for (int i : appWidgetIds)
  {  
    String key = "WIDGET_" + i + "_COLOR";
    SharedPreferences prefs = context.getSharedPreferences("settings", Activity.MODE_PRIVATE);
    Editor editor = prefs.edit();
    editor.remove(key);
    editor.commit();  
  }

Zusammenfassung

Android App Widgets sind eine Besonderheit der Android-Plattform und sowohl bei den Usern als auch bei den Entwicklern sehr beliebt. Bevor Sie sich ans Coden machen, sollten Sie wichtige Überlegungen wie die Größe des Widgets, das UI sowie die Updatefrequenz anstellen. Die Updatefrequenz ist ein wichtiger Faktor und sollte möglichst gering gewählt werden, um die Batterie zu schonen. Ein ansprechendes UI und der maßvolle Umgang mit dem vorhandenen Platz auf den Home Screens sind ebenfalls wichtig, um bei den Usern gut anzukommen. Richtig angewandt stellen die Android App Widgets eine große Chance dar. App Widgets sind ein idealer Einstiegspunkt in die eigentliche App und sollten in jede Planung einer Android App mit einbezogen werden.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -