A developer weblog

 yet another
Promemoria   Articoli
  • RSS Feed
  •   
  • LinkedIn
  • Google+
  • Facebook
09
Oct

Widget con Pulsanti su Android

By massimo|Android

L’obbiettivo è creare un semplice widget, contenete due pulsanti: il primo inizializzerà una notifica nella barra delle notifiche, il secondo avvierà un Activity.

Widget for Android

Indice

  • Generalità sull’App Widget
  • Android Manifest: AndroidManifest.xml
  • AppWidgetProviderInfo: button_widget_provider.xml
  • AppWidgetProvider
  • Activity
  • Le View
  • Riferimenti e Allegati

Generalità sull’App Widget

Widget: Riferimento su Android Developers →

Per Creare un widget abbiamo bisogno di 3 cose:

  • AppWidgetProvider : Una Classe Java che implementa i metodi del nostro widget
  • AppWidgetProviderInfo : Un Oggetto Java che descrive i metadati std per il nostro Widget.
  • Una View Per l’interfaccia grafica iniziale.

Android Manifest: AndroidManifest.xml

Android Manifest: Riferimento su Android Developers →

Iniziamo introducendo nell’AndroidManifest.xml 2 sezioni (evidenziate nel codice)

AndroidManifest è un descrittore del nostro progetto, in esso sono definite tutte le Classi adoperate, viene letto per primo da Android per sapere con che programma abbiamo a che fare. In esso sono definite tutte le operazioni che può compiere l’applicazione, è il file che viene usato per creare la pagina pre-installazione (dove sono appunto elencate tutte le operazioni che l’applicativo può eseguire).
E’ il nostro punto di partenza in quanto in esso vengono definite le prime generalità sull’App che stiamo andando a creare.
<!--?xml version="1.0" encoding="utf-8"?-->
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="tests.ButtonWidget"
      android:versionCode="1"
      android:versionName="1.0">
    <uses-sdk android:minSdkVersion="10" />

    <application android:icon="@drawable/icon" android:label="@string/app_name">

 		<!-- Broadcast Receiver that will process AppWidget updates -->
		<receiver android:name=".ButtonWidget" android:label="@string/app_name">
			<intent-filter>
				<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
				<!-- Broadcast Receiver that will also process our self created action -->
				<action android:name="tests.HelloWidget.ButtonWidget.ACTION_WIDGET_RECEIVER"/>
			</intent-filter>
			<meta-data android:name="android.appwidget.provider"
				android:resource="@xml/button_widget_provider" />
		</receiver>

		<!-- this activity will be called, when we fire our self created ACTION_WIDGET_CONFIGURE -->
		<activity android:name=".ClickOneActivity">
			<intent-filter>
				<action android:name="tests.HelloWidget.ButtonWidget.ACTION_WIDGET_CONFIGURE"/>
			</intent-filter>
		</activity>

    </application>
</manifest>
Righe 12-20: Definizione del WidgetProvider
Presentiamo al Sistema Operativo la classe ButtonWidget, definendola come in grado di ricevere gli intent APPWIDGET_UPDATE e ACTION_WIDGET_RECEIVER ( è quindi un Broadcast Receiver), quest’ultimo è un intent interno al nostro progetto (La sua radice è quella del nostro Porgettoandroid:name=”tests.HelloWidget.ButtonWidget.***”).

Non Abbiamo ancora specificato al Sistema Operativo che la suddetta classe è uno widget (La distinzione sarà effettuata nel momento in cui andremo a definire la classe, ereditando la classe “AppWidgetProvider”)
In effetti non c’è bisogno di definire il nostro applicativo come widget in questa sede, pensiamo sempre all’interfaccia pre-installazione,caricata leggendo questi dati, dove NON viene specificato se l’applicazione che stiamo installando è un widget, un servizio, una singola attività o altro.

Il tag <meta-data> con name=”android.appwidget.provider” inizializza la risorsa AppWidgetProviderInfo.

