What you learn: You will learn how to use custom images and basic control states set up via XML to create customized ToggleButton UI controls. Included are three visually distinct ToggleButtons that all work with the same code logic. The intent is to inspire you to create a unique look and feel for your applications.
Tested on emulator (Android 2.0), and Droid handset (Android 2.0.1).
Problems/Questions: Just ask…
Difficulty: 1.5 of 5 if you already have artwork, or use the attached artwork (linked at the end); more difficult if you don’t, depending on your comfort level with DCC (Digital Content Creation) tools, e.g., Photoshop.
The attachment contains all the artwork used in this tutorial, in .png format. I am not a production artist so you may find it deficient in one or more ways.
What it looks like:
What a full set of state images for the gel ToggleButton looks like:
The other ToggleButtons have similar sets of associated images; .pngs for all images are included in the attachment.
(If you are in a hurry, feel free to skip to the cut and paste steps in the Implementation section. This background is for those new to this type of control).
A ToggleButton is a UI control that can show one of two positions. There are several states associated with each position, and each state can have its own image. This tutorial covers the normal, focused and pressed states. A standard ToggleButton control is provided with Android. We’re going to customize this control and give it our own images for the states we care about.
Why not use a CheckBox or RadioButton?
A CheckBox is a great choice if you have a yes/no or on/off type UI option. However, if your choice is red/yellow, version 3.0/version 4.0, English/French, etc., it is more difficult to communicate the 'other' choice to the user.
An example of a good use of a CheckBox is one labeled ‘Sound’. The user can easily deduce that checking the box will enable sound; leaving it unchecked will disable sound. Contrast this with the Version 3.0/Version 4.0 example: which version would the ‘checked’ state apply to? Even worse is a label that just reads ‘use Version 3.0’: the checked state is obvious, but what is the unchecked state? Version 4.0? Version 1.0? The label would have to read "Version 3.0 (checked)/Version 4.0 (unchecked)" in order for this to make sense. Apart from the verbosity, the UI ends up cluttered.
For a ToggleButton, all the necessary information is encoded in the toggle images (i.e., for the above, one position will say ‘Version 3.0’, the other ‘Version 4.0’, with (possibly) images for the focused and pressed states – more on this shortly).
If you have a two choice UI option, a ToggleButton can easily take the place of a CheckBox, but the reverse is not true. If you have more than two choices: red/yellow/green, version 3.0/version 3.5/version 4.0, English/French/Spanish, etc., a RadioGroup would be more appropriate.
A RadioButton can only be toggled to its on state. In order to toggle a RadioButton off, another RadioButton in the same RadioGroup must be clicked. You can certainly use two RadioButtons in a RadioGroup to emulate the behavior of a ToggleButton, but this needlessly complicates both the UI and the code.
ToggleButtons and States:
A ToggleButton has two positions. The current ToggleButton position can be determined by its state_checked attribute. The state_checked attribute is a Boolean, and the decision of which position is associated with state_checked equal to true is arbitrary; you can assign whichever you feel like. This assignment is handled in a selector .xml file. We will discuss these shortly.
A ToggleButton’s focused state supplies feedback to the user that he has navigated to the toggle via a controller (keyboard, trackball, D-Pad, etc). We need a focused image for each of our two toggle positions. Let’s call the two positions A and B, so we end up with four state combinations:
- Code: Select all
state_checked true, state_focused true //A position, has focus
state_checked true, state_focused false //A position, no focus
state_checked false, state_focused true //B position, has focus
state_checked false, state_focused false //B position, no focus
Although the focused state is sufficient to supply feedback to the user, sometimes it may also be desirable to have a pressed state. Normally, this would give us eight states. However, if we use a pressed state we will make it take precedence over the focused state. In other words, if a user has pressed a control we will show the pressed state and ignore the current focused state. So now we have a total of six state combinations:
- Code: Select all
state_checked true, state_pressed true //A position, pressed (don’t care about focus)
state_checked true, state_focused true //A position, has focus (unpressed is implied)
state_checked true, state_focused false //A position, no focus (unpressed is implied)
state_checked false, state_pressed true //B position, pressed (don’t care about focus)
state_checked false, state_focused true //B position, has focus (unpressed is implied)
state_checked false, state_focused false //B position, no focus (unpressed is implied)
Note the order, from top to bottom: this state layout will be evaluated by Android in this order. We specify states, and assign images to the specified states using a selector .xml file. In our selector .xml we list all the state cases we care about in the order we want them evaluated, and we assign an image to each state case.
The final state case in our selector .xml must be a default case. In this tutorial, I’ve (somewhat arbitrarily) chosen the B position’s normal image for the default state. This will be the image the user sees of the control when the application first starts, but it can be changed programmatically if desired by, for example, using the setChecked() API. Exact implementation details of selector .xmls are given later on.
Touch Interaction vs. Controller Interaction:
You will notice a difference in behavior when interacting with most UI controls depending on whether you directly touch the screen or use a controller. When you touch the screen, there is no visual indication of a focused state, even if you have specified one in your selector .xml. This behavior is in contrast to using a controller, where you will see a focused image as you navigate around the screen. A pressed state always shows up if its specified in the selector .xml, regardless of whether the interaction is via touch or controller.
This behavior is by design and the logic behind it is: if you are interacting directly with the touch screen, i.e., using your finger(s) to interact with a UI control, then you just go right to that control and touch it – so your focus is always where your finger is, and you don’t need an explicit image for your focused state. But with a keyboard, trackball, D-pad or other controller, you need to actually navigate to the UI control of interest. For example, if you are using the arrow keys to ‘arrow over’ to a particular UI control, then you would want visual feedback as to where you currently are on the screen. If for some reason you want a focused state for your touch interactions, you can override the default behavior (search for setFocusableInTouchMode or focusableInTouchMode in the Android docs). More information about this behavior can be found here.
Regarding the pressed state, the user will get visual feedback from one toggle position’s normal state to the other, when he interacts with the current control, so providing an explicit pressed image is somewhat of a design decision. If you have a separate image for your pressed state, it will display on a keydown or touchdown event, in other words, as soon as a user interacts with the control. On keyup the other normal position image will be displayed. It is up to your design team to decide if you really want both forms of feedback - in some cases where a UI has a lot of controls it may make the UI too ‘busy’, or the controls may feel more natural without the pressed image. Since the user gets feedback on the keyup anyway, you may decide you don’t want the additional clutter of an explicit pressed state.
In this tutorial I include images for the pressed states for all ToggleButtons, and handle the pressed states in the selector .xmls. Selector .xmls that have only a normal and focused state are included in the download, so you can implement either scenario.
0.) In Eclipse, create a new Android Project, targeting Android 2.0 (older versions may work too, but the folders may be slightly different from those shown here). For consistency with this tutorial you may wish to name your main activity CustomToggleButtons, and your Package name com.example.customtogglebuttons.
(a) Download and unzip the attachment (linked at the end): it contains all the artwork. Copy the contents of the unzipped CustomToggleButtonsResources folder (contains all the .pngs and the selector .xmls) into your project’s /res/drawable-hdpi folder. You will end up with three unique ToggleButtons, with six images each, representing the normal, focused, and pressed states for each of the two toggle positions. You will also have one additional image that will be used as the background for the third ToggleButton (as in the first picture above),
(b) Create your own images and your own selector .xml files (described next) – you’ll need six images and one selector .xml per ToggleButton; all go in the /res/drawable-hdpi folder.
The rest of this tutorial assumes you are using (a), though the source code at the end lists all of the selector .xmls.
1.) There are now three selector .xmls in your res/drawable-hdpi folder. Here is geltoggle_selector.xml:
Here we’ve assigned the set of images (normal, focused and pressed) representing the left toggle position to be displayed when the value of state_checked is true. If state_checked is false, the state assignments are to the right toggle position’s image set.
If state_pressed is true, we always show a pressed state image, regardless of the state_focused value. As explained earlier, order matters - imagine this is a switch/case statement: if both state_checked and state_pressed are true, the first case test succeeds (the following cases won’t even be tested). Since the first case test succeeds, the gettoggleleft_pressed image will be displayed.
If, however, state_pressed is false, the first case test will fail, and the second case will be tested. The second case test succeeds if state_focused is true, so this would be the case that would decide our image – the geltoggleleft_focused image will be displayed.
And so on.
A final default case must be present and it must be the last case; I chose to display the gel toggle’s right position, in its normal state. Users will see this when they first start the app.
The other two selector .xmls - knifetoggle_selector.xml and textimagetoggle_selector.xml - are handled similarly, and should already be in your /res/drawable-hdpi folder.
(Optional: only do this if you do not want to display pressed state images, otherwise ignore and proceed to step 2).
As mentioned earlier, included in the download is an additional folder containing selector .xmls that use only the focused and normal states. To use these selectors, replace the ones in the /res/drawable-hdpi folder with those from the Focused state only selectors folder, and if you wish you can delete all the xxx_pressed.png resource files as they will no longer be needed.
2.) In the res/layout folder, replace the contents of main.xml with:
This is a LinearLayout broken into three sections, one for each of our ToggleButtons. The LinearLayout has attributes set to completely fill the parent view both horizontally (layout_width) and vertically (layout_height), and an attribute that tells any included controls to be laid out vertically (orientation set to vertical), and for each included control, to make sure it’s centered horizontally (gravity set to center_horizontal).
The layout for the first two ToggleButtons is very similar. Both are set up to lie directly on the parent view. Since we are using our own images, we set the text attributes to null (otherwise the default on and off text would appear over our control). We use the background attribute of our ToggleButton to point to our selector .xml. Selector .xmls were described in the previous section. For the first ToggleButton we also provide a bit of extra space between the control and the top of the view, via the layout_marginTop attribute.
Do not get layout_marginXXX attributes confused with paddingXXX attributes. Padding makes the interactive area of a control larger, so if you use small graphics someone with large fingers can more easily interact with your control. The layout_marginXXX attributes just add space between the control and the edges of the view, without affecting the usable area of the control.
The third ToggleButton’s layout is included in another LinearLayout. This is done so we can also include a background image, so that the control appears on top of the background. Notice again how we fill the parent view (layout_width and layout_height set to fill_parent), but in this case the parent is what is left over after the other two ToggleButtons are set up (approximately the lower half of the screen as seen in the picture at the beginning of this tutorial). We again use an attribute to specify that any included controls should be centered horizontally (we only have the one ToggleButton, but we still need to center it left to right). The actual image that we want as the background is set using the LinearLayout’s background attribute and is here set to oldparchment (already included in your res/drawable-hdpi folder if you followed the download instructions).
The third ToggleButton layout is similar to the first two, except for an additional layout_gravity attribute, which centers the control in the parent view. This is in addition to the gravity attribute we set in the parent LinearLayout – that attribute tells us where we want our control, left to right, but not where we want it vertically. Try commenting out the gravity and/or the layout_gravity attributes to see how they affect the layout.
3.) We have elected to do our UI layout and state/image assignment in XML (we could also have done it in Java but we chose the XML approach here), so the hard part is finished. The Java code we need is very straightforward. Replace the contents of CustomToggleButtons.java with:
All this code does is set up click listeners for our three ToggleButtons, and log some state information about which position is being displayed.
4.) (optional) If you are planning on debugging this tutorial on a real handset you will need to set the android:debuggable attribute to true in the project’s manifest. To do this, open the project’s AndroidManifest.xml and replace:
- Code: Select all
<application android:icon="@drawable/icon" android:label="@string/app_name">
- Code: Select all
<application android:icon="@drawable/icon" android:label="@string/app_name" android:debuggable="true">
- Custom artwork gives polish and a unique look and feel to your applications.
- Understand that state cases in selector .xmls are order dependent, and need a final default case.
- Decide if you need a pressed state, if using both focused and pressed states, one ToggleButton implementation will require six images and one selector .xml.
- UI layout and image assignment can be done in Java or XML; we’ve shown the XML approach.
You can get many good ideas for UI element design from your DVD movie collections. The UI on a DVD is a good example of a minimalist UI designed for controllers with only a couple of buttons (television remote), and that is easy to see from a distance.
The Full Source:
You’ll need the attached image files (linked at the end), or your own equivalents.
If you want the original fullsize artwork, layers intact, send me a PM. I didn’t include them here because the download is large. I used Paint Shop Pro X2 for the art, but I can export Photoshop .PSDs if desired. I don’t know how compatible they are as I don’t own Photoshop. For legibility, all original artwork filenames are upper/lower case and will need to be renamed all lower case upon being exported to .png. A bulk rename utility can be used if you don’t want to rename them all by hand.
Hope this helps!