Often during the creation of an Android application, a developer will need to create a settings page. This allows the app's user to change settings for the application that will persist to the next session, such as "enable my location" or "show map markers". Each setting is in effect an on/off switch.
Out of the Box
As you can see from the screenshot, these controls work fine for setting an on/off state within the application. But...they look pretty lame don't they? There isn't any animation and no way to get the look and feel to blend with your application. As you move beyond the beginner stages in Android development, you'll want to learn how to create your own controls with your own look and feel. Android's built-in componentized model allows a developer to create Custom Components that you can use to construct your UI. For our purposes a Compound Control will do the job. According to the Android documentation, Compound Controls allow you to "to put together a reusable component that consists of a group of existing controls". Let's take a look at how we can create a better-looking toggle button.
The toggle button will consist of a black oval containing a circle. The circle's position represents the current setting: left for "off", right for "on". When the toggle button is pressed, the circle will move to the right and the containing oval will animate to green.
Create the Shapes
To start with, let's create the circle. To create the components for our toggle button we will use Android shapes. Creating shapes through XML is preferable to creating bitmaps because of scaling; we don't have to worry about the many different devices with differing screen sizes and densities. With shapes, Android will handle the scaling for us. Creating a shape in Android is quite simple. We just create an oval with a pre-defined height and width. For a circle, these values will just be the diameter.Next, we'll create the shapes for the containing oval. For this, we'll use a layer-list. I won't dive into a detailed explanation of layer-lists in this tutorial, but they're pretty easy to grasp. Here's how we create an elongated oval. This is essentially two circles on either side of a rectangle.And then, we'll create one for the "on" setting, which is the same thing but with a green background instead of black.
Create the Layout
Alright, now that we've got our basic shapes in place, let's create a layout that will represent the markup for our custom control and use the shapes we've just created. Our layout will consist of a FrameLayout (in order to stack our shapes on top of each other) and a TextView to hold the label for our control.Note the first tag in our layout <merge>. What does that do? The merge tag is an essential aspect of optimizing Android layouts - simply put, it merges its contents into whatever Layout contains it. This prevents us from creating redundant and unnecessary views that only slow down the rendering process.
Each View element is just a generic view with a background representing the shapes we just created. I've set the visibility of the green oval to off for now.
Create a Custom Class
Now comes the real work. Our custom control needs to be its own class that subclasses any one of Android's built-in views. For our purposes here, we'll subclass a relative layout. This class is like any other class - it contains the properties of our toggle button as well as methods to handle how it should behave. Our constructor does our setup work, including grabbing some passed-in attributes (more on that in a minute). But pay attention to these lines
To perform the animation of the circle from right to left, we'll be using Android's ObjectAnimator. But the animator needs to know an exact number of *pixels* to animate. As any Android developer knows, this presents a problem because we often do not know the dpi (screen density or pixels per inch) of the device running our app. Android supports devices with myriad screen sizes and densities. Luckily, we have quite a few handler methods at our disposal. The method getDimensionPixelSize will return to us the number of pixels a particular size represents for the device running the app. For instance, I've set our control's width to 44dp (which is density *independent* pixels). My particular device has a density of 2 pixels per inch. Therefore, the width of my oval is 88 pixels on the screen. Once we have that value, the next two lines simply animate the "x" property of the circle to half of this number (as the left edge of the circle will go from 0 to 44 pixels, in this case). We chain this with a call to setDuration, which sets the time for the animation to 250 milliseconds. Now that we have our animator set up, we need to wait for a click event for the animation to actually happen. Our class will implement the View class's interface onClickListener. We then override the onClick method:
Our custom class will both read and write values to Android's Shared Preferences, which will store the user's settings for future app uses.The first line of code checks to make sure the animation is not currently running. We then get a reference to Android's SharedPreferences editor. We use this to determine if the current setting is off (false) or on (true). If it's on, we start the animator to move the circle left, to the off position, and vice versa if it's off. Finally, we store the value in the Shared Prefs for this setting. The private method "_crossfadeViews" animates the background color of the oval between black and green. It does this by first making the target view visible, but setting the opacity to 0, which means it will still be invisible. Then we fade out the current oval and fade in the new oval. In the callback onAnimationEnd, we set the beginning oval's visibility to GONE.
Add it to Our Application
That's it! We've created our own Compound Control. To use this in an activity or fragment, we simply drop in the xml just like any other View.Notice the tags prefaced with "widget". These represent attributes for our custom control. For each setting we have in our application, we may need to define certain attributes about it. To create custom attributes, we first create a namespace, which is the first line of the above code. You can see from the code above, I've created two attributes: prefName (the key) and text (Text to the right of the control). In our attrs.xml file, we define each attribute we want for our custom control. At this point, the sky's the limit for how customizable you want your control to be. As you see below, my control can set the background of the off setting (say, for when it lives on a white background), the color of the text for the label, etc.In the constructor for our SettingsToggle class, we grab each one of these values defined in the xml via the attrs parameter passed in.
That's it, we're done! Here is a screenshot of it in action.
Much better eh? The beauty of Android is the ability to customize the look and feel of your application. And since the Android SDK is open source, you can inspect the source code to get an idea for how a particular control works. This, in turn, could generate new ideas for your own controls.
For the full source code of this tutorial, visit the repo on GitHub.