Animator

API Reference: UI.Animator

Animator is used to change the appearance of the UI objects with animation.

TypeScript code blocks includes example of how to implement, override & add components with theme. Create page with UI-Editor to make your page themeable and then able to implement themeable components programmatically. Once page created with UI-Editor, it generates class under scripts/generated/pages. Extend that class with below typescript classes.

TypeScript
JavaScript
TypeScript
import PageSampleDesign from 'generated/pages/pageSample';
import FlexLayout = require('sf-core/ui/flexlayout');
import Application = require('sf-core/application');
import Color = require('sf-core/ui/color');
import View = require('sf-core/ui/view');
import System = require('sf-core/device/system');
import Button = require('sf-core/ui/button');
import Animator = require('sf-core/ui/animator');
//You should create new Page from UI-Editor and extend with it.
export default class AnimatorSample extends PageSampleDesign {
myView: View;
myButton: Button;
constructor() {
super();
// Overrides super.onShow method
this.onShow = onShow.bind(this, this.onShow.bind(this));
// Overrides super.onLoad method
this.onLoad = onLoad.bind(this, this.onLoad.bind(this));
this.layout.flexDirection = FlexLayout.FlexDirection.ROW;
this.layout.justifyContent = FlexLayout.JustifyContent.CENTER;
this.layout.alignItems = FlexLayout.AlignItems.CENTER;
}
}
/**
* @event onShow
* This event is called when a page appears on the screen (everytime).
* @param {function} superOnShow super onShow function
* @param {Object} parameters passed from Router.go function
*/
function onShow(superOnShow: () => void) {
const { headerBar } = System.OS === "Android" ? this : this.parentController;
superOnShow();
Application.statusBar.visible = false;
headerBar.visible = false;
}
/**
* @event onLoad
* This event is called once when page is created.
* @param {function} superOnLoad super onLoad function
*/
function onLoad(superOnLoad: () => void) {
superOnLoad();
this.myView = new View();
this.myButton = new Button({
text: 'Animate',
onPress: (): void => {
this.myView.backgroundColor = Color.RED;
let animationRootView = System.OS === "iOS" ? this.layout : this.myView.parent;
Animator.animate(animationRootView, 5000, (): void => {
this.myView.left = 150;
this.myView.right = 150;
}).then(2500, (): void => {
this.myView.left = 10;
this.myView.right = 10;
}).complete((): void => {
this.myView.backgroundColor = Color.GREEN;
});
}
});
this.layout.addChild(this.myView, "myView", ".sf-view", {
left: 10,
top: 10,
right: 10,
height: 100,
flexProps: {
positionType: "ABSOLUTE",
},
backgroundColor: "#008000"
});
this.layout.addChild(this.myButton, "myButton", ".sf-button", {
left: 10,
top: 150,
right: 10,
height: 65,
flexProps: {
positionType: "ABSOLUTE",
},
backgroundColor: "#808080",
});
}
JavaScript
const Page = require("sf-core/ui/page");
const extend = require("js-base/core/extend");
const Color = require('sf-core/ui/color');
const View = require('sf-core/ui/view');
const System = require('sf-core/device/system');
const Button = require('sf-core/ui/button');
const Animator = require('sf-core/ui/animator');
const FlexLayout = require('sf-core/ui/flexlayout');
var Page1 = extend(Page)(
function(_super) {
var self = this;
_super(this, {
onShow: function(params) {
Application.statusBar.visible = false;
this.headerBar.visible = false; //For Android
}
});
var myView = new View({
left: 10,
top: 10,
right: 10,
height: 100,
positionType: FlexLayout.PositionType.ABSOLUTE,
backgroundColor: Color.GREEN
});
var myButton = new Button({
text: 'Animate',
left: 10,
top: 150,
right: 10,
height: 65,
positionType: FlexLayout.PositionType.ABSOLUTE,
backgroundColor: Color.GRAY,
onPress: function() {
myView.backgroundColor = Color.RED;
var animationRootView = System.OS === "iOS" ? self.layout : myView.parent;
Animator.animate(animationRootView, 5000, function() {
myView.left = 150;
myView.right = 150;
}).then(2500, function() {
myView.left = 10;
myView.right = 10;
}).complete(function() {
myView.backgroundColor = Color.GREEN;
});
}.bind(this)
});
this.layout.addChild(myView);
this.layout.addChild(myButton);
}
);
module.exports = Page1;

