Przygotujemy aplikację-widget, którego jedyną funkcją będzie pokazywanie zegara. 1. Z menu File, New, Other... tworzymy projekt typu Android Application Project (jak zwykle). a. API 4 (Android 1.6) b. Nazwa: ZegarekWidget c. Przestrzeń nazw: pl.umk.fizyka.zegarekwidget d. W kolejnym kroku kreatora odznaczamy "Create Activity" - aplikacja nie będzie miała aktywności. 2. Po uwtorzeniu projektu, musimy do niego dodać klasę: menu File, New, Class a. Pakiet: pl.umk.fizyka.zegarekwidget b. Nazwa klasy: Main c. Dziedziczy po (pole Superclass): android.appwidget.AppWidgetProvider e. Klikamy Finish. Uzyskamy: package pl.umk.fizyka.zegarekwidget; import android.appwidget.AppWidgetProvider; public class Main extends AppWidgetProvider { } 3. Klasę ZegarekWidget uzupełniamy o metodę onUpdate, z package pl.umk.fizyka.zegarekwidget; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.ComponentName; import android.content.Context; import android.widget.RemoteViews; //wygodniejszy od BroadcastReceiver w przypadku widgetow public class Odbiorca extends AppWidgetProvider { @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { ComponentName tenWidget = new ComponentName(context, Odbiorca.class); int[] wszystkieWidgety = appWidgetManager.getAppWidgetIds(tenWidget); for (int widget : wszystkieWidgety) { DateFormat format = SimpleDateFormat.getTimeInstance(SimpleDateFormat.MEDIUM, Locale.getDefault()); //import java.text.DateFormat; RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.main); rv.setTextViewText(R.id.textView1, format.format(new Date())); appWidgetManager.updateAppWidget(widget, rv); } } } Poza onUpdate są metody onEnabled, onDisable i onDeleted. 4. Tworzymy plik XML opisujący widok = interface okna: a. Z menu File, New, Other... -> Android XML Layout File b. W kreatorze podajmy nazwę pliku main.xml c. zaznaczmy root element: LinearLayout d. Finish. e. W pliku XML ustalmy konktetną wysokość i szerokość okna i wstawmy zegar analogowy oraz TextView, na którym będziemy dodatkowo wyświetlać czas: f. Używany łańcuch definiujemy w res/values/strings.xml ZegarekWidget --:--:-- Widget nie może używać każdego widoku (możliwe to FrameLayout, LinearLayout i RelativeLayout). Dopuszczalne kontrolki to AnalogClock, Button, Chromometer, ImageButton, ImageView, ProgressBar i TextView. Od Androida 3.0 widoków i kontrolek jest więcej. Obrazek background.png pobieramy z http://developer.android.com/guide/practices/ui_guidelines/widget_design.html lub bezpośrednio z http://kasperholtze.com/wp-content/uploads/2009/11/background.png i zapisujemy do res/drawable-hdpi. 5. Definiujemy plik XML opisującego dostawcę: a. W katalogu res tworzymy podkatalog xml, b. w nim tworzymy plik dostawca.xml: Aktualizacja ma następować co sekundę (1000 ms). Co taki czas ma być wywoływana metoda onUpdate. To o wiele za często. szybko padnie bateria, bo telefon nie bedzie zasypial, ale my od razu bedziemy widzieli efekt dzialania widgetu (w teorii). Tak naprawdę system nie gwarantuje odświeżania widgetu co taki czas. Może to być znacznie rzadziej!!! W szczególności minimalny okres odświeżania to 30 min = 1800000 ms. Inna metoda aktualizacji widgetu korzysta z AlarmManager (zob. poniżej i http://www.vogella.com/articles/AndroidWidgets/article.html). 6. Modyfikujemy plik manifestu - rejestrujemy widget: Ten wpis oznacza, że rejestrujemy klasę Odbiorca jako odbiorce komunikat APPWIDGET_UPDATE i ustala metadane dla widgetu. 7. Uruchom widget w normalny sposób. Aplikacja się nie uruchomi (nie ma aktywności), ale zostanie umieszczona w emulatorze lub urządzeniu. Wówczas możemy ją umieścic na ekranie. Będzie dostępna wśród Widgetów pod nazwą Zegarek (label z manifestu). W zależności od urządzenia aplikacja może się nie odświeżać (dotyczy to tekstu, bo zegarek ma chyba własny wątek). Na emulatorze onUpdate w ogóle nie jest uruchamiana. Komentarz z http://kasperholtze.com/android/how-to-make-a-simple-android-widget/: Please note that this is a very bad practice to set updatePeriodMillis to a high frequency (or low value) like this. At every updatePeriodMillis the phone will be waken up to deliver the call to the receiver’s onUpdate which will effectively prevent your phone from sleeping at all. What you shall do is to implement the other widget events as well and properly catch the moment when your first widget is created (and the last is removed) and deliver your updates using a non-wakeup type AlarmManager periodic callback or an AsyncTask, from a separate service. Beside this there is an other minor problem, as updatePeriodMillis updates will not be synced to a whole second boundary, resulting in seconds displayed being potentially skewed by 0-999 milliseconds. ---------- Kliknięcie widgetu - odświeżenie. 8. Należy uzupełnić metodę onUpdate: public class Odbiorca extends AppWidgetProvider { @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { for (int widgetId : appWidgetIds) { DateFormat format = SimpleDateFormat.getTimeInstance(SimpleDateFormat.MEDIUM, Locale.getDefault()); //import java.text.DateFormat; RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.main); rv.setTextViewText(R.id.textView1, format.format(new Date())); //klikniecie (czy to najlepsze miejsce?) Intent intent = new Intent(context, Odbiorca.class); intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); rv.setOnClickPendingIntent(R.id.linearLayout1, pendingIntent); // appWidgetManager.updateAppWidget(widgetId, rv); } } } ------------------ Odświeżanie widgetu za pomocą usługi (AlarmManager) 9. Do katalogu pl.umk.fizyka.zegarekwidget dodaj klase: - nazwa: AktualizujWidgetService - klasa bazowa: android.app.Service - napisz abstrakcyjne metody (jest tylko onBind) 10. W tej klasie definiujemy metodę onStart: package pl.umk.fizyka.zegarekwidget; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.Timer; import java.util.TimerTask; import android.app.PendingIntent; import android.app.Service; import android.appwidget.AppWidgetManager; import android.content.Context; import android.content.Intent; import android.os.IBinder; import android.widget.RemoteViews; public class AktualizujWidgetService extends Service { Timer timer; @Override public void onStart(final Intent intent, int startId) { final Context context = this.getApplicationContext(); timer=new Timer(); timer.scheduleAtFixedRate( new TimerTask() { @Override public void run() { //znaczna czesc tego kodu powinna byc wylaczona //i uwspolniona z metoda Odbiorca.onUpdate!!! AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); int[] allWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS); //ComponentName tenWidget = new ComponentName(getApplicationContext(),Odbiorca.class); //int[] allWidgetIds2 = appWidgetManager.getAppWidgetIds(tenWidget); for (int widgetId : allWidgetIds) { DateFormat format = SimpleDateFormat.getTimeInstance(SimpleDateFormat.MEDIUM, Locale.getDefault()); //import java.text.DateFormat; RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.main); rv.setTextViewText(R.id.textView1, format.format(new Date())); //klikniecie Intent clickIntent = new Intent(context,Odbiorca.class); clickIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); clickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS,allWidgetIds); PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, clickIntent,PendingIntent.FLAG_UPDATE_CURRENT); rv.setOnClickPendingIntent(R.id.linearLayout1, pendingIntent); appWidgetManager.updateAppWidget(widgetId, rv); } }; }, 0, 1000); //co 1000 ms stopSelf(); super.onStart(intent, startId); } @Override public IBinder onBind(Intent arg0) { // TODO Auto-generated method stub return null; } } 11. Deklarujemy usługę w pliku manifestu: ... 12. I wreszcie zmieniamy metodę onUpdate widgetu (plik Odbiorca.java) tak, żeby ograniczała się do uruchamiania usługi: public class Odbiorca extends AppWidgetProvider { @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { ComponentName thisWidget = new ComponentName(context,Odbiorca.class); int[] allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget); Intent intent = new Intent(context,AktualizujWidgetService.class); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, allWidgetIds); context.startService(intent); } } Komentarz: The AlarmManager allows you to be more resource efficient and to have a higher frequency of updates. To use this approach you define a service and schedule this service via the AlarmManager regularly. This service updates the widget.