Righe 23-27: Definizione di una Activity accessoria
Inizializziamo qui l’attività che verrà avviata con il secondo pulsante. Questo componente è definito come in grado di ricevere l’intent ACTION_WIDGET_RECEIVER

L’attività verrà avviata tramite la chiamata dell’Intent ACTION_WIDGET_CONFIGURE.In questo file, definiamo infatti che la nostra attività può ricevere questo intent.
Gli Intent sono chiamate di broadcast effettuate a livello di sistema, dal sistema stesso. Le applicazioni in grado di gestire gli intent chiamati verranno attivate.
Intents and Intent Filters: Riferimento su android developer →
L’Oggetto Intent: Riferimento su android developer →

AppWidgetProviderInfo: button_widget_provider.xml

AppWidget ProviderInfo: Riferimento su Android Developers →

Adesso vediamo un’altro file descrittivo, passiamo a definire il nostro AppWidgetProviderInfo, cioè quell’oggetto che descrive i metadati per un App Widget, come ad esempio il layout iniziale dell’App Widget, la frequenza di aggiornamento, etc…

&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
	android:minWidth="146dip"
	android:minHeight="72dip"
	android:updatePeriodMillis="0"
	android:initialLayout="@layout/main"
/&amp;gt;

In questo descrittore xml viene definito:
le dimensioni: Espresse in “dip” (Density Independent Pixels) Px relativi ad uno schermo di 160dpi quindi 1px equivale a 1 dip su uno schermo a 160dpi.
il Periodo di update: 0 indica che il nostro widget non si aggiornarà a intervalli di tempo regolari, ma solo al momento della creazione.
initialLayout: La View iniziale assegnata al nostro widget. (Tutte le view sono definite in fondo alla pagina)

Dopo aver definito le linee base della nostra applicazione passiamo a definire nel dettaglio i metodi della classe AppWidgetProvider.

AppWidgetProvider: Classe ButtonWidget ButtonWidget.java

AppWidget Provider: Riferimento su Android Developers →
package tests.ButtonWidget;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.RemoteViews;
import android.widget.Toast;

public class ButtonWidget extends AppWidgetProvider {
	
	public static String ACTION_WIDGET_CONFIGURE = "ConfigureWidget";
	public static String ACTION_WIDGET_RECEIVER = "ActionReceiverWidget";
	
	@Override
	public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
		Toast.makeText(context, "OnUpdate", Toast.LENGTH_SHORT).show();
		
		RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.main);
		
		// First Intent
		Intent configIntent = new Intent(context, ClickOneActivity.class);
		configIntent.setAction(ACTION_WIDGET_CONFIGURE);
		
		// Second Intent
		Intent active = new Intent(context, ButtonWidget.class);
		active.setAction(ACTION_WIDGET_RECEIVER);
		active.putExtra("msg", "Message for Button 1");
		
		PendingIntent actionPendingIntent = PendingIntent.getBroadcast(context, 0, active, 0);
		PendingIntent configPendingIntent = PendingIntent.getActivity(context, 0, configIntent, 0);
		
		remoteViews.setOnClickPendingIntent(R.id.button_one, actionPendingIntent);
		remoteViews.setOnClickPendingIntent(R.id.button_two, configPendingIntent);
		
		appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);
	}
	
	@Override
	public void onReceive(Context context, Intent intent) {
			Toast.makeText(context, "Receive: "+intent.getAction(), Toast.LENGTH_SHORT).show();

			// v1.5 fix that doesn't call onDelete Action
			final String action = intent.getAction();

			if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) {
				final int appWidgetId = intent.getExtras().getInt(
				AppWidgetManager.EXTRA_APPWIDGET_ID,
				AppWidgetManager.INVALID_APPWIDGET_ID);
				if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
					this.onDeleted(context, new int[] { appWidgetId });
				}
			} else {
				// check, if our Action was called
				if (intent.getAction().equals(ACTION_WIDGET_RECEIVER)) {
					String msg = "null";
					try {
						msg = intent.getStringExtra("msg");
					} catch (NullPointerException e) {
						Log.e("Error", "msg = null");
					}
					Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
					PendingIntent contentIntent = PendingIntent.getActivity(context, 0, intent, 0);
					NotificationManager notificationManager =
						(NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
					Notification noty = new Notification(R.drawable.icon, "Button 1 clicked",
					System.currentTimeMillis());
					noty.setLatestEventInfo(context, "Notice", msg, contentIntent);
					notificationManager.notify(1, noty);
				}
				super.onReceive(context, intent);
			}
	}
}

