Android homescreen widgets bouwen

Door Wiebbe op vrijdag 19 juni 2009 09:59 - Reacties (11)
CategorieŽn: android, java, Views: 5.819

Android
Tis alweer een hele tijd geleden sinds ik gepost heb. Helaas is het onderhouden van een Blog een rare taak en als je niks te schrijven hebt (of iets interessants dan, want Faust kan er ook wat van :P) kan je beter ook maar niks neer zetten.

Sinds kort heb ik een Android G1 van T-mobile in mijn bezit. Ik heb hem niet geroot, maar je kan er nog steeds hele leuke dingen voor schrijven. Het eerste wat ik deed was hem updaten naar Cupcake 1.5.

Leuk, want nu kan je widgets op je homescreen plaatsen, wat het meteen een stuk uitgebreider maakt!

Maar nu komt het, ik vond hem in het nederlands wel prettig (ondanks dat ik al mijn WinMo toestellen in het engels had door de roms die ik flashde) maar volgens mij zijn bijna 99% van alle widgets in het engels gemaakt :(

Heb je een hele mooie digitale klok (DigitalClock Widget) te pakken, flikkert die neer op je home screen krijg je inderdaad een mooie digitale klok. Maar, de datum en dag van de week zijn in het engels :(

Ook een hele mooie SMS Counter, waarbij je het sms icoontje van berichten/messages vervangt met een icoon waarbij je een getal zit komen te staan als je nieuwe ongelezen berichten hebt. Leuk, maar hoe heet hij dan? Messages ipv Berichten. Nou kan je dit simpel oplossen door hem weer in het engels te zetten, of je schrijft gewoon alles zelf! (Zoals het een tweaker betaamt!)

Vreemd genoeg vind ik de documentatie over widgets bouwen redelijk beperkt, maar een simpele digitale klok moest toch wel lukken. En behold, hier is dan mijn digitale klok!

Eclipse
Ik schrijf alles in Eclipse en uiteraard in java. Nou programmeer ik zelf over het algemeen in PHP of C#. Tijdens mijn studie heb ik wel veel java gekregen (waardoor ik er een gruwelijke hekel aan gekregen heb ;)) maar het was roestig. Gelukkig lijkt het erg veel op C# en alle programmeer methodes zijn zowat precies hetzelfde. Ik hoefde eigenlijk niks op een andere manier te doen.

Maar ja, widgets! Een widget in Android wordt op een speciale manier gemaakt. (Vind ik dan!) Het gedeelte op het Homescreen is niks meer dan een frontend met een "RemoteView". Deze remote view doet op een door jou ingestelde update tijd een request naar een class die jij opgeeft. Die class wordt dan aangeroepen en kan de widget gaan updaten etc..

Ik zal hier gewoon simpel weg mijn klok laten zien. Anyway, on to the code!

In principe heb je maar een aantal bestanden nodig. Ik zal hier in het kort uitlichten wat we hebben:
  • DigitaleKlok.java
  • res/layout/main.xml
  • res/layout/widget.xml
  • res/xml.widget_info.xml
  • AndroidManifest
Het eerste wat we moeten aanpassen is de AndroidManifest.xml, hiermee laten we Android weten dat de applicatie een widget is en waar de widget moet kijken voor zijn update class.

XML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.wiebbe.DigitaleKlok"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
            <receiver android:name="DigitaleKlok" >
                <intent-filter>
                    <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
                </intent-filter>
                <meta-data android:name="android.appwidget.provider"
                           android:resource="@xml/widget_info" />
            </receiver>
    </application>
    <uses-sdk android:minSdkVersion="3" />
</manifest>


Zoals je ziet declareer ik hier een <receiver>. De name van de receiver geeft aan welke class moet worden aangeroepen die van het type AppWidgetProvider moet zijn.

Doormiddel van deze receiver en de intent filter declareer je dat hij moet reageren op de APPWIDGET_UPDATE action. Daarnaast geef je aan dat dat hij voor de appwidget.provider de widget_info.xml moet uitlezen.

In de widget_info.xml staat de volgende informatie:

XML:
1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="144dip"
    android:minHeight="72dip"
    android:updatePeriodMillis="60000"
    android:initialLayout="@layout/widget"
    />



Hier mee geef je aan hoe groot hij is, volgens de android blog moet je het als volgt doen:

code:
1
Minimum size in dip = (Number of cells * 74dip) - 2dip



De update tijd is netjes in miliseconden, die nu dus staat op 60 seconden, elke minuut updaten.

Ook geven aan hoe hij eruit moeten te komen zien:

widget.xml

XML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
<TextView  
    android:id="@+id/Klok"
    android:layout_width="144dp" 
    android:textSize="52dp"
    android:textStyle="bold"
    android:shadowColor="#000"
    android:shadowDx="1.1"
    android:shadowDy="1.1"
    android:text="tijd" 
    android:layout_gravity="center" 
    android:shadowRadius="1.1" android:gravity="top|center_horizontal" android:layout_height="60dp"/>
<TextView  
    android:id="@+id/Datum"
    android:layout_width="144dp" 
    android:textSize="20dp"
    android:textStyle="bold"
    android:shadowColor="#000"
    android:shadowDx="1.1"
    android:shadowDy="1.1"
    android:shadowRadius="1.1"
    android:text="datum"
    android:layout_gravity="center"
    android:gravity="center_horizontal"
    android:layout_height="28dp"/>
</LinearLayout>



Hiermee krijg je een 2x1 cells widget met daarin een grote tekst die opgebouwd is uit een grote tekst voor de tijd en een kleine tekst voor de datum.

Nu moeten we nog de DigitaleKlok class schrijven die dus een AppWidgetProvider moet overerven en moet reageren op de UPDATE intent van de receiver.


Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.wiebbe.DigitaleKlok;

import java.util.Calendar;

import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.widget.RemoteViews;

public class DigitaleKlok extends AppWidgetProvider {

    private int mHour;
    private int mMinute;
    private int mMaand;
    private int mDag;
    private int mJaar;
    
    private static String pad(int c) {
        if (c >= 10)
            return String.valueOf(c);
        else
            return "0" + String.valueOf(c);
    }
    
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        final int N = appWidgetIds.length;

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

            RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget);

            // get the current time
            final Calendar c = Calendar.getInstance();
            mHour = c.get(Calendar.HOUR_OF_DAY);
            mMinute = c.get(Calendar.MINUTE);
            
            mMaand = c.get(Calendar.MONTH);
            mDag = c.get(Calendar.DATE);
            mJaar = c.get(Calendar.YEAR);
            
            views.setTextViewText(R.id.Klok,  new StringBuilder()
            .append(pad(mHour)).append(":")
            .append(pad(mMinute)));

            views.setTextViewText(R.id.Datum,  new StringBuilder().append(pad(mDag)).append("-").append(pad(mMaand)).append("-").append(pad(mJaar)));
            
            appWidgetManager.updateAppWidget(appWidgetId, views);
        }
    }
}



