und wilkommem am Pfingstmontag zum weiterem Teil meiner Widget-Reihe.
Heute zeige ich euch, wie ihr mehrere Buttons in euer Widget einbauen könnt und jeweils mit einem Event belegt.
Dieses Tutorial setzt Kenntnisse in Widget-Entwicklung voraus und basiert auf meinem Beitrag Zeitliche- und Interaktive-Updates des Widgets.
Entwickelt und getestet mit:
- Eclipse Indigo Service Release 2
- Android SDK Tools rev. 17
- Android version 2.3.6
- MB860 (Atrix)
- ROM: Marko!s Algor!thm V0.5
Beginnen wir mit dem Layout, das ich diesmal benutzt habe.
Das wichtigste sind die Buttons!
layout/widget_view_sites.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="4dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="4dp"
android:background="@drawable/widget_background" >
<Button
android:id="@+id/buttonPrevious"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_marginLeft="4dp"
android:padding="8dp"
android:text="previous" />
<Button
android:id="@+id/buttonNext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_marginRight="4dp"
android:padding="8dp"
android:text="next" />
<TextView
android:id="@+id/textHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:layout_marginTop="4dp"
android:background="@drawable/widget_item_background_top"
android:padding="8dp"
android:text="Platzhalter header"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/textBody"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/buttonPrevious"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_below="@+id/textHeader"
android:layout_margin="4dp"
android:background="@drawable/widget_item_background"
android:padding="8dp"
android:text="Platzhalter body"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/textFooter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/buttonPrevious"
android:layout_alignTop="@+id/buttonPrevious"
android:layout_marginBottom="4dp"
android:layout_toLeftOf="@+id/buttonNext"
android:layout_toRightOf="@+id/buttonPrevious"
android:background="@drawable/widget_item_background"
android:padding="8dp"
android:text="Platzhalter footer"
android:textAppearance="?android:attr/textAppearanceSmall" />
</RelativeLayout>
drawable/widget_background.xml:
<?xml version="1.0" encoding="UTF-8"?>drawable/widget_item_background.xml:
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<stroke
android:width="4dp"
android:color="#FF666666" />
<gradient
android:angle="135"
android:endColor="#FF000000"
android:startColor="#FF000000" />
<corners android:radius="5dp" />
</shape>
<?xml version="1.0" encoding="UTF-8"?>drawable/widget_item_background_top.xml:
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<gradient
android:angle="45"
android:endColor="#EE0099CC"
android:startColor="#EE0099CC" />
</shape>
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<gradient
android:angle="45"
android:endColor="#EE0099CC"
android:startColor="#EE0099CC" />
<corners
android:topLeftRadius="4dp"
android:topRightRadius="4dp" />
</shape>
Wenn ich mir das anschaue, denk ich immer, das ist zuviel code für sowenig Auswirkung. Aber das sei wohl jedem Layoutdesigner selbst überlassen.
In der Manifest müssen die Action-Events als Intent-Filter eingetragen werden!
Bei mir sind es zwei Variablen aus der Klasse BasisAppWidgetProvider.
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.alexroid.widgetsample"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="9" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<receiver android:name="de.alexroid.widgetsample.BasisAppWidgetProvider" >
<intent-filter>
<action android:name="android.appwidget.action.
APPWIDGET_UPDATE" />
<action android:name="de.alexroid.widgetsample.BasisAppWidgetProvider.
ACTION_WIDGET_NEXT" />
<action android:name="de.alexroid.widgetsample.BasisAppWidgetProvider.
ACTION_WIDGET_PREVIOUS" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_view_sites" />
</receiver>
<service android:name="de.alexroid.widgetsample.UpdateWidgetService" />
</application>
</manifest>
In der Konfigurationsdatei steht wie gewohnt das Layout, das geladen wird.
xml/widget_view_sites.xml:
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/widget_view_sites"
android:minHeight="170dp"
android:minWidth="250dp"
android:updatePeriodMillis="1800000" >
</appwidget-provider>
Kommen wir zum WidgetProvier. Dieser enthält zwei verschiedene PendingIntents, diese werden ausgelöst, sobald die Buttons gedrückt worden sind.
BasisAppWidgetProvider:
package de.alexroid.widgetsample;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.widget.RemoteViews;
public class BasisAppWidgetProvider extends AppWidgetProvider {
//custom action-Bezeichnungen für zwei verschiedene Events
public static final String ACTION_WIDGET_NEXT = "next";
public static final String ACTION_WIDGET_PREVIOUS = "previous";
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
//das geladene Layout
RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
R.layout.widget_view_sites);
// Build the intent to call the service
Intent intent = new Intent(context.getApplicationContext(),
UpdateWidgetService.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
// Update the widgets via the service
context.startService(intent);
//Unterschiedliche PendigIntens für die verschiedene Buttons mit Events
//Event "weiter"
Intent activeNext = new Intent(context, BasisAppWidgetProvider.class);
activeNext.setAction(ACTION_WIDGET_NEXT);
activeNext.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
PendingIntent actionNextPendingIntent = PendingIntent.getBroadcast(
context, 0, activeNext, PendingIntent.FLAG_UPDATE_CURRENT);
//die dazugehörige Button-ID
remoteViews.setOnClickPendingIntent(R.id.buttonNext,
actionNextPendingIntent);
//Event "zurück"
Intent activePrevious = new Intent(context,
BasisAppWidgetProvider.class);
activePrevious.setAction(ACTION_WIDGET_PREVIOUS);
activePrevious.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS,
appWidgetIds);
PendingIntent actionPreviousPendingIntent = PendingIntent.getBroadcast(
context, 0, activePrevious,
PendingIntent.FLAG_UPDATE_CURRENT);
//die dazugehörige Button-ID
remoteViews.setOnClickPendingIntent(R.id.buttonPrevious,
actionPreviousPendingIntent);
appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);
}
@Override
public void onReceive(Context context, Intent intent) {
if (intent != null && intent.getExtras() != null) {
int[] appWidgetIds = intent.getExtras().getIntArray(
AppWidgetManager.EXTRA_APPWIDGET_IDS);
// Build the intent to call the service
Intent intentService = new Intent(context.getApplicationContext(),
UpdateWidgetService.class);
intentService.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS,
appWidgetIds);
intentService.addFlags(PendingIntent.FLAG_UPDATE_CURRENT);
if (intent.getAction() != null
&& (intent.getAction().equals(ACTION_WIDGET_NEXT) || intent
.getAction().equals(ACTION_WIDGET_PREVIOUS))) {
intentService.putExtra("button_action", intent.getAction());
// Nur Update durchführen, wenn einer der knöpfe gedrück worden ist
context.startService(intentService);
} else {
super.onReceive(context, intent);
}
}
}
}
Kommen wir zum Service, der das Widget im Hintergrund aktualisiert.
Dazu muss ich vorweg sagen, dass ich eine Datenerhaltungs- bzw. Datenbereitstellungsschicht benutze. Die Klasse DataController symbolisier diese Schicht und beinhaltet hardcodiert alle Datenobjekte, die für die Anzeige der Texte notwendig sind. Ein Datenobject also die Klasse DataObject beinhaltet den Text für Header, Body und Footer.
UpdateWidgetService:
package de.alexroid.widgetsample;
import java.util.Arrays;
import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.IBinder;
import android.widget.RemoteViews;
public class UpdateWidgetService extends Service {
private static final String PREF_PAGE = "page";
@Override
public IBinder onBind(Intent arg0) {
return null;
}
@Override
public void onStart(Intent intent, int startId) {
if (intent != null) {
ComponentName componentName = new ComponentName(getBaseContext(),
BasisAppWidgetProvider.class);
String button_action = intent.getStringExtra("button_action");
AppWidgetManager appWidgetManager = AppWidgetManager
.getInstance(this.getApplicationContext());
int[] allWidgetIds = appWidgetManager
.getAppWidgetIds(componentName);
// NPE beim Löschen des Widgets vom Home-Screen
if (allWidgetIds != null) {
//unser Layout als RemoteView beziehen
RemoteViews remoteViews = new RemoteViews(this.getBaseContext()
.getPackageName(), R.layout.widget_view_sites);
//laden und abspeichern der Seite, die geladen ist.
SharedPreferences preferences = getSharedPreferences(getClass()
.getSimpleName(), 0);
int pagePosition = preferences.getInt(PREF_PAGE, 0);
DataObject dataObject;
//Event "weiter"
if (button_action != null
&& button_action
.equalsIgnoreCase(BasisAppWidgetProvider.ACTION_WIDGET_NEXT)
&& pagePosition < 2) {
++pagePosition;
dataObject = DataController.getInstance()
.getDataAtPostition(pagePosition);
//Event "zurück"
} else if (button_action != null
&& button_action
.equalsIgnoreCase(BasisAppWidgetProvider.ACTION_WIDGET_PREVIOUS)
&& pagePosition > 0) {
--pagePosition;
dataObject = DataController.getInstance()
.getDataAtPostition(pagePosition);
//kein Event, dennoch laden/aktualisieren
} else {
dataObject = DataController.getInstance()
.getDataAtPostition(pagePosition);
}
//Wenn Daten vorhanden, den passenden Views zuordnen
//und neue aktuelle Seite speichern
if (dataObject != null) {
String header = dataObject.getHeader();
String body = dataObject.getBody();
String footer = dataObject.getFooter();
remoteViews.setTextViewText(R.id.textHeader, header);
remoteViews.setTextViewText(R.id.textBody, body);
remoteViews.setTextViewText(R.id.textFooter, footer);
Editor editor = preferences.edit();
editor.putInt(PREF_PAGE, pagePosition);
editor.commit();
}
//Pendant zu invalidate
appWidgetManager.updateAppWidget(allWidgetIds, remoteViews);
}
}
stopSelf(startId);
super.onStart(intent, startId);
}
}
DataController:
package de.alexroid.widgetsample;DataObject:
public class DataController {
private static DataController instance;
private DataObject[] dataObjects;
public DataController() {
this.dataObjects = new DataObject[3];
this.dataObjects[0] = new DataObject("Kopf", "Körper", "Fuß");
this.dataObjects[1] = new DataObject("head", "body", "foot");
this.dataObjects[2] = new DataObject("rdm1", "rdm2", "rdm3");
}
public DataObject getDataAtPostition(int position) {
DataObject res;
if (position <= this.dataObjects.length) {
res = dataObjects[position];
} else {
res = null;
}
return res;
}
public static DataController getInstance() {
if (instance == null) {
instance = new DataController();
}
return instance;
}
}
package de.alexroid.widgetsample;
public class DataObject {
private String header;
private String body;
private String footer;
public DataObject(String header, String body, String footer) {
this.header = header;
this.body = body;
this.footer = footer;
}
public String getHeader() {
return header;
}
public String getBody() {
return body;
}
public String getFooter() {
return footer;
}
@Override
public String toString() {
return String.format("%s#header:%s;body:%s;footer:%s", getClass()
.getSimpleName(), header, body, footer);
}
}
Das ist viel Code für ein Resultat... das ich gebraucht habe :)
Wie steht es mit euch, fehlt da was?
Hier Screenshot, wie es bei mir aussieht.
Damit verabschiede ich mich für den Tag und wünsche euch noch frohe restliche Pfingsten.
regards
Alexander Fink
Keine Kommentare:
Kommentar veröffentlichen