Advertisement

Build a Custom Clock Widget: Implementing User Configuration

by

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, we have designed and implemented the widget in XML and Java and have a functioning clock widget the user can add to their homescreen. In this final part of the series, we're going to implement basic user configuration. In Part 2 we created multiple clock designs, so now we will allow the user to choose between them.

This is Part 4 of 4 in a series on Building a Customizable Android Analog Clock Widget in four tutorials:

Building user configuration into our widget app is going to involve a new Java Activity class, presenting the selection of choices to the user. When the user selects a design, we will update the widget appearance and store the user choice to the application's Shared Preferences. We will also be extending the widget class to handle user clicks on the widget and to read from the Shared Preferences for the user's choice. As well as working with these two Java files, we will create a new values file and an XML layout file for the choice Activity together with some images to display within it.


Step 1: Handle Widget Clicks

First, let's add some code to the widget class to detect user clicks. In the "ClockWidget" class, inside the "if" statement in the "onReceive" method, after the line in which we retrieved the Remote Views object, add the following code to create an Intent for the chooser Activity we are going to use:

Intent choiceIntent = new Intent(context, ClockChoice.class);

Don't worry about the Eclipse errors for now, they will disappear when we create the new Activity class in the next step. After this line, create a Pending Intent as follows:

PendingIntent clickPendIntent = PendingIntent.getActivity
	(context, 0, choiceIntent, PendingIntent.FLAG_UPDATE_CURRENT);

Launching Activities on widget clicks is a little different, as you can see. Notice that we pass the Context object and a reference to the new Intent. Now add the following code specifying that the Pending Intent should be launched when the widget is clicked:

views.setOnClickPendingIntent(R.id.custom_clock_widget, clickPendIntent);

We specify the widget by referring to the ID of the parent layout in the "clock_widget_layout" XML file. We need to use the Remote Views to refer to the user interface items as we are in a widget class rather than an Activity class. We will add more code to this class later.


Step 2: Create a Chooser Activity

Now for the Activity in which we let users choose a design. Create a new class in your project by right-clicking or selecting the source package folder and choosing "File" - then select "New," "Class" and enter "ClockChoice" as the class name. Eclipse will open the new class when you click Finish. Remember that we included this Activity in the project Manifest file in Part 1.

Make your new class an Activity and one that will handle user clicks by extending its opening line as follows:

public class ClockChoice extends Activity implements OnClickListener {

Again, just ignore any error messages, they will appear until we provide the "onClick" method. You will need the following import statements:

import android.app.Activity;
import android.view.View.OnClickListener;

Provide the Activity "onCreate" method inside the class as follows:

public void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.clock_choice);

}

We will create the layout file in the next step. You will need another import:

import android.os.Bundle;

We will be adding more code to this class later.


Step 3: Chooser Activity Design

Let's create the layout we specified in the Activity class above. Create a new layout file by right-clicking or selecting the "res/layout" folder and choosing "File" then clicking "New," "File" and entering "clock_choice.xml" to match the reference in the above code.

When Eclipse opens the file, select the "clock_choice.xml" tab to edit the code. Enter the following layout outline in your file:

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_height="wrap_content" 
	android:layout_width="fill_parent">
	<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
		android:orientation="vertical" 
		android:layout_width="wrap_content" 
		android:layout_height="wrap_content"
		android:padding="10dp">
	
</LinearLayout>
</ScrollView>

The design includes a Linear Layout inside a Scroll View. Inside the Linear Layout, first add some informative text as follows:

<TextView 
	android:layout_width="wrap_content" 
	android:layout_height="wrap_content" 
	android:text="@string/choice_intro"
	android:textStyle="italic" />

Add the String resource indicated here to your "strings.xml" file - making sure you include it in both copies of the strings file, in "values" and "values-v14":

<string name="choice_intro">Choose an option:</string>

Now we are going to display an image to represent each clock design. These images are going to act as buttons for users to press when selecting a design. Create your images now, remembering to include different versions for each density. If you do not want to create your own just yet, you can use the images in the download link at the bottom of the tutorial - here are the medium density versions of each:

Save each version of your images into the drawable folders for your app, remembering to use the same names in each density folder.

Now go back to your "clock_choice" layout XML file. Add an Image Button for each design you are using, with the following for the three example designs we have used, after the Text View:

<ImageButton
	android:id="@+id/design_0"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:src="@drawable/clock_image"
	android:contentDescription="@string/design1"
	android:background="#00000000"
	android:padding="5dp"/>
<ImageButton
	android:id="@+id/design_1"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:src="@drawable/clock_image_stone"
	android:contentDescription="@string/design2"
	android:background="#00000000"
	android:padding="5dp"/>
<ImageButton
	android:id="@+id/design_2"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:src="@drawable/clock_image_metal"
	android:contentDescription="@string/design3"
	android:background="#00000000"
	android:padding="5dp" />

