Pebble SDK 2.0 Tutorial #4: Animations and Timers

Required Reading

Pebble SDK 2.0 Tutorial #1: Your First Watchapp

Pebble SDK 2.0 Tutorial #2: Telling the Time

Pebble SDK 2.0 Tutorial #3: Images and Fonts

Introduction

After adding custom images and fonts to our watch face, the next logical way to improve it is to add non-static elements, movement if you will. For this we have Animations! Using the Pebble SDK provided Animation structure we can schedule movements of a Layer‘s bounds whenever we want. This sort of “start here and go here” animation is called ‘tweened’ animation. The alternative kind offers greater flexibility and is achieved through the use of AppTimers.

Tweened Animation

Firstly, we will apply a simple example of a tweened animation to our tutorial watch face. Start by importing the project code from the last section into CloudPebble and giving it a new name, such as ‘SDKTut4’ for example. Be sure to change the short and long app names in the ‘Settings’ section! We are going to animate the TextLayer showing the time when the minute changes. The first step is to change the type of tick event we subscribe to to SECOND_UNIT in init():

tick_timer_service_subscribe(SECOND_UNIT, (TickHandler) tick_handler);

This will enable us to be more precise about when the time display changes, which should ideally be as close to the zero-second mark as possible. To carry out a tweened animation we create a PropertyAnimation instance and specify its duration, delay after scheduling (allowing sub-second timing) and a handler function to be called when it has finished to free up the memory we used in creating it in the first place. This process is summarized in the code segment below, which should be added before the tick_handler() function:

void on_animation_stopped(Animation *anim, bool finished, void *context)
{
	//Free the memory used by the Animation
	property_animation_destroy((PropertyAnimation*) anim);
}

void animate_layer(Layer *layer, GRect *start, GRect *finish, int duration, int delay)
{
	//Declare animation
	PropertyAnimation *anim = property_animation_create_layer_frame(layer, start, finish);

	//Set characteristics
	animation_set_duration((Animation*) anim, duration);
	animation_set_delay((Animation*) anim, delay);

	//Set stopped handler to free memory
	AnimationHandlers handlers = {
		//The reference to the stopped handler is the only one in the array
		.stopped = (AnimationStoppedHandler) on_animation_stopped
	};
	animation_set_handlers((Animation*) anim, handlers, NULL);

	//Start animation!
	animation_schedule((Animation*) anim);
}

Note: If you are compiling for the Basalt platform, you do not need to manually destroy your animation, so leave the handler out. 

You can cast a PropertyAnimation* pointer to a Animation* pointer and vice versa where needed. In addition, for simplicity the GRects describing the start and finish positions of the animated Layer are created on the heap, and then specified to the animate_layer() wrapper function as pointers using the ‘&’ operator.

Now that these new functions are in place, it is time to use them! The animation we will add will slide the time display out to the right hand side at seconds == 59 and then change the time and slide it back in from the left on seconds == 0. To do this, we simply modify our tick_handler() function to create the GRects and call the wrapper function to schedule the animations. This is shown by annotated example below:

void tick_handler(struct tm *tick_time, TimeUnits units_changed)
{
	//Format the buffer string using tick_time as the time source
	strftime(buffer, sizeof("00:00"), "%H:%M", tick_time);

	int seconds = tick_time->tm_sec;

	if(seconds == 59)
	{
		//Slide offscreen to the right
		GRect start = GRect(0, 53, 144, 168);
		GRect finish = GRect(144, 53, 144, 168);
		animate_layer(text_layer_get_layer(text_layer), &start, &finish, 300, 500);
	}

	else if(seconds == 0)
	{
		//Change the TextLayer text to show the new time!
		text_layer_set_text(text_layer, buffer);

		//Slide onscreen from the left
		GRect start = GRect(-144, 53, 144, 168);
		GRect finish = GRect(0, 53, 144, 168);
		animate_layer(text_layer_get_layer(text_layer), &start, &finish, 300, 500);
	}

	else
	{
		//Change the TextLayer text to show the new time!
		text_layer_set_text(text_layer, buffer);
	}
}

