Widget con Pulsanti su Android
L’obbiettivo è creare un semplice widget, contenete due pulsanti: il primo inizializzerà una notifica nella barra delle notifiche, il secondo avvierà un Activity.
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
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
Iniziamo introducendo nell’AndroidManifest.xml 2 sezioni (evidenziate nel codice)
<!--?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
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
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.
AppWidgetProviderInfo: button_widget_provider.xml
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…
&lt;?xml version="1.0" encoding="utf-8"?&gt; &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" /&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
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
Righe 20-41: Metodo OnUpdate
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
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
Riga 75: Super.OnReceive
[da approfondire]
- 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.
Ci manca sono da definire i layout per il widget e la classe Activity, ma possiamo già considerare la parte difficile terminata.
Activity:
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(); } }
Le View Utilizzate
main.xml
E’ Il layout utilizzato nel widget.
&lt;?xml version="1.0" encoding="utf-8"?&gt; &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"&gt; &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"/&gt; &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"/&gt; &lt;/LinearLayout&gt;
configure.xml
E’ il layout utilizzato nell’Activity ClickOneActivity
&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"&gt; &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"/&gt; &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"/&gt; &lt;/TableLayout&gt;
E’ il nostro punto di partenza in quanto in esso vengono definite le prime generalità sull’App che stiamo andando a creare.