Righe 16-17: Definizione degli Intent
Per prima cosa andiamo a definire i due Eventi (Intent) già utilizzati nel file Manifest.xml
Righe 20-41: Metodo OnUpdate
La funzione più importante, onUpdate, viene eseguita all’avvio del widget e regolarmente come scadenziato nel parametro android:updatePeriodMillis nel WidgetProviderInfo.xml definito poco sopra.
Dopo aver lanciato un Toast (riga 21 – solo per divertimento 🙂 ) e aver definito la view sul quale lavoreremo R.layout.main (riga 23), passiamo a inizializzare gli intent da assegnare ai bottoni.
A riga 26 creiamo un intent tramite una dichiarazione esplicita (cioè passando il contesto e un componente che fornirà la classe esatta da eseguire; serve per definire principalmente intent ad uso interno dell’applicativo che stiamo creando Vedi Intent:Intent Resolution in Android Developer) e al rigo successivo applichiamogli la nostra azione ACTION_WIDGET_CONFIGURE.
L’activity ClickOneActivity, essendo stata definita nel file Manifest, come ricevitrice del metodo appena definito, sarà richiamata in automatico dal sistema non appena questo intent verrà lanciato (senza bisongo di ulteriore codice).
Dopo facciamo lo stesso per l’Intent ACTION_WIDGET_RECEIVER specificando però che il componente che risolverà l’intent sarà la classe ButtonWidget (se stesso).Inoltre aggiungiamo un dato extra al nostro evento, che sarà poi estratto e utilizzato nella procedura chiamata.
Da riga 34 a 38 assegnamo gli intent come eventi Click eseguibili dai due bottoni nella View. Lo facciamo tramite l’utilizzo di PendingIntent.
Dando un PendingIntent a un’altra applicazione, gli si concede il diritto di eseguire l’operazione che è stata specificata come se l’altra applicazione fosse parte dell’applicazione chiamante (con le stesse autorizzazioni e identità).
L’ultima riga ci serve per “committare” le modifiche al nostro widget.
Riga 44: Metodo OnReceive
Il Motodo OnReceive è il metodo delegato a ricevere tutti gli Intent (quelli ricevibili dal componente) e viene utilizzato quando necessitiamo di ricevere ed elaborare Intent diversi da quelli standard.

Alcuni dei principali Intent di sistema sono:
ACTION_APPWIDGET_DELETED
ACTION_APPWIDGET_DISABLED
ACTION_APPWIDGET_ENABLED
ACTION_APPWIDGET_UPDATE

Nel momento in cui viene ricevuto un intent creiamo un toast (riga 45 – Adoro i toast!) e passiamo ad eseguire le azioni giuste a seconda del tipo di intent ricevuto, instaurando un semplice ciclo if-else.
Se il metodo intent è ACTION_APPWIDGET_DELETED ( metodo richiamato al momento in cui il widget viene cestinato) applichiamo il fix per correggere il bug eliminazione su Android 1.5.
Se il metodo intent ricevuto è ACTION_WIDGET_RECEIVER estriamo il dato extra definito in precedenza e creiamo una notifica tramite NotificationManager

