One thing I’ve been grappling with recently is how to get home screen applications widgets to work for my application. There is a decent tutorial in the Android developer documents about the subject (located here), but it doesn’t really go into too much detail on how to make your widgets interactive. Sure, it shows you how to set up an OnClickListener on an element of your widget to load an activity from elsewhere, but what if you want the widget to do something more, like redraw itself with a new image when its clicked? This seems pretty simple, but to be honest, it took me a lot of fiddling to get it to happen.
The first obvious thing we need is an idea of what our widget is going to do. In my case, my app widget toggles on the camera LEDs (on the Moto Droid/Milestone). Not too complicated. So, what do we want to happen when we push the widget? Well, the LED’s need to either come on or turn off, and also the little flashlight icon should switch between lit and unlit respectively.
So, with this in mind, now we need to actually set up our application to use a widget. For brevitys sake, Ill let the good folks over at google cover this topic, as they already have a good write-up/tutorial on how its all done (written much better than I could too). The link to the Widget tutorial is here.
Once you have your application set up to support a widget, the next step we need to do is to hook our widget up to our existing code to allow the user to interact with our main application from their home screen. Lets first focus on calling our method to toggle the lights on and off. We need to somehow get our widget to register when the user clicks on it. Since app widgets are slightly different than other widgets within Android, we cannot just attach an onClickListener to our widget and go. We need to instead use a RemoteViews object. The Android documentation defines a RemoteViews as a “class that describes a view hierarchy that can be displayed in another process”.
private RemoteViews views = new RemoteViews("com.test.widgetTest", R.layout.widget);
Our RemoteViews object takes two arguments, the package name, and the layout to use in the object. This object will allow us to connect an onClickListener to our widget. Before we do that however, we need to specify a new intent for our onClickListener to start. We will also need to create a PendingIntent to store our new intent.
Intent intent = new Intent(context, Widget.class); intent.setAction(ACTION_WIDGET_RECEIVER); PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
For our intent, we need to supply a context and a target activity. In this case, the target activity is the widget class, the same class we are currently working in. Why we are doing this will be explained in a moment. We also need to set an action for the intent, and in this case we have chosen ACTION_WIDGET_RECEIVER, which we will use in the onReceive() method when a user clicks on the widget. Finally, we set up our pending intent with the intent we just created, but we set it to broadcast, rather than start a new activity.
The next step we need to take is to set up our onClickReceiver using our RemoteViews object.
views.setOnClickPendingIntent(R.id.toggle_button_widget, pendingIntent);
We use the setOnClickPendingIntent() method to set our widget to use our pending intent whenever the user clicks on it. For the purposes of this tutorial, I have used an ImageView in my widget layout called toggle_button_widget.
So, now that we have our onClick all set up, lets make it do something when a user clicks on it. As I mentioned earlier, our intent broadcasts to our widget class. What this means is that whenever the widget is clicked on, it sends a broadcast to itself, which automatically triggers the widgets onReceive() method. It is in the onReceive() method where we will do the appropriate action (in this case toggle the lights on or off).
Earlier, we set the action of our intent to ACTION_WIDGET_RECEIVER. One of the first things we want to do is check to see which action was broadcast in our intent. This can be accomplished using a simple if statement (or possibly a switch if you have multiple cases):
if (intent.getAction().equals(ACTION_WIDGET_RECEIVER)) {
}
By using this sort of check at the start of our method, we can handle multiple types of broadcasts if need be, but for our example we’ll only be using one. Once we are sure that the action that was sent is the action we want, we can then set up our logic to flip the lights on or off. This might look something like this:
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(ACTION_WIDGET_RECEIVER)) {
try {
led = new FlashlightLED();
led.toggleLight();
}
}
super.onReceive(context, intent);
}
Thats all we need to do to get our widget to toggle a value in our main application! Pretty simple, whenever the widget is clicked, it calls its own onReceive() and then runs the appropriate logic. But while thats all well and good, when our light is on, the image on the widget is the same as if it were off, which can be confusing. The next thing we want to do is to change the image on the widget according to the state of our light.
I’m going to put a disclaimer in at this point: The following method, while it will work fine, is probably not the best way to go about this, but it is, at the time of this writing, the only way I have found to accomplish this. Please chime in in the comments if you know of a better way.
What we basically need to do is to change the image that is stored in our ImageView, toggle_button_widget, and then update the widget to reflect our new changes. Since we have our widget layout stored in a RemoteViews object, the first step, physically changing the image, is fairly easy:
views.setImageViewResource(R.id.toggle_button_widget, R.drawable.flashlight_lit);
All we are doing here is finding the ImageView in our RemoteViews object and updating its value. Next, we need to update the widget to reflect our changes. To do this, we create a ComponentName object, which takes the context, and the class, in our case our widget class. We can then use this ComponentName object, along with our RemoteViews object to update the widget:
ComponentName cn = new ComponentName(context, Widget.class); AppWidgetManager.getInstance(context).updateAppWidget(cn, views);
It should be noted here that if the user has more than one instance of the widget active on their screen, this will update all of them simultaneously. This makes sense for my widget, as if the light is on, all the instances of the widget should reflect this fact.
So, now that we can update the image each time the light is toggled, our onReceive() method could look something like this:
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(ACTION_WIDGET_RECEIVER)) {
try {
led = new FlashlightLED();
led.toggleLight();
if (led.isEnabled()){
views.setImageViewResource(R.id.toggle_button_widget, R.drawable.flashlight_lit);
ComponentName cn = new ComponentName(context, Widget.class);
AppWidgetManager.getInstance(context).updateAppWidget(cn, views);
}else{
views.setImageViewResource(R.id.toggle_button_widget, R.drawable.flashlight_off);
ComponentName cn = new ComponentName(context, Widget.class);
AppWidgetManager.getInstance(context).updateAppWidget(cn, views);
}
}
}
super.onReceive(context, intent);
}
And the full code for our widget might look something like this:
public class Widget extends AppWidgetProvider {
private static final String ACTION_WIDGET_RECEIVER = "ActionRecieverWidget";
private RemoteViews views = new RemoteViews("com.test.widgetTest",
R.layout.widget);
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
final int N = appWidgetIds.length;
// Perform this loop procedure for each App Widget that belongs to this
// provider
for (int i = 0; i < N; i++) {
int appWidgetId = appWidgetIds[i];
Intent intent = new Intent(context, Widget.class);
intent.setAction(ACTION_WIDGET_RECEIVER);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.toggle_button_widget, pendingIntent);
// Tell the AppWidgetManager to perform an update on the current App
// Widget
appWidgetManager.updateAppWidget(appWidgetId, views);
}
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(ACTION_WIDGET_RECEIVER)) {
try {
led = new FlashlightLED();
led.toggleLight();
if (led.isEnabled()){
views.setImageViewResource(R.id.toggle_button_widget, R.drawable.flashlight_lit);
ComponentName cn = new ComponentName(context, Widget.class);
AppWidgetManager.getInstance(context).updateAppWidget(cn, views);
}else{
views.setImageViewResource(R.id.toggle_button_widget, R.drawable.flashlight_off);
ComponentName cn = new ComponentName(context, Widget.class);
AppWidgetManager.getInstance(context).updateAppWidget(cn, views);
}
}
}
super.onReceive(context, intent);
}
}
And there you have it! A simple, lightly interactive homescreen app widget for Android.
You may also be interested in these articles:
Comments
Leave a comment Trackback