Custom Animated Interactivity #Dialler

Problem: Calculating the button which has been pressed within a Painted Canvas

I created a telephone dial using Flutter’s CustomPainter API. This permitted me to draw the various elements onto the screen relative to the size of the screen. The problem now is : how to detect which number has been dialled. There are no workable Gesture detection strategies within the custom painter which allows me to directly detect on individual elements, like the numbers. I can however detect when the canvas is touched using the hitTest method of the CustomPainter class. This provides me with the screen coordinates (offset) of the x and y position of the ‘hit’; the position where the user has touched the screen.

Now, to be able to work out how that position corresponds to a number (on the dialler) I need to do some math. Given the size of the screen I can easily calculate the center by divided the x/y values by two. To illustrate this I used the CustomPainter to draw to red circles on my canvas; one at the center and one where a hit occurs.

Initial Dial Drawing with center point and touched point highlighted

Now, if I can calculate the angle from the position of the calculated center to the position of the hit I will be able to map the value to a known position of a number on the dial.

In the above example the tapped area shown on the left at the number seven is 50(x)  and 240 (y). It’s important to note that the values used by Flutter are logical pixels and not physical ones. So these values don’t correspond to the actual screen resolution. In the case here, the red spot at dial seven is 50 logical pixels along the x axis and 240 logical pixels along the y axis. These are both relative to the top-left of the  canvas.

The center, in my case, working on a physical Pixel 3 XL, is at 205,205 (the canvas is therefore a square with 410 logical pixel sides)

I can now calculate the angle from the center of the canvas to the hit point using Dart’s built in atan2 function

double rads = atan2(position.dy - (canvasSize.height / 2),
    position.dx - (canvasSize.width / 2));

ref : https://api.flutter.dev/flutter/dart-math/atan2.html

Using the x and y coordinates above equates to the following:

rads = atan2(240 - 205, 50 - 205);

Which returns 2.92 radians (rounded to 2 decimal places)
To calculate the angle in degrees, Dart has a build in function. Otherwise the formula is :

(2.92 * 180.0) / pi

Which returns 167 degrees and is confirmed by Google:

Google Calculator to validate Radians and Degrees conversion
The Dial with a protractor image from Mathsisfun.com superimposed to show the angles look right

Dart’s atan2 function returns a range from -pi (3.14159…) to +pi. And pi * Rad equals 180 degrees. So the final value in degrees will always between -180 and +180. Therefore the sign (+/-) determines whether the hit was in the upper or lower half of the dial.

I can now calculate which number is being dialled  based on the initial hit.  The next step is to prevent the dial from turning beyond the stopper. The stopper was used as a catch on rotary phones to indicate the desired number before automatically returning the dial back to its original position. The caller would therefore place the finger inside a circular opening above the desire number and rotate the dial clockwise until they reached the stopper. The numbers remained stationary and the angle of rotation was used to calculate each digit of the telephone number to call.

Leave a Reply