State of UI.animate(...)

Hi Garmin,

What is the current state of WatchUi.animate(...)? Does it work yet? If so, do you have a working example?

/Tonny
  • As far as I am aware, WatchUi.animate() is fully functional, but I don't have a working example on hand. Have you run into any issues with it?
  • I haven't managed to figure out what the "property" is to pass to it. No matter what type of Symbol I pass in, it tells me it's not valid.

    Also, what is the rendering framerate for animate? The same 1 frame/sec as the regular watch, or does it call the updateview more frequently than that for the duration of the animation?
  • I created a new widget and added this code to make the image slide across the screen.

    //! Restore the state of the app and prepare the view to be shown
    function onShow() {
    var drawable = findDrawableById("id_monkey");
    Ui.animate(drawable, :locX, Ui.ANIM_TYPE_EASE_IN, 50, 200, 5.4, method(:onDone));

    }


    You can animate any variable of the Drawable class, though typically it'll either be :locX or :locY. The duration is listed as a Number in the documentation, but it is actually a float. The callback you pass will be called when the animation is complete.
  • I had posted this thread a while back that showed it working as well.
  • Nice, thank you to both of you. I swear I tried the :locX property and got back a not found error, but I must not have! Probably typoed it as locx or something.

    Confirmed working for me, if you create a test widget with default simple display, then change the onExitSleep() code to:
    function onExitSleep() {
    var drawable = findDrawableById("TimeLabel");
    Ui.animate(drawable, :locX, Ui.ANIM_TYPE_EASE_IN, 0, screenWidth/2, 2.5, null);
    }

    it will work to demo.

    I also like your link with the extending of drawable to animate circle, well done.
  • The only properties that seem to animate correctly are locX and locY (also noted in other thread).

    As an extension of TRAVIS's code in that thread, here's some working code to animate radius, just use the locX property to do radius drawing...
    However, the animate function on a rigged drawing does not clear the previous renderings automatically, so you have to take care of that on your own in the draw method. The code here does not do such.

    To trigger animation, Enter Low Power mode, then Exit Low Power mode. Most credit for the following goes to Travis.Vitek, I just fleshed it out for specifics:

    layout.xml:
    <layout id="CircleA">
    <label x="102" y="74" /><!--position-->
    <label x="15" y="1" /><!--radius/fill-->
    </layout>


    AnimateView.mc:
    using Toybox.WatchUi as Ui;
    using Toybox.Graphics as Gfx;
    using Toybox.System as Sys;
    using Toybox.Lang as Lang;


    class MyCircle extends Ui.Drawable
    {
    hidden var radius;
    hidden var fill;
    hidden var color;

    function initialize(params) {
    Ui.Drawable.initialize(params);

    radius = params.get(:radius);
    if (radius == null) {
    radius = 10;
    }

    fill = params.get(:fill);
    if (fill == null) {
    fill = false;
    }

    color = params.get(:color);
    if (color == null) {
    color = Gfx.COLOR_WHITE;
    }
    }

    function draw(dc) {
    dc.setColor(color, Gfx.COLOR_TRANSPARENT);

    if (fill) {
    dc.fillCircle(radius, locY, locX);
    }
    else {
    dc.drawCircle(radius, locY, locX);
    }
    }
    }

    class AnimateView extends Ui.WatchFace {
    var drawable;
    var screenWidth;
    var screenHeight;

    //! Load your resources here
    function onLayout(dc) {
    var layout = Rez.Layouts.CircleA(dc);
    screenWidth=dc.getWidth();
    screenHeight=dc.getHeight();
    var x = layout[0].locX;
    var y = layout[0].locY;
    var radius = layout[1].locX;
    var fill = layout[1].locY;

    drawable = new MyCircle({
    :locX => radius,
    :locY => y,
    :radius => x,
    :fill => fill,
    :color => Gfx.COLOR_RED
    });
    }

    //! Restore the state of the app and prepare the view to be shown
    function onShow() {
    }

    //! Update the view
    function onUpdate(dc) {
    dc.setColor(Gfx.COLOR_BLACK, Gfx.COLOR_TRANSPARENT);
    dc.clear();
    drawable.draw(dc);
    }

    //! The user has just looked at their watch. Timers and animations may be started here.
    function onExitSleep() {
    Ui.animate(drawable, :locX, Ui.ANIM_TYPE_EASE_IN, 10, screenWidth/2, 1.4, null);
    }

    //! Terminate any active timers and prepare for slow updates.
    function onEnterSleep() {

    }

    }
  • or you could rig it up to animate (or in the below code's case, pulsate) constantly while not in lowpower mode... I assume this would absolutely crush the battery life on the watch though ;( Entering lowpower mode does interrupt and stop the animation automatically though.

    Remove the animation from the exit sleep, initialize some view class-scoped vars for beginSize=15; endSize=screenWidth/2; and change the onUpdate to this:
    class AnimationOverloadView extends Ui.WatchFace {
    var drawable;
    var screenWidth;
    var screenHeight;
    var beginSize;
    var endSize;
    var pulsateDirection = true;
    //! Load your resources here
    function onLayout(dc) {
    var layout = Rez.Layouts.CircleA(dc);
    screenWidth=dc.getWidth();
    screenHeight=dc.getHeight();
    var x = layout[0].locX;
    var y = layout[0].locY;
    var radius = layout[1].locX;
    var fill = layout[1].locY;

    drawable = new MyCircle({
    :locX => radius,
    :locY => y,
    :radius => x,
    :fill => fill,
    :color => Gfx.COLOR_RED
    });
    beginSize = 15;
    endSize = screenWidth/2;
    changeDir();
    }

    //! Restore the state of the app and prepare the view to be shown
    function onShow() {
    }

    //! Update the view
    function onUpdate(dc) {
    dc.setColor(Gfx.COLOR_BLACK, Gfx.COLOR_TRANSPARENT);
    dc.clear();
    // Get and show the current time
    var clockTime = Sys.getClockTime();
    drawable.draw(dc);

    }
    function changeDir(){
    pulsateDirection = !pulsateDirection;
    if(pulsateDirection){
    beginSize = screenWidth/2;
    endSize = 15;
    }else{
    beginSize = 15;
    endSize = screenWidth/2;
    }
    Ui.animate(drawable, :locX, Ui.ANIM_TYPE_EASE_IN, beginSize, endSize, 3.91, method(:changeDir));
    }
    //! The user has just looked at their watch. Timers and animations may be started here.
    function onExitSleep() {
    changeDir();
    }

    //! Terminate any active timers and prepare for slow updates.
    function onEnterSleep() {

    }

    }


    and make this the draw method of the circle class:
    function draw(dc) {
    //to manually clear around the circle when it shrinks
    dc.setColor(Gfx.COLOR_BLACK, Gfx.COLOR_TRANSPARENT);
    dc.fillCircle(radius, locY, locX+20);

    dc.setColor(color, Gfx.COLOR_TRANSPARENT);
    if (fill) {
    dc.fillCircle(radius, locY, locX);
    }
    else {
    dc.drawCircle(radius, locY, locX);
    }
    dc.setColor(Gfx.COLOR_WHITE, Gfx.COLOR_TRANSPARENT);
    dc.drawText(radius, locY, Gfx.FONT_NUMBER_MILD, locX.format("%.2f"), Gfx.TEXT_JUSTIFY_CENTER|Gfx.TEXT_JUSTIFY_VCENTER);
    }


    <s>note that this absolutely will not play well with other things drawn on the screen in the same area unless you're handling all of their drawing in the circle class's draw method.</s> I was wrong here, as long as you put all your stuff below the drawable.draw(dc) call, it will work out as normal.
  • I created a new widget and added this code to make the image slide across the screen.

    //! Restore the state of the app and prepare the view to be shown
    function onShow() {
    var drawable = findDrawableById("id_monkey");
    Ui.animate(drawable, :locX, Ui.ANIM_TYPE_EASE_IN, 50, 200, 5.4, method(:onDone));

    }


    You can animate any variable of the Drawable class, though typically it'll either be :locX or :locY. The duration is listed as a Number in the documentation, but it is actually a float. The callback you pass will be called when the animation is complete.


    OK... Got it now.

    I got the semantics wrong. I thought that it was just "drawable.draw(dc)" that would be invoked with a new dc, but I understand now that it is actually a normal "UI.requestUpdate()" with the corresponding "view.onUpdate(dc)".
  • Yeah it is, you could just animate a useless drawable and use the onUpdate() to do your own animation tween calculations.
    No need to dump it all the the draw() method of the drawable like I did above.
    So something like this substitued for the onUpdate in my code above would also work out.
    function onUpdate(dc) {
    dc.setColor(Gfx.COLOR_BLACK, Gfx.COLOR_TRANSPARENT);
    dc.clear();
    drawable.draw(dc);
    dc.drawText(100, 25, Gfx.FONT_NUMBER_HOT, Lang.format("$1$", [ drawable.locX.format("%0.2f")]), Gfx.TEXT_JUSTIFY_LEFT);
    }