On iOS side, Animator is not working smoothly if it is applied to button object. We suggest using other UI elements for animations.

Animation Root View

The animation is happening within the Animation Root View. It is similar, but not exactly, during the animation, FlexLayout.applyLayout() is called to the root object. It is important to pick the correct root.

Best practices for selecting the perfect root

  • On Android just pick the parent view

  • On iOS, pick the most parent object. If the animating view is a direct descendant of the page, then select the page.layout or the view is within a ScrollView, then select the scrollView.layout. The rule is simple, traverse to all the way to the parents, if there is no parent, use that view. So page.layout, scrollView.layout do not have any parents.

Limitations of the root view

A root view can handle one animation at a time. It is not possible to use different animate functions and providing same root view most likely to cause exception

View Animations

Views on the screen need to be repositioned. It is possible to animate the scale, position, rotation, and alpha of any type of View with View animations.

Moving Into Header Bar

View can have position on HeaderBar by means of Dialog and getScreenLocation() function for a View or a HeaderBarItem.

Using Dialog on Android and iOS

Android runs asynchronous on onShow() function of Dialog, so it must be animated onShow() function of Page. Dialog BackgroundColor must be set transparent on iOS.

TypeScript
JavaScript
TypeScript
import PageSampleDesign from 'generated/pages/pageSample';
import FlexLayout = require('sf-core/ui/flexlayout');
import Application = require('sf-core/application');
import Color = require('sf-core/ui/color');
import View = require('sf-core/ui/view');
import System = require('sf-core/device/system');
import Button = require('sf-core/ui/button');
import Animator = require('sf-core/ui/animator');
import ImageView = require('sf-core/ui/imageview');
import HeaderBarItem = require('sf-core/ui/headerbaritem');
import Dialog = require("sf-core/ui/dialog");
//You should create new Page from UI-Editor and extend with it.
export default class AnimatorSample extends PageSampleDesign {
myFlProd: ImageView;
myButton: Button;
myFlCart: ImageView;
headerBarItem: HeaderBarItem;
myDialog: Dialog;
animationRootView: View;
constructor() {
super();
// Overrides super.onShow method
this.onShow = onShow.bind(this, this.onShow.bind(this));
// Overrides super.onLoad method
this.onLoad = onLoad.bind(this, this.onLoad.bind(this));
this.layout.flexDirection = FlexLayout.FlexDirection.ROW;
this.layout.justifyContent = FlexLayout.JustifyContent.CENTER;
this.layout.alignItems = FlexLayout.AlignItems.CENTER;
}
animate(): void {
let itemPos = this.headerBarItem.getScreenLocation();
let itemSize = this.headerBarItem.size;
//@ts-ignore
let animationRootView = System.OS === "iOS" ? this.myDialog.layout : this.myFlProd.parent;
Animator.animate(animationRootView, 1200, (): void => {
this.myFlProd.top = itemPos.y + (itemSize.height / 2) - this.myFlProd.height / 2 - (System.OS === "Android" ? Application.statusBar.height : 0);
this.myFlProd.left = itemPos.x + (itemSize.width / 2) - this.myFlProd.width / 2;
this.myFlProd.scale = { x: 0.01, y: 0.01 };
}).complete((): void => {
this.myFlProd.visible = false;
this.myDialog.hide();
this.headerBarItem.badge.text = "1";
});
}
}
/**
* @event onShow
* This event is called when a page appears on the screen (everytime).
* @param {function} superOnShow super onShow function
* @param {Object} parameters passed from Router.go function
*/
function onShow(superOnShow: () => void) {
const { headerBar } = System.OS === "Android" ? this : this.parentController;
superOnShow();
Application.statusBar.visible = true;
headerBar.visible = true;
this.myButton.onPress = (): void => {
this.myDialog.show();
if (System.OS === "iOS") {
this.myDialog.layout.backgroundColor = Color.TRANSPARENT;
this.animate();
}
};
this.myDialog.android.onShow = (): void => {
this.animate();
};
}
/**
* @event onLoad
* This event is called once when page is created.
* @param {function} superOnLoad super onLoad function
*/
function onLoad(superOnLoad: () => void) {
superOnLoad();
this.myButton = new Button({
text: "Add To Cart"
});
this.myFlProd = new ImageView({
image: "images://bird.png"
});
this.myFlCart = new ImageView({
image: "images://bird.png"
});
this.headerBarItem = new HeaderBarItem({
title: "Cart",
color: Color.BLACK
});
this.headerBar.setItems([this.headerBarItem]);
this.myDialog = new Dialog();
this.myDialog.android.isTransparent = true;
this.myDialog.layout.addChild(this.myFlProd);
this.animationRootView = System.OS === "iOS" ? this.myDialog.layout : this.myFlProd.parent;
this.myDialog.layout.applyLayout();
this.layout.addChild(this.myButton, "myButton", ".sf-button", {
width: 150,
height: 70,
backgroundColor: "#57A5CF",
top: 100,
left: 100
});
this.layout.addChild(this.myFlCart, "myFlCart", ".sf-imageView", {
width: 200,
height: 200,
top: 110,
left: 80
});
}
JavaScript
const Application = require("sf-core/application");
const extend = require("js-base/core/extend");
const System = require("sf-core/device/system");
const Button = require('sf-core/ui/button');
const Color = require('sf-core/ui/color');
const Page = require("sf-core/ui/page");
const HeaderBarItem = require('sf-core/ui/headerbaritem');
const Dialog = require("sf-core/ui/dialog");
const Animator = require('sf-core/ui/animator');
const ImageView = require('sf-core/ui/imageview');
var myButton = new Button({
width: 150,
height: 70,
backgroundColor: Color.create("#57A5CF"),
text: "Add To Cart",
top: 100,
left: 100
});
var myFlProd = new ImageView({
width: 200,
height: 200,
image: "images://bird.png",
top: 150,
left: 100
});
var myFlCart = new ImageView({
width: 200,
height: 200,
image: "images://bird.png",
top: 110,
left: 80,
});
var headerBarItem = new HeaderBarItem({
title: "Cart",
color: Color.BLACK
});
const Page1 = extend(Page)(
function(_super) {
_super(this);
const page = this;
Application.statusBar.visible = false;
page.headerBar.visible = false; //For Android
page.headerBar.backgroundColor = Color.WHITE;
page.headerBar.title = "";
page.headerBar.setItems([headerBarItem]);
this.layout.addChild(myFlCart);
this.layout.addChild(myButton);
var animationRootView;
var myDialog = new Dialog({
isTransparent: true,
});
myDialog.layout.addChild(myFlProd);
animationRootView = System.OS === "iOS" ? myDialog.layout : myFlProd.parent;
myDialog.layout.applyLayout();
this.onShow = function() {
myButton.onPress = function() {
myDialog.show();
if (System.OS === "iOS") {
myDialog.layout.backgroundColor = Color.TRANSPARENT;
animate();
}
};
myDialog.android.onShow = function() {
animate();
};
function animate() {
var itemPos = headerBarItem.getScreenLocation();
var itemSize = headerBarItem.size;
Animator.animate(animationRootView, 1200, function() {
myFlProd.top = itemPos.y + (itemSize.height / 2) - myFlProd.height / 2 - (System.OS === "Android" ? Application.statusBar.height : 0);
myFlProd.left = itemPos.x + (itemSize.width / 2) - myFlProd.width / 2;
myFlProd.scale = { x: 0.01, y: 0.01 };
}).complete(function() {
myFlProd.visible = false;
myDialog.hide();
headerBarItem.badge.text = "1";
});
}
}.bind(this);
});
module.exports = Page1;

Non-practical animations

There are several scenarios that animate can cause problems or may not work as intended.

ListView

Do not animate anything within the ListViewItem. ListView is re-using the objects. In that case, row rendering operations will conflict with the ongoing animations.

Non-size & Non-position

Animation works best with sizes and position affecting scenarios. Animating other properties might not result as expected (such as colors, texts)

SwipeView page change animations

Moving the page indicator with animation in a SwipeView to make a carousel display, can cause problems. The user is going to swipe the pages again before the animation is complete. In that case, another animation is about to start with using same root view.