Timers Example

This example demonstrates the use of a timer to periodically update a user interface.

A screenshot of the application.

We import the classes and modules that will be needed by the application. The most relevant are the classes from the android.os module.

from java.lang import Math, Object, Runnable
from java.util import ArrayList, Timer, TimerTask
from android.app import Activity
from android.content import Context
from android.graphics import Bitmap, Canvas, Color, Paint
from android.os import Bundle, Handler
from android.view import MotionEvent, View

The TimersActivity class is derived from the standard Activity class and represents the application. Android will create an instance of this class when the user runs it.

class TimersActivity(Activity):

    __interfaces__ = [Runnable]

The activity implements the Runnable interface from the java.lang module. This requires us to implement the run method.

The initialisation method simply calls the corresponding method in the base class.

    def __init__(self):
    
        Activity.__init__(self)

The onCreate method is called when the activity is created. We call the onCreate method of the base class with the Bundle object passed to this method to set up the application.

    @args(void, [Bundle])
    def onCreate(self, bundle):
    
        Activity.onCreate(self, bundle)

We create a Handler object that enables us to schedule events so that we can arrange for regular timed updates. This may not be the most optimal way to try and perform animations with a certain number of frames per second.

        self.handler = Handler()

We also create a custom view that is used as the main view in the activity.

        self.view = DrawView(self)
        self.setContentView(self.view)

When the activity starts or is navigated to by the user, the onResume method is called. We call the corresponding method in the base class before requesting a message to be delivered after a 25 milliseconds delay.

    def onResume(self):
    
        Activity.onResume(self)
        self.handler.postDelayed(self, long(25))

When the user navigates away from the activity, the onPause method is called. We call the onPause method in the base class and remove any pending message callbacks from the message queue. This prevents messages from being delivered when the activity is inactive.

    def onPause(self):
    
        Activity.onPause(self)
        self.handler.removeCallbacks(self)

Since we declared that the activity implements the Runnable interface, we need to define a run method that will be called when a message, sent using the Handler's postDelayed method, is delivered to the activity. Our implementation simply updates the custom view and requests a new message so that another update can be performed 25 milliseconds later.

    def run(self):
    
        self.view.update()
        self.handler.postDelayed(self, long(25))

We define a Point class to represent points defined using polar coordinates.

class Point(Object):

    __fields__ = {"radius": double, "angle": double}

Instances of this class contain two attributes that other objects need to access, so we define these as fields in order to make them accessible.

The initialisation method accepts two doubles for its radius and angle parameters which we store in the corresponding attributes.

    @args(void, [double, double])
    def __init__(self, radius, angle):
    
        Object.__init__(self)
        self.radius = radius
        self.angle = angle

We also define a PointArray class, using the standard ArrayList template class. This enables us to create arrays containing instances of the Point class we defined above.

class PointArray(ArrayList):

    __item_types__ = [Point]
    
    def __init__(self):
        ArrayList.__init__(self)

The item type held by the container is defined using the __item_types__ attribute. This attribute is only used at compile time. The __init__ method only needs to call the corresponding method in the base class.

The DrawView class is derived from the standard View class and is used to show custom graphics in the activity.

class DrawView(View):

    PI_2 = 6.283185307179586

For convenience, we define a constant to hold the value of 2 * pi. This is only used at compile time, so we can't use values from the Java standard library.

As with other views, the initialisation method accepts a Context as its parameter and initialises itself by calling the initialisation method of the View class.

    @args(void, [Context])
    def __init__(self, context):
    
        View.__init__(self, context)

We define two Paints that we will later use to draw the view's background and foreground decorations.

        self.background = Paint()
        self.background.setColor(Color.BLACK)
        self.foreground = Paint()
        self.foreground.setColor(Color.WHITE)

We also create an instance of a PointArray in which we store Point objects. These points will be used in the onDraw method to draw foreground decorations in the view. We add one to ensure that something is shown when the view is displayed.

        self.points = PointArray()
        self.points.add(Point(40.0, 0.0))

Note that the use of the PointArray class is only necessary if we do not wish to assign items to the instance since we need to specify type information for it. In the above code, we could simply write self.points = [Point(40.0, 0.0)] instead.

The onSizeChanged method is called when the view is first shown and whenever it changes size afterwards.

    @args(void, [int, int, int, int])
    def onSizeChanged(self, width, height, oldWidth, oldHeight):

We record the coordinates of the centre of the view as attributes of the view, as well as calculating a reasonable size for the circles we intend to draw on it.

        self.ox = width/2
        self.oy = height/2
        self.bs = Math.min(width/50, height/50)

The onDraw method is called when the view needs to be displayed. The parameter is a Canvas object that we draw onto. We call the onDraw method in the base class before adding our own decorations.

    @args(void, [Canvas])
    def onDraw(self, canvas):
    
        View.onDraw(self, canvas)

We fill the background with the Paint that we defined earlier.

        canvas.drawPaint(self.background)

We iterate over each point in the list held by the view's points attribute.

        it = self.points.iterator()
        
        while it.hasNext():

For each point, we obtain its position relative to the centre of the view and draw a circle with the radius calculated earlier.

            p = it.next()
            r = p.radius
            
            x = self.ox + r * Math.cos(p.angle)
            y = self.oy + r * Math.sin(p.angle)
            canvas.drawCircle(x, y, self.bs, self.foreground)

If the point happens to be at the centre of the view, we remove it from the list after drawing it.

            if r == 0.0:
                it.remove()

The onTouchEvent method is called when the view receives touch events. We are only interested in events that inform us that the user has touched the view.

    @args(bool, [MotionEvent])
    def onTouchEvent(self, event):
    
        if event.getAction() != MotionEvent.ACTION_DOWN:
            return False

We ignore any other kind of event, returning False to indicate that we did not handle the event.

For the touch events that we handle, we convert the coordinates of the touch from the view coordinate system to the polar coordinate system used by the Point class, and we add a Point to the points list for display.

        x = double(event.getX()) - self.ox
        y = double(event.getY()) - self.oy
        r = Math.sqrt(Math.pow(x, 2.0) + Math.pow(y, 2.0))
        a = Math.acos(x/r)
        if y < 0.0:
            a = DrawView.PI_2 - a
        
        self.points.add(Point(r, a))

We return True to indicate that we handled the event.

        return True

When each update message is received by the activity, its run method calls this method of the view.

    def update(self):
    
        i = 0
        l = len(self.points)

We simply iterate over all points in the list held by the points attribute, changing the angle of each and decreasing its radius towards a minimum value of 0.0.

        while i < l:
            angle = (self.points[i].angle + 0.06) % DrawView.PI_2
            self.points[i].angle = angle
            radius = Math.max(0.0, self.points[i].radius - 0.5)
            self.points[i].radius = radius
            i += 1
        
        self.invalidate()

Finally, we invalidate the view to ensure that the onDraw method will be called, updating the contents of the view to the user.

Files