Compile this and check it works. If you are unsure about the timing, remove the if statements and have the Animation run every second (although perhaps only the first one!) and work your way up to two working in tandem.

Timers

The other main method of moving elements around is to use an AppTimer. These allow you to schedule something to happen whenever and how often you like. The paradigm is that you register some callback (another name for handler) function to run after a given delay. When that delay has elapsed, the callback function is called and performs your task. You can think of an Animation as a task executed by an AppTimer but with a very small delay. The example we are going to create is a small shape that moves back and forth above the time display as an extra aesthetic touch.

To enable a smooth animation effect, the rate at which the shape moves its position should be at least 25 frames per second. At this rate, the delay in milliseconds between AppTimer callback executions will be 1000ms / 25 frames per second = 40ms delay. The first step is to create a new TextLayer to be our shape (here, a square). Do this at the top of the C file to accompany the other pointers:

TextLayer *text_layer, *square_layer;

We are using a TextLayer for the convenience of being able to set just its bounds and background colour. Otherwise we would have go into graphics and update procedures, which are beyond the scope of this section. Also, we will need to declare the other elements of our moving cube; the AppTimer, its size, the time delta between frames and its movement direction, which will be either 1 or -1:

AppTimer *timer;
const int square_size = 10;
const int delta = 40;
int dx = 1;

Next, we define the timer callback to update the position of the square. As you will see below, there are several stages to complete each time the callback is called:

void timer_callback(void *data) {
	//Get current position
	GRect current = layer_get_frame(text_layer_get_layer(square_layer));

	//Check to see if we have hit the edges
	if(current.origin.x > 144 - square_size)
	{
		dx = -1;	//Reverse
	}
	else if(current.origin.x < 0)
	{
		dx = 1;	//Forwards
	}

	//Move the square to the next position, modifying the x value
	GRect next = GRect(current.origin.x + dx, current.origin.y, square_size, square_size);
	layer_set_frame(text_layer_get_layer(square_layer), next);

	//Register next execution
	timer = app_timer_register(delta, (AppTimerCallback) timer_callback, NULL);
}

Make sure this callback is defined before its first use, which will be in window_load() after the TextLayer itself is allocated, as shown below:

//Create the square layer
square_layer = text_layer_create(GRect(0, 55, square_size, square_size));
text_layer_set_background_color(square_layer, GColorWhite);
layer_add_child(window_get_root_layer(window), text_layer_get_layer(square_layer));

Then, at the end of the function, start the chain reaction with an initial timer registration:

//Start the square moving
timer = app_timer_register(delta, (AppTimerCallback) timer_callback, NULL);

Finally, we must add function calls to window_unload() to tear down the elements related to the moving square and free the memory used:

//Cancel timer
app_timer_cancel(timer);

//Destroy square layer
text_layer_destroy(square_layer);

Finally, recompile and test the resulting watch face, and see the results! A thing to note is that waking up the Pebble’s CPU this often will incur battery life penalties, so use timers sparingly!

Conclusions

So that’s how to use Animations and AppTimers! If you think about it, there is a way to replace the moving square’s AppTimer with a PropertyAnimation, rendering its use here void. A more robust example is my Starfield Demo, which uses such timers as the core of its operation. A link to the finished product from this section can be found HERE. Enjoy! If you have queries or comments, make them below, or Tweet me.