Notification Manager: Riferimento su Android Developer →
L’Oggetto Notification: Riferimento su Android Developer →
Riga 75: Super.OnReceive
A riga 75 chiamiamo tramite la keyword Super il metodo OnReceive della classe padre.
[da approfondire]
Ok! Ricapitoliamo:

  • Abbiamo creato il file manifest definendo il widget ed una Activity accessoria (sotto definita).
  • Abbiamo impostato le opzioni principali per il nostro widget tramite AppWidgetProviderInfo
  • Abbiamo creato la classe AppWidgetProvider (i comportamenti del widget) scrivendo:
    -Il metodo OnUpdate che alla creazione si occupa di creare i pulsanti e definire gli Intent che devono lanciare.
    -Il metodo OnReceive per ricevere l’Intent che deve creare la notifica nella barra delle notifiche.
Per aprire l’activity non c’è bisogno di scrivere del codice, in quanto verrà attivata in automatico dal sistema operativo nel momento in cui viene chiamato l’Intent relativo.

Ci manca sono da definire i layout per il widget e la classe Activity, ma possiamo già considerare la parte difficile terminata.

Activity:

Activity: Riferimento su Android Developers →

Definiamo qui l’Activity referenziata a riga 26 dell’AppWidget Provider, sopra definito.
Questa è l’Attività che viene aperta da Sistema Operativo, nel momento in cui con il nostro Bottone lanciamo l’intent ACTION_WIDGET_CONFIGURE.
Abbiamo già associato l’intent a questa activity nel file AndroidManifest.xml.

package tests.HelloWidget;

import android.app.Activity;
import android.os.Bundle;
import android.widget.Toast;

public class ClickOneActivity extends Activity {
	
		@Override
		protected void onCreate(Bundle savedInstanceState) {
			super.onCreate(savedInstanceState);
			// change to our configure view
			setContentView(R.layout.configure);
			// don't call 'this', use 'getApplicationContext()', the activity-object is
			// bigger than just the context because the activity also stores the UI elemtents
			Toast.makeText(getApplicationContext(), "We are in ClickOneActivity",
			Toast.LENGTH_SHORT).show();
		}

}
In pratica, non facciamo niente su questa Activity: settiamo il layout R.layout.configure e facciamo un toast per indicare che siamo nella Activity.

Le View Utilizzate

View: Riferimento su Android Developers →

main.xml

View Main.xml

E’ Il layout utilizzato nel widget.

&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="fill_parent"
	android:orientation="horizontal"
	android:background="@drawable/widget_bg_normal"
	android:layout_gravity="center"
	android:layout_height="wrap_content"&amp;gt;
	
	&amp;lt;Button android:text="B1" android:id="@+id/button_one"
		android:layout_gravity="center_horizontal|center"
		android:layout_height="fill_parent"
		android:layout_width="wrap_content"/&amp;gt;
	
	&amp;lt;Button android:text="B2" android:id="@+id/button_two"
		android:layout_gravity="center_horizontal|center"
		android:layout_height="fill_parent"
		android:layout_width="wrap_content"/&amp;gt;
	
&amp;lt;/LinearLayout&amp;gt;

configure.xml

View Main.xml

E’ il layout utilizzato nell’Activity ClickOneActivity

&amp;lt;TableLayout android:id="@+id/TableLayout01"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent"
	xmlns:android="http://schemas.android.com/apk/res/android"&amp;gt;
	
		&amp;lt;TextView android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:id="@+id/TextOne"
			android:text="Text"
			android:textColor="@android:color/white"
			android:textSize="15dip"
			android:textStyle="bold"
			android:layout_marginTop="5dip"/&amp;gt;
		
		&amp;lt;EditText android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:id="@+id/txtFieldId"
			android:clickable="true"
			android:inputType="number"
			android:layout_marginTop="5dip"
			android:layout_marginBottom="20dip"/&amp;gt;
		
&amp;lt;/TableLayout&amp;gt;

Riferimenti e Allegati

 Archivio del Progetto Eclipse completo. (HelloWidget.tar)
 Widget su Android Developers →
 Guida originale su HelloWorld Android →
Tagged as: activity, android, AppWidget, AppWidgetProvider, AppWidgetProviderInfo, barra delle notifiche, notification manager, Toast, widget
View More Posts:
  • ←
  • →