How to resume css animation on resumeEvent


#1

Going through the spinning apple angular sample I notice that the apple stops spinning (at least on Android) when the app comes back from the resumeEvent. I was able to detect the resumeEvent, and get the image reference, but could not figure out how to restart the css animation. This would be a great extra addition to the tutorial.


#2

I was able to get the apple spinning again with the resumeEvent, but had to resort to using a new animation set attached to the image ViewChild. Still would be interested in finding out how to interact with the .css animation object. Also I noticed that getting the keyframe animation info with the name in sample “spin” returns the info object but it seems to reset the curve property to ease even though I set it to linear as in the example.

let animationInfo = this.page.getKeyframeAnimationWithName("spin");
let animation = KeyframeAnimation.keyframeAnimationFromInfo(animationInfo);

If I do a console.dir(animationInfo) I expected it would be return all the attributes set on the spin keyframe, in the sample but there are some that don’t match up, it looks like this:

{
JS: “name”: “”,
JS: “duration”: 0.3,
JS: “delay”: 0,
JS: “iterations”: 1,
JS: “curve”: “ease”,
JS: “isForwards”: false,
JS: “isReverse”: false,
JS: “keyframes”: [
JS: {
JS: “duration”: 0,
JS: “declarations”: [
JS: {
JS: “property”: “rotate”,
JS: “value”: 0
JS: }
JS: ]
JS: },
JS: {
JS: “duration”: 1,
JS: “declarations”: [
JS: {
JS: “property”: “rotate”,
JS: “value”: 360
JS: }
JS: ]
JS: }
JS: ]
JS: }

The name makes sense to be blank, but you would think the curve would be the same as the .css declaration?

Still leaves me wondering how to work with the animation in code once it is instantiated by .css, there should be a way to work with it and not sure why the .css based animation stops on resume, while the code based animation using animation.play() has no problem on resume?

Maybe it is supposed to work, but for the very first introductory example having the animation stop on resume is NOT a good first impression. Does anyone from Progress monitor this Getting Started forum? Yikes!


#3

The code I am using to restart the apple spinning on android is working fine:

  if ( this.animationSet.isPlaying != true ) {
    this.animationSet.play().then(() => {
        console.log("Played with code!");
    });
  }

However this technique does not work for iOS I setup my animationSet on ngAfterViewInit with this:


ngAfterViewInit() {
  console.log("ngAfterViewInit");
  let viewImage: View = this.appleImage.nativeElement;
  this.animationSet = new Animation([{
    target: viewImage,
    rotate: 360,
    duration: 3000,
    iterations: Number.POSITIVE_INFINITY,
}]);

I did not add the curve: property because examples I have seen use the conditional approach of something like:

curve: viewImage.ios ? UIViewAnimationCurve.UIViewAnimationCurveLinear : new android.view.animation.LinearInterpolator

However, I don’t see a clear example of getting the UIViewAnimationCurve property imported, and this may have changed since those examples were created? I tried to import AnimationCurve:

import { AnimationCurve } from "ui/enums";

But, while I have access to the properties, I don’t see much in the docs in terms of usage, and not sure if this now automatically converts between iOS an Andriod depending on the target? It is really tough when there are a mix of old and new examples and the documentation, doesn’t really have examples attached to the classes, methods, and properties.

Furthermore, on iOS, I removed the css animation and just went for the code based approach with animationSet.play(). I do not have it autoloaded, but have it invoked on the resumeEvent, it starts animating on the first resume then after that I am getting an uncaught error in the animationSet.play().then promise. I also notice that on iOS the resumeEvent seems to fire quite a few times compared with the single shot that you get on Android, that may just be how iOS works.

I am still searching for just a simple consistent way to restore the animation in the dirt simple spinning apple example why is this so difficult to do? I makes me concerned that there are many other gotchas like this when dealing with platform differences and resume events (or other events) in general. Why so silent around here, does Progress look at this forum or has everyone moved to a new locale?


#4

There are still a lot of unanswered questions, but for the possible benefit of other and may be incite a response from Progress or other interested parties out there, here is my final solution that takes a little different approach for iOS and Android:

import { Component, ElementRef, ViewRef, ViewChild, OnInit, AfterViewInit } from "@angular/core";
import * as app from "application";
import { Image } from "ui/image";
import { KeyframeAnimation } from "ui/animation/keyframe-animation";
import { Animation } from "ui/animation//animation";
import { AnimationCurve } from "ui/enums";
import { Page } from "ui/page";
import { View, Color } from "ui/core/view";

@Component({
  selector: "my-app",
  template: `
    <ActionBar class="action-bar" title="The Famous Spinning Apple"></ActionBar>
    <!--ActionBar style="color: white; background-color: blue;" title="The Famous Spinning Apple"></ActionBar -->
    <!-- Your UI components go here -->
    <Image #appleImage *ngIf="isIos" src="~/images/apple.jpg"></Image>
    <Image #appleImage *ngIf="isAndroid" class="appleSpin" src="~/images/apple.jpg"></Image>
  `,
  styles: [`
    @keyframes spin {
      from { transform: rotate(0); } to { transform: rotate(360) }
    }
    .appleSpin {
      animation-name: spin;
      animation-duration: 3s;
      animation-iteration-count: infinite;
      animation-timing-function: ease;
    }
  `]
})
export class AppComponent implements OnInit, AfterViewInit {
  // Your TypeScript logic goes here
  public appleImage: Image;
  @ViewChild("appleImage") appleImageRef: ElementRef;
  appleAnimation: Animation;
  public isAndroid: boolean;
  public isIos: boolean;

  constructor(private page: Page) {
  }

  ngOnInit(): void {
    console.log("onPageLoaded");
    if (app.ios) {
        this.isAndroid = false;
        this.isIos = true;
    } else if (app.android) {
        this.isAndroid = true;
        this.isIos = false;
    }
 }

  ngAfterViewInit() {
    console.log("ngAfterViewInit");
    this.appleImage = this.appleImageRef.nativeElement;
    console.log('imageID: ' + this.appleImage + ' isLoading: ' + this.appleImage.isLoading + ' isLoaded: ' + this.appleImage.isLoaded);
    if (this.isIos) {
      this.appleAnimation = this.appleImage.createAnimation({
        rotate: 360,
        duration: 3000,
        translate: { x: 0, y: 0},
        iterations: Number.POSITIVE_INFINITY,
        curve: AnimationCurve.easeIn
      });
      this.playAnimation();
    }

    app.on(app.suspendEvent, (args) => {
      if (args.android) {
        // For Android applications, args.android is an android activity class.
        console.log("Activity: " + args.android);
        console.log('CLASSNAME REPORTS: ' + this.appleImage.className);
        this.appleImage.className = '';
      } else if (args.ios) {
        // For iOS applications, args.ios is UIApplication.
        console.log("UIApplication: " + args.ios);
        if (this.appleAnimation.isPlaying == true) {
          this.appleAnimation.cancel();
        }
      }
    });

    app.on(app.resumeEvent, (args: app.ApplicationEventData) => {
      if (args.android) {
        // For Android applications, args.android is an android activity class.
        console.log("Activity: " + args.android);
        this.appleImage.animate({ rotate: 0 })
          .then(() => { 
            console.log("Reset rotation back to 0 finished.");
            this.appleImage.animate({
              rotate: 360,
              translate: { x: 0, y: 0 },
              duration: 3000,
              iterations: Number.POSITIVE_INFINITY
            });
        })
          .catch((e) => {
          console.log(e.message);
        });

      } else if (args.ios) {
        // For iOS applications, args.ios is UIApplication.
        console.log("UIApplication: " + args.ios);
        this.playAnimation();
      }
    });
  }

  playAnimation() {
    console.log('appleImage is loaded: ' + this.appleImage.isLoaded + ' animation is playing: ' + this.appleAnimation.isPlaying);
    if ( app.ios && this.appleAnimation.isPlaying != true ) {
      this.appleAnimation.play().then(() => {
        console.log("return from promise?  appleImage is loaded: " + this.appleImage.isLoaded);
        this.appleImage.translateY = 0;
        this.appleImage.translateX = 0;
      }).catch((e) => {
        console.log(e.message);
      });
    }
  }

}

iOS plays much better with the NativeScript api and the loading of the image. I tried to add a loaded event on the image tag, but that never fired, that would have allowed for a cleaner approach. This is because Android never set the animationSet.isPlaying property, but it did eventually set the isLoaded property on the image. I was going to try and create the animationSet after getting the image isLoaded event, that way, in theory, I could have created the animation set then all would work as expected. It would be nice to see an official example and this one has some questionable approaches.