There are several points to note here. First, notice that each Image Button has an ID with the same syntax but an incrementing integer at the end - we will use this to iterate through the designs in Java, so if you are including more than three designs, make sure you assign them iterating numbers, such as "design_3" etc. Apart from the design attributes, each element also indicates the relevant drawable resource, so alter these if the image files you just created have different names. Finally, the content description attributes refer to string resources, so add them to the "strings.xml" files in both your "values" and "values-v14" folders as follows:

<string name="design1">Default design</string>
<string name="design2">Stone design</string>
<string name="design3">Metal design</string>

Alter these descriptions if necessary to suit your own designs.

Let's make use of Android themes to automate certain aspects of the chooser Activity appearance. Open your project Manifest file and extend the element representing the clock chooser Activity as follows:

<activity android:name=".ClockChoice" android:theme="@android:style/Theme.Dialog">
</activity>

Adding the dialog theme makes the Activity appear overlaid on the homescreen - this is its appearance with the sample designs we have used in this tutorial series:


This is what will appear when the user clicks the widget. If the device display is too small to accommodate all three designs, they will scroll as the layout uses a Scroll View. When the user selects a design, the widget appearance will update and this choice Activity will disappear.

It goes without saying that you may wish to alter the number of possible designs within your clock widget app. To make this process easier, we are going to make our Java code read the number of designs dynamically. For this purpose, we are going to use a numerical value to keep track of the number of designs we are using. Create a new file in each of your two values folders, right-clicking or selecting each folder in turn and choosing "File" then "New," "File" and entering "numbers.xml" as the file name.


Select the "numbers.xml" tab and enter the following code in your new file:

<resources>
	<integer name="num_clocks">3</integer>
</resources>

Alter the number if you used a different number of designs, making sure the value matches the number of Image Buttons you included in the layout and the number of Analog Clock elements you have in your widget layout. Remember you need a copy of the "numbers.xml" file in both values folders, so copy and paste it if necessary.

If you do alter the number of designs you are using at any point, you need to alter the value in the "numbers.xml" file(s), add each design as an Analog Clock element in the app's "clock_widget_layout" file and an Image Button for each design in the chooser Activity layout file.


Step 4: Handle User Choice

Let's handle user interaction with the selection of clock designs. Open your "ClockChoice" Activity file. Inside the class, before the "onCreate" method, add the following instance variables:

//count of designs
private int numDesigns;
//image buttons for each design
private ImageButton[] designBtns;
//identifiers for each clock element
private int[] designs;

We will use these variables to iterate through the various designs. Add another import:

import android.widget.ImageButton;

Inside the "onCreate" method, after the existing code, instantiate the variable to keep track of the number of clock designs, as follows:

numDesigns = this.getResources().getInteger(R.integer.num_clocks);

Here we retrieve the value in the numbers XML file we created earlier - we will be able to use this value as a reference when iterating through the designs. Next instantiate the arrays for the design buttons and clock elements:

designBtns = new ImageButton[numDesigns];
designs = new int[numDesigns];

The first array is going to refer to the Image Buttons we are using within this chooser Activity to detect the user's choice. The second array is going to store references to the Analog Clock elements for each design in the widget layout file itself.

Now add a loop to iterate through the designs:

for(int d=0; d<numDesigns; d++){

}

Inside the loop, retrieve a reference to each Analog Clock element in the widget layout:

designs[d] = this.getResources().getIdentifier
	("AnalogClock"+d, "id", getPackageName());

If you look back at the "clock_widget_layout" file, you will see that each Analog Clock element has an ID comprising "AnalogClock" followed by an incrementing integer - we use this here to retrieve a reference to each one using the loop counter. Next, still inside the loop, retrieve the ID values for the Image Buttons in the chooser Activity layout:

designBtns[d]=(ImageButton)findViewById(this.getResources().getIdentifier
	("design_"+d, "id", getPackageName()));

Here we are using "findViewById" to get a reference to the relevant Image Button element, passing the ID, which comprises "design_" followed by the incrementing integer. Now set the click listener for each of these buttons so that we can handle clicks in this Activity class. While still inside the loop:

designBtns[d].setOnClickListener(this);

Now we can go ahead with the click method for the design buttons. After the "onCreate" method in the "ClockChoice" class, add an "onClick" method as follows:

public void onClick(View v) {

}

Add the following import:

import android.view.View;

In the "onClick" method, we first need to determine which button the user has pressed. Add the following loop inside the method for this purpose:

int picked = -1;
for(int c=0; c<numDesigns; c++){
	if(v.getId()==designBtns[c].getId()){
		picked=c;
		break;
	}
}

Here we check the clicked View ID against the IDs we have stored in the Image Button array. When the loop exits we have the chosen index stored in a local variable. After the loop store a reference to the ID for the chosen Analog Clock element:

int pickedClock = designs[picked];

Now we need to get a reference to the widget layout elements, for which we need the Remote Views, passing the widget layout as a reference:

RemoteViews remoteViews = new RemoteViews
	(this.getApplicationContext().getPackageName(), 
	R.layout.clock_widget_layout);

For this you need another import statement:

import android.widget.RemoteViews;