Zoals je hier ziet, zit er 1 functie (nou twee, maar eentje is gewoon om de tijd netjes te padden..) die voor de updates zorgt. De BroadcastReceiver geeft dus een seintje aan de DigitaleKlok class dat hij de OnUpdate moet draaien.

Omdat je meerdere widgets kan hebben die allemaal hier gebruik van maken zorg ik ervoor dat alle widget geupdate worden. Het kan veel netter natuurlijk, op zich zou ik niet de tijd/data elke keer moeten ophalen in de loop, maar whatever ;)

Als je deze code nu runt en de widget plaatst krijg je heel netjes een klok met datum in het beeld a la zo:

Digitale Klok widget

De code die ik heb gebruikt heb ik voornamelijk hier vandaan:

http://android-developers.blogspot.com/search/label/Widgets

Het gehele project vind je hierzo:

DigitaleKlokSrc.zip

[edit]
Aangezien we het natuurlijk deden om het nederlands te maken, heb ik deze localisation zooi erin geknalt en ik krijg nu een hele mooie nederlandse datum tekst.

Java:
1
2
views.setTextViewText(R.id.Datum,    new StringBuilder()
            .append(DateFormat.format("EE, dd MMM yy", c)));

Volgende: Android DigitaleKlok widget Redux 06-'09 Android DigitaleKlok widget Redux
Volgende: IDisposable voor Excel Com 03-'09 IDisposable voor Excel Com

