Android SDK: Creating a Rotating Dialer

The Android SDK offers a wide range of interface components, including TextViews, Buttons, and EditText boxes. However, adding custom interface elements to your app is a great way to stand out in the App Market. This tutorial will dive into custom interface component creation by teaching you how to build a sleek rotary dialer.

This tutorial assumes some basic knowledge of Java and Android. Furthermore, I won’t introduce a complete API. Instead, specific classes are going to be used and explained. I will also explain, step by step, why I have chosen a particular implementation for this interface component.


Step 1: The Concept

We have a circle and want to rotate it around its center. The simplest approach is to take a two dimensional Cartesian coordinate system as a template.

coordinate system

You touch the circle, rotate it, and then let it go. Moreover, the circle should register a "fling" when this occurs.

Android provides a very simple interface to transform images with the help of the Bitmap class and the Matrix class. The circle gets displayed in an ImageView. The mathematical basis of the unit circle can be implemented by using the Math class. Android also offers a good API to recognize touch events and gestures. As you can see, most of the work is already available to us with the SDK!


Step 2: Project Setup

Create a new Android project and add an Activity. Then add the dialer graphic to the "drawable" folder. We won’t need different versions for different display densities, because later we will scale the image programmatically. One image with a high resolution, which covers all display sizes, should be enough and will save storage space on the user's phone.

project setup

Step 3: Layout

For this tutorial, we will keep the user interface very simple. We only add one ImageView to the layout. For the ImageView’s content, we select our dialer graphic. It is no problem to add more Views later as doing so doesn’t influence our rotating dialer.


Step 4: The Activity

As described in the steps above, we need to load the image as Bitmap while creating the activity. Furthermore, we need a matrix for the transformations, like the rotation. We do all of this in the onCreate method.

We also need a properly scaled copy of the image. Since we only know after measuring the layout how much space our ImageView fills, we add an OnGlobalLayoutListener. In the onGlobalLayout method, we can intercept the event that the layout has been drawn and query the size of our view.

The OnTouchListener is kept very simple. In the ACTION_DOWN event we initialize the angle (i.e. the unit circle). With each movement, the difference between the old and new angle gets additively incremented to the dialer.

We add the OnTouchListener as private inner class. Doing so will save us from the unnecessary passing of required parameters (e.g. the size of the view).

The calculation of the angle can be quickly solved with the help of the unit circle. However, the touch event's coordinate needs to be converted to the coordinates of the two dimensional Cartesian coordinate system first. It would probably help you to set up these formulas by yourself once again to understand them completely.

The method for rotating the dialer is very simple. We replace the old ImageView’s content every time with the new rotated Bitmap.

Notice that, apart from some small issues, rotating the dialer already works. Also note that if the rotated bitmap does not fit into the view, the bitmap gets scaled down automatically (see the scale type of the ImageView). This causes the varying size.


Step 5: Algorithm Correction

A few bugs were introduced in the step above, for example the varying scale, but they can be easily fixed. Do you see where the problem is with the method implemented above?

If you take a deeper look into the implementation of the calculation of the rotated image, you will quickly discover that a new Bitmap instance is created each time. This wastes a lot of RAM and results in often running garbage collector in the background (you notice this issue in the output of LogCat). This causes the animation to look choppy.

The problem can only be avoided by changing the approach. Instead of rotating a bitmap and applying it to the ImageView, we should directly rotate the ImageView’s content. For that purpose, we can change the scale type of the ImageView to “Matrix” and apply the matrix to the ImageView each time. This also adds another small change: now we need to add the scaled bitmap to the ImageView directly after the initialization.

Now another bug occurs. The calculation of the angle works properly with the ImageView’s center. However, the image rotates around the coordinate [0, 0]. Therefore, we need to move the image at initialization to the correct position. The same applies to the rotation. First the bitmap has to be shifted, so that the center is located in the origin of the ImageView, then the bitmap can be rotated and moved back again.

Now our animation works much better and is very smooth.


Step 6: Fling

Android provides an easy-to-use API for recognizing gestures. For example a scroll, long press, and a fling can be detected. For our purposes, we extend the SimpleOnGestureListener class and override the method onFling. This class is recommended by the documentation.

Furthermore, we need to animate the rotation and thereby decrease its speed. Since these are just small calculations, it is not necessary to outsource the functionality in an extra thread. Instead, you can transfer the action to a Handler that can manipulate the GUI in the main thread. We can even avoid the handler instance by passing the action to the ImageView that uses its own Handler for it.


Step 7: Fixing The Inversed Rotations

If you extensively test the fling, it quickly becomes clear that at various positions the dialer rotates in the wrong direction. For example, this is the case when the start and end points are only in the third quadrant. However there are also more difficult error cases (e. g. when wiping from quadrant two to four over quadrant three).

The simplest solution is to remember the touched quadrants. The error cases are caught in the onFling method and the velocity gets inverted accordingly.


Step 8: Stop Fling

Of course the fling animation has to stop if you touch the dialer while the animation is running. For this it is only necessary to add a boolean value for checking whether the animation is allowed to be played or not.

In the ACTION_DOWN event you want to stop it. Once you let the dialer go (ACTION_UP), the animation should be played.


Step 9: What’s Next

All the desired features are included in the dialer. However, this is only an example and can be extended indefinitively.

A few additional enhancements that come to mind include: you could track the angle and invert or decrease certain values (see my app Shutdown Remote for an example of this). Also, after releasing the dialer you could animate it back to the original position to replicate the mechanical function of real, antique rotary dials. Further, the dialer could be extracted into a custom View class, which would increase the reusability of the component. The possibilities for enhancement abound!


Conclusion

In this tutorial, I have shown you how to implement a simple rotary dialer. Of course, the functionality can be extended and improved further as discussed above. The Android SDK offers a great and very useful API with many classes. There is no need the reinvent a lot, you normally just need to implement what is already there. Do not hesitate to give comments or leave suggestions!


Sources

http://en.wikipedia.org/wiki/File:Cartesian_coordinates_2D.svg

Tags:

Comments

Related Articles