Remember that we included each Analog Clock design in the widget layout file - this is because we can't dynamically alter the attributes of an Analog Clock such as the drawables for the dial and hands, so we instead include all of the designs and set all but one to be invisible. Before we set the chosen clock design to be visible, we will set the others to be invisible, which we can do in a loop as follows:

for(int d=0; d<designs.length; d++){
	if(d!=pickedClock)
		remoteViews.setViewVisibility(designs[d], View.INVISIBLE);
}

We use the array in which we stored the Analog Clock element ID values to set each one invisible as long as it isn't the chosen one. Now we can set the chosen design to be visible, after this loop:

remoteViews.setViewVisibility(pickedClock, View.VISIBLE);

Now, because this is a widget app, we need to update the widget appearance as follows:

//get component name for widget class
ComponentName comp = new ComponentName(this, ClockWidget.class);
//get AppWidgetManager
AppWidgetManager appWidgetManager = 
	AppWidgetManager.getInstance(this.getApplicationContext());
//update
appWidgetManager.updateAppWidget(comp, remoteViews);

This is similar to the way in which we updated the widget in the widget provider class, but with a couple of additional processing steps because we are in an Activity class here. You will need to add these imports:

import android.appwidget.AppWidgetManager;
import android.content.ComponentName;

Now the widget appearance will update according to the user's choice.


Step 5: Update Shared Preferences

Next, we are going to use the application Shared Preferences to store the user design choice. Back up at the top of the clock choice Activity class, add another instance variable:

private SharedPreferences clockPrefs;

Now at the end of the "onCreate" method, after the existing code, add the following to instantiate the Shared Preferences variable:

clockPrefs = getSharedPreferences("CustomClockPrefs", 0);

We will use the same preferences name each time we access the data regarding the user's choice. Now move down to the end of the "onClick" method, after the code in which we updated the widget. Get the preferences editor as follows:

SharedPreferences.Editor custClockEdit = clockPrefs.edit();

Now pass the data regarding the user choice to the editor and commit it:

custClockEdit.putInt("clockdesign", picked);
custClockEdit.commit();

We specify a name for the data value and the ID of the chosen design. Finally, still inside "onClick" add the following as the Activity's job is done:

finish();

Step 6: Check Shared Preferences

Now we can make use of the Shared Preferences data from within the widget class. Open your "ClockWidget" class, which extends AppWidgetProvider. The algorithms we use here will be similar to those we used above for managing the clock designs. Add the following additional instance variables at the top:

//preferences
private SharedPreferences custClockPrefs;
//number of possible designs
private int numClocks;
//IDs of Analog Clock elements
int[] clockDesigns;

You will need the following import:

import android.content.SharedPreferences;

In the "onReceive" method, before the existing code, retrieve the number of designs from our value resource:

numClocks = context.getResources().getInteger(R.integer.num_clocks);

Next instantiate the clock ID array as follows:

clockDesigns = new int[numClocks];

Now loop through this array, setting each element as the ID for the relevant Analog Clock element in the widget layout:

for(int d=0; d<numClocks; d++){
	clockDesigns[d]=context.getResources().getIdentifier
		("AnalogClock"+d, "id", context.getPackageName());
}

Now move to the content of the "if" statement in the "onReceive" method, after the line in which we retrieved the Remote Views and before the line in which we created the Intent for the clock choice class, get the Shared Preferences and check for the data value we set for the user's choice:

custClockPrefs = context.getSharedPreferences("CustomClockPrefs", 0);
int chosenDesign = custClockPrefs.getInt("clockdesign", -1);

The Shared Preferences name and data value name must match what you included when setting the user's choice in the chooser Activity above. The user may not have chosen a design yet, so include an "if" statement to check:

if(chosenDesign>=0){
			
}

Inside the "if" statement, first loop through the designs, setting each invisible if it is not the chosen one:

for(int d=0; d<numClocks; d++){
	if(d!=chosenDesign)
		views.setViewVisibility(clockDesigns[d], View.INVISIBLE);
}

You will need another import:

import android.view.View;

Now set the chosen design visible:

views.setViewVisibility(clockDesigns[chosenDesign], View.VISIBLE);

Now when the widget is updated, if the user has chosen a design, that design will be displayed.


Conclusion

That's the completed clock widget! You can run it on an emulator or device to test it. It should run continuously, updating the time and reflecting the user's design choice.


Do take some time to look back over the various application elements and make sure you understand how they work in conjunction with one another. The process of developing any other widget app will involve lots of the same steps. The vital elements in a widget app are: the XML "appwidget-provider" element, the Manifest file, the class extending AppWidgetProvider and of course the visual elements including layouts and resources.

If you're looking to further develop the skills you have learned in this series, there are a range of options. If you want to provide advanced user configuration for your widgets, you can use a Preference Activity together with the APPWIDGET_CONFIGURE action. If you want to include digital clock displays along with your analog options, the process is a little more complex. You can include the default Digital Clock class in a standard app, but not in a widget app, so you have to implement the digital time display and updates yourself, using additional components such as Services and Alarm Managers.

Feel free to use the complete source code and images for this tutorial provided in the download link.

Advertisement