Developing widgets for the Android platform involves a slightly different set of tasks than standard app development. In this series of tutorials, we will work through the process of developing a customizable analog clock widget. The clock will be based on the Android AnalogClock class and customized with your own graphics.
So far in this series on developing a custom analog clock widget for Android we have set the project up and created the main design elements. In this part, we will implement the Java widget class, handling updates, and also launching the app. We will be working on two Java classes, one extending the AppWidgetProvider class and the other an Activity class that will start if the user attempts to launch the app. We will also work on the layout file for the launch Activity. After this tutorial, you will have a functioning clock widget that will continuously update to reflect the current time.
In the final part of the series we will be allowing the user to make a choice from the selection of clock designs we created last time.
This is Part 3 of our series on Building a Customizable Android Analog Clock Widget over four tutorials:
- Android Widget Project Setup
- Designing the Clock
- Receiving Updates and Launching
- Implementing User Configuration
Widget apps extend the AppWidgetProvider class in Android, so that's what we will do here. In this class you can provide processing steps for updating your widget as it runs on the user's homescreen. Our launcher Activity will use the main XML layout file to display a String resource we defined in the first tutorial. In this tutorial, we will also cover using launcher (and optionally preview) icons for our widget app.
Step 1: Implement the Launcher Activity
You may remember that in the first tutorial, we decided to include a launcher Activity within the widget app. This is not a necessity, but offers a couple of advantages. Users unaware of how to launch a widget app will see instructions on launching it. Also, devices running Android 4.0 sometimes fail to include new widget apps within the menu, but providing a launch Activity seems to circumvent this issue.
When you created your project in Eclipse, if you specified an initial Activity for the app, Eclipse should have created a Java class for it. Using the default steps outlined in Part 1, you should have a Java class in your project package named "CustomClockWidgetActivity" or an alternative name if you changed it. Open this in Eclipse - it should contain the following outline:
//package name import android.app.Activity; import android.os.Bundle; public class CustomClockWidgetActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } }
If Eclipse did not create the class, create it now and add the code, altering the class name if necessary. We can actually use the default behavior of this main Activity class and leave the code as it is. Notice that the code specifies the main XML layout for its content view. We will alter the main XML layout file next to suit the details of our widget app.
The Activity should already be included in your project Manifest file as follows:
<activity android:name=".CustomClockWidgetActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
Here we indicate that this Activity should start when the app is launched. This is the way a launcher Activity is listed within the Manifest for a standard (non-widget) app. Although our widget will generally run by being added to the user's homescreen, we are providing a launcher Activity for the reasons mentioned above.
Step 2: Alter the Main XML Layout
Open the main XML layout file: "res/layout/main.xml". Eclipse should have created this file when you created the project. Most of this file can be left with the default code, but we want to add the following attribute to the TextView element for improved readability:
android:padding="10dp"
Your main XML layout file should now appear as follows:
<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:layout_width="fill_parent" android:layout_height="wrap_content" android:padding="10dp" android:text="@string/hello" /> </LinearLayout>
The layout simply displays the "hello" text String we edited in Part 1 of this series. The String instructs the user in terms of how to add the widget to their homescreen. Remember that we included two versions of the String, one in the "values" folder and one in the "values-v14" folder, so that we could tailor the instructions to the version of Android that the user has installed.
Step 3: Create a Launcher Icon
With a widget app, the launcher icon appears in app listings. In our case, it will also appear in the main user device menu because we have provided a launch Activity. You can create a preview image for your widget if you like, but if you do not, the launcher icon will appear in widget previews. For users running Android 4.0 and onwards, widgets are added from the Widget tab in the device menu. Within this section of the menu, the preview image appears or the launcher icon will appear if no preview has been provided. On previous API levels the preview or icon appears within the widget picker, after long-pressing the homescreen.
For our clock widget we will just be using a launcher icon, so create yours now - you can use the following set if you don't want to design yours yet:
Use the following maximum size guide for your icons at each density:
- low - 36px
- medium - 48px
- high - 72px
- extra high - 96px
If you use the filename "ic_launcher" for your launcher icon, you should not need to alter any of the code as the tools default to this name. Check that your project is indeed using this name by opening the Manifest file and checking the icon attribute of the application element, which should appear as follows:
android:icon="@drawable/ic_launcher"
If it does not appear this way, alter the code to match the name you want to use for your launcher icon. Save your copies of the icon in each drawable folder, remembering to use the same name in each folder (but image content with varied sizes to suit each density). If you are creating your own launcher icons, try to make sure they reflect the appearance and function of your clock widget so that users will intuitively make the link between the icon and the widget.
Tip: Creating Preview Images Using the Android Emulator
If you want to create preview images for your widget apps, you can use the Widget Preview application. Run a widget app on an AVD in Eclipse (for the clock widget you can do it at the end of this tutorial or the next one) - make sure your AVD has some SD card storage allocated. Open the emulator device menu and select the Widget Preview app. Choose your widget from the list. Once the image of your widget appears, click "Take Snapshot" to capture a preview.
You can then save the preview directly onto your computer from within Eclipse. To do this, open the DDMS perspective, select your emulator in the Devices view, and browse to the image in the File Explorer view (look in the Download directory on the SD card). Select the preview image file and click the "Pull a File From the Device" button to save the image to your local file system.
Once you have the preview image, copy it to your project drawable folder(s) and list its name in your "res/xml" widget properties file "appwidget-provider" element, alongside the dimensions, update period and initial layout values, using the following attribute:
android:previewImage="@drawable/clock_preview"
This would apply to an image file named, for example, "clock_preview.png" saved in each relevant drawable folder.
Step 4: Implement the Widget Class
Now let's turn to the Java class for our clock widget. Remember that in the project Manifest we included a receiver element indicating a class named "ClockWidget" - we will create this next. Take a moment to look back at the Manifest now before you implement your class. Notice that the receiver element includes the "APPWIDGET_UPDATE" action and specifies meta-data comprising the "clock_widget" XML file in which we defined the basic widget properties.
Create your widget class now by right-clicking or selecting the main app package in the "src" directory and choosing "File" then "New," "Class" - enter "ClockWidget" to match the name in the Manifest. Eclipse should open your new class file. Extend the opening class declaration line as follows:
public class ClockWidget extends AppWidgetProvider {
You will need to add the following import statement above this line at the top of the class:
import android.appwidget.AppWidgetProvider;
Add an instance variable inside the class declaration as follows:
RemoteViews views;
This will allow us to refer to the user interface / visible elements for the widget, which requires a slightly different process in widget classes than in Activity classes. The Remote Views let us refer to the widget layout and the views within it. This is essential when updating and providing interactive functions for the widget. You will need another import statement:
import android.widget.RemoteViews;
Next add the "onReceive" method outline inside the class as follows:
public void onReceive(Context context, Intent intent) { }
The AppWidgetProvider inherits this method from the BroadcastReceiver class. In the next tutorial, we will use this method to handle clicks on the clock widget. For the moment, all we need to do is instruct Android to update the widget appearance when the method executes. Add the following import statements first:
import android.content.Context; import android.content.Intent;
Inside the "onReceive" method, include a conditional test to check whether the method is running because the widget is due to update:
//find out the action String action = intent.getAction(); //is it time to update if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) { }
Add another import statement:
import android.appwidget.AppWidgetManager;
Inside the "if" statement, retrieve the Remote Views for our widget layout, instantiating the instance variable we created as follows:
views = new RemoteViews(context.getPackageName(), R.layout.clock_widget_layout);
Notice that we use the Context object passed to the "onReceive" method to retrieve the package name. We specify the clock widget layout file we worked on in the last tutorial - if you used a different file name, alter this code to reflect it. Now update the widget by adding the following code, still inside the "if" statement:
AppWidgetManager.getInstance(context).updateAppWidget (intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS), views);
Here we use the AppWidgetManager to call the update, passing the Remote Views object as a reference. You can now run your widget app to see its default appearance and behavior. When the app is initially installed/ launched, the launcher Activity runs:
The launch Activity is purely informative so it's best in general to keep it simple, unless you have some other purpose in mind for it. Other than that feel free to alter its appearance in any way you like. The following screen-shots show the widget being added from the user device menu on Ice Cream Sandwich:
Notice that the launcher icon appears in the Widget menu - if you include a preview image and list it in your widget XML it will appear here instead. Finally, the clock widget runs using its default design:
Next Time
The basic elements of the Analog Clock widget are now complete. In the next and final part of the tutorial series, we will implement allowing the user to choose a clock design. The default clock design is currently displayed because in the last tutorial we set the other designs to be invisible. We will handle user clicks on the widget by adding functionality to the widget class. We will also use a new Activity to display the selection of clock designs to the user, detecting which one they pick and updating the widget appearance accordingly. Finally, we will use the application Shared Preferences to store a record of which widget design the user selected, making it customizable.
Comments