Reacties


Door Tweakers user Rhapsody, vrijdag 19 juni 2009 10:59

Wel erg koel dat het schrijven van Widgets (Todayscreen plugins) voor je foon zo eenvoudig is. (relatief dan)

Voor WM is het een hel als je geen C++ kunt. In .NET is het zo goed als onmogelijk. Er is wel een oud voorbeeld op de MSDN, maar dat werkt voor geen meter en is volgens de beschrijving ook nog erg beperkt.

Door Tweakers user Wiebbe, vrijdag 19 juni 2009 11:09

Ja inderdaad, ik heb ook veel geprobeerd om te coden voor WinMo maar om gebruik te maken van het framework of de hardware features van zo'n toestel (een HTC Diamond) is echt een ramp gewoon..

Dit is echt easy peasy :P

Door Tweakers user Mad-Monkey, vrijdag 19 juni 2009 11:34

mooi project, net mijn vriendin het laten lezen (geen programmeer kennis) en die kon het allemaal goed volgen dus duidelijke blog ook :). Ik moet zeggen, dit zo lezend nijg ik nu toch weer naar een android telefoon als opvolger van mijn (stokoude) D600

Door Tweakers user himlims_, vrijdag 19 juni 2009 11:34

Android is liev, maar de performance gaat erg hard achteruit zodra je wat meer dan de gemiddelde gebruiker geinstalleerd hebt; mp3 spelen en browsen tegelijk is onmogelijk bij mij. Helaas zitten er geen opties in om je installatie op te schonen, super os, moet alleen nog erg hard bijgeschaafd worden.

Door Tweakers user DRaakje, vrijdag 19 juni 2009 11:49

@himlims
Herkenbaar, ik zat er ook aan te denken om een andere rom erop te zetten, het schijnt dat er sommige builds zijn die echt veel sneller zijn. Volgens mij is het ook niet moeilijk, SDkaartje naar fat32 converten en dan rom erop en reboot.

Klinkt makkelijk :D

Door Tweakers user Snake, vrijdag 19 juni 2009 12:56

Just a tip:

Stel dat je in je onUpdate een request doet naar een database, dan kan je dat het beste doen door een service te starten:


Java:
1
2
3
4
5
@Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
    {
        context.startService(new Intent(context, MVWidgetService.class));
    }



Waarbij MVWidgetService de class Service extends :)

Dit vermijd dat Android je zegt dat je class niet meer reageert.

[Reactie gewijzigd op vrijdag 19 juni 2009 12:56]


Door Tweakers user wouteryeah, vrijdag 19 juni 2009 13:02

Heb sinds een week mijn G1 en ben me sinds gister aan het inlezen op het gebied van ontwikkeling voor Android.

Wanneer ik vanavond tijd heb zal ik deze post nog wat uitgebreider lezen, maar tot zover vind ik het een erg goede post _/-\o_

Ga zo door!

Door Tweakers user Wiebbe, vrijdag 19 juni 2009 13:35

@Snake, inderdaad anders krijg je Application Not Responding (ANR) timer shutdown.

Maar in het licht van de simpele applicatie en de zeer licht werkzaamheden die deze doet vond ik het het niet waard om dat te implementeren hierzo.

Door Tweakers user Snake, vrijdag 19 juni 2009 13:58

@Wiebbe hoeft ook niet voor dit :) Dit gaat in enkele milliseconden, een request naar een site kan wel wat langer duren, daarmee.

Desalniettemin: mooi voorbeeld :)

Door Tweakers user qless, vrijdag 19 juni 2009 14:33

Gelukkig lijkt het erg veel op C# en
Het is eerder omgekeerd ;)

Door Tweakers user Wiebbe, vrijdag 19 juni 2009 16:01

@qless

Ja dat is waar, maar voor mij is C# leidend ;)

Reageren is niet meer mogelijk