Advertisements
29 comments
  1. Ender said:

    Pebble should put a link to your site on their SDK website. Your in depth tutorials are the best and just using them I was able to create my own app AND put some nice aesthetic finishing touches on it. Good job once again!

    • bonsitm said:

      Thanks again Ender, maybe you could recommend they do? Have you written about your app? I would be very much interested to see the result!

  2. Jordan said:

    Loving this tutorials as well, im working on an animated mario face haha. I just had a quick question if you dont mind.
    If I were to use second ticks vs minute ticks, would that use more battery/CPU since its generating a new tick every second as apposed to every minute?
    thanks Jordan

    • bonsitm said:

      Hi Jordan,

      You are correct in your assumption, but there may be more subtlety than that. In my experience the effect is minimal (i.e: it won’t cut days off your battery life), so use your best judgement.

      Thanks

      • Jordan said:

        Hey,
        Is there any way to load the bitmaps and layers and destroy them on demand, rather then when the face is opened, it seems I have too many layers and have run out of memory,

        Thanks,
        Jordan

      • bonsitm said:

        Indeed, just do the following:

        1. Declare the pointer to the layer globally and bitmap_layer_create() and bitmap_layer_destroy() whenever you like.
        2. Stack allocate the BitmapLayers as you go, although you may run into longevity problems: a lot of components in the Pebble SDK like to live as long as possible.

        3. Experiment!

      • Jordan said:

        Awesome thanks for the ideas,
        just curious what you mean by stack allocate though?

      • bonsitm said:

        ‘Stack allocate’ refers to creating a variable that lasts only until it goes out of scope at the next end of block (a block is the code between the { and } symbols in an if statement, for example). After this time it is free’d, unless it’s allocated dynamically. In that case you would have to free it manually.

  3. Hello!

    First, thanks for these tutorials, they’re awesome!

    Second, I think the formatting for this article is broken…

    • bonsitm said:

      Hi Joao, thanks for the feedback. I don’t know how it happened, but it appears it is indeed broken. I’ll fix it now!

      • Some of the code here is messed up and uses things like > and & can you fix that?

      • bonsitm said:

        Ah, the ol’ WordPress touch. I’ll fix it now!

  4. dexterjr said:

    Hi there, very nice tutorials 🙂 I did all of them so far, although the comparison for changing direction of the floating layer must be changed. But every programmer must notice this 🙂

    • bonsitm said:

      Hi, thanks for the feedback! Which line do you mean?

      • dexterjr said:

        those lines from timer_callback function
        if(current.origin.x 0)

        if the origin is bigger than 144 it must be reverse and move forward when reaches 0

      • bonsitm said:

        You are correct! Thanks for letting me know, I have acknowledged you at the top of the post 🙂

  5. Correct me if I am wrong but isn’t “if(current.origin.x > 0)” should be “if(current.origin.x < 0)” ? So it should reverse DX to positive when it hit left border.

  6. I have tried multiple times to get this to work but it doesnt. Even if I copy and paste the text CloudPebble gives me two errors:

    ../src/main.c: In function ‘tick_handler’:
    ../src/main.c:23:9: error: implicit declaration of function ‘animate_layer’ [-Werror=implicit-function-declaration]
    ../src/main.c: At top level:
    ../src/main.c:122:6: error: conflicting types for ‘animate_layer’ [-Werror]
    ../src/main.c:23:9: note: previous implicit declaration of ‘animate_layer’ was here
    cc1: all warnings being treated as errors

    I have followed your texts word for word but still his happens

    • Wow… ignore my previous comment.., It turns out I hadn’t read your guide properly… I feel like a massive idiot now 🙂

      • bonsitm said:

        Happens to the best of us!

  7. Hiya,

    Am using these tutorials to get my watchface idea out off my head. Thank you for these.

    Just a small note to anyone not seeing the moving square:

    I think declared layers in the window_load function must be listed in a bottom to top way. I had to list the square layer after the inverter layer to get it to appear.

    The included sdktut4.zip file helped in this respect.

    Cheers.

  8. Hi,

    Thanks for the tutorial. Your tutorial was very helpful in understanding the documentation.

    I’m facing a issue with my Watchface project. I’m using SDK 3.0 Basalt and was trying to use your first method to animate a Layer in my watchface. “property_animation_destroy” in the ‘.stopped’ handler causes the application to crash. App functions properly without the “property_animation_destroy” though. Is that mandatory to destroy a property manually or it destroys itself after animation? Please let me know. Thanks.

    • bonsitm said:

      Hi Karthikeyan, this requirement changed in SDK 3.0 for the Basalt platform. I have amended the post to reflect this.

      Thanks for pointing it out!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: