Animation Guide
Advanced cross platform animation with Apple Swift
Works with BETA5 and higher
Animation effects, sparsely used, greatly enhance the attractiveness of an app. With BETA5, we reworked the Animation API. This guide gives an overview over the animation API and the inner workings.
We animate all vector elements using a powerful animation model drawing on CoreAnimations, Web Animations, SMIL and CSS Animations. We are pretty confident to enable any kind - even complicated animations - using the API.
Some other good reads on mobile application & animation
- http://www.creativebloq.com/features/how-to-use-animation-in-mobile-apps
- https://uxplanet.org/prototyping-mobile-ui-animations-5-inspiring-examples-376693fd7c45
- https://tubikstudio.com/ui-in-action-15-animated-design-concepts-of-mobile-ui/
Source code for example
Please find the sample project here. Edit the start.swift to run different examples.
Animation demo
Basics
Let's quickly recap how to access a vector element. The page consists a custom control that contains a rectangle with the id rect1:
To get a refences to any vector element:
- make sure vector element has an id (here rect1)
- use the drawing attribute and find method to get a reference
- create a basic animation, here the rotate animation
Use an id tag to identify the vector element
BoundingBox
A rectangular bounding box which is used to describe the bounds of a vector element independant of its shape. You retrieve the BoundingBox using the getBoundingBox() method available on any vector element.
Get vector element absolute location
The absolute location of the vector element is computed by the absolute location of the bounding box. Here the code to get the absolute XY coordinates of the vector element on the mobile device in pixels.
func getAbsoluteCoordinates(of vectorElement:SCDSvgShape) -> (Float,Float) {
let location = vectorElement.getBoundingBox().location
return (Float(location.x),Float(location.y))
}
Rotate vector element
A few lines of code to animate the rectangle:
import ScadeKit
class BasicPageAdapter: SCDLatticePageAdapter {
var rect : SCDSvgRect?
// page adapter initialization
override func load(_ path: String) {
super.load(path)
}
override func show(_ view: SCDLatticeView?) {
super.show(view)
// all animmation setup has to happen in show method
self.rect = self.page!.drawing!.find(byId: "rect1") as? SCDSvgRect
// create an animation
let rotateAnimation = SCDSvgRotateAnimation()
rotateAnimation.angle = 360
rotateAnimation.duration = 2
// add animation to element
self.rect!.animations.append(rotateAnimation)
}
}
Pretty straight forward
Center of rotation
You can set the center of the rotate animation using the anchor attributes. By default, the center of rotation is defined in relative values starting from the top left to the bottom right. You can set it to absolute values by setting rotateAnimation.isAbsolute = true. For now, let keep it the default (false) and the anchor attribute to a value from 0 .. 1.
The following code sets the anchor point to the middle of the object.
// Use of relative anchor coordinates
rotateAnimation.anchorX = 0.5
rotateAnimation.anchorY = 0.5
rotateAnimation.rotateAnimation = false // false is the default
Moving objects
The translate animation moves an object by dx and dy pixels. The movement is relative to the current location, i.e. it moves by +/- pixels in dx direction, and in +/- pixels in vertical direction. So if the object is located at (100,100), and you use (dx,dy) = (10,20), your new location is (110,120).
Here the swift code to create an animation that moves vector object by dx and dy:
func getMoveByAnimation(dx:Float, dy:Float) -> SCDSvgTranslateAnimation {
// create translate animation
let moveByAnimation = SCDSvgTranslateAnimation()
moveByAnimation.dx = dx
moveByAnimation.dy = dx
moveByAnimation.duration = 2.5 // 2.5 seconds
return moveByAnimation
}
Compute coordinate for middle lower third of device's screen
Now lets say you want to move to certain coordinate, lets say we want to move to the middle of the lower third of the device, lets further assume we have a resolution of 400 x 690. The coordinate to move to would be (200, 460 + 115) = (200, 575).
Here the code to
- get the width and height of the screen
- compute the destination coordinate
func getXYCoordinateCenterLowerThird() -> (Float,Float) {
// get width and height of screen
let screensize = SCDRuntime.getSystem().getScreenSize()
let (w,h) = (Float(screensize.width), Float(screensize.height))
return (w/2.0, h*(2/3) + (h*(1/3) / 2) )
}
Move to computed coordinate
The code for moving to the coordinate is pretty easy:
let (tx,ty) = getXYCoordinateCenterLowerThird()
let (dx,dy) = ( tx - self.rect!.x.value, ty - self.rect!.y.value)
let moveAnimation = getMoveByAnimation(dx:dx,dy:dy)
self.rect!.animations.append(moveAnimation)
Moving along a path
Most certainly, we can also move along a path using the SCDSvgMotionAnimation. The object will be moved along a path, where the path comprises of the a number of straight or curved line. Usually, you wouldnt create the path yourself, but create the path through a tool such as Adobe AfterEffects. For demonstration purposes, let us move the object along the sides of a rectangle.
-
Step 1 - 2 are straightforward. We compute the corners of the rectangle. We use the current device height and width for this
-
We compute the absolute location of the vector element.
-
Each motion animation needs a path SCDSvgPath along which the vector object is moved. Each path starts with a SCDSvgPathMove element, followed by a number of SCDSvgPathLine elements:
- We add SCDSvgPathMove using the vector elements current location
- We add a couple of lines
func getMoveAlongRectangle() -> SCDSvgMotionAnimation {
// move vector object along lines of a rectangle
let screenSize = SCDRuntime.getSystem().getScreenSize()
// compute points of rectangle to move along by
let (x0,y0) = (Float(screenSize.width) * 0.2, Float(screenSize.height) * 0.2)
let (x1,y1) = (Float(screenSize.width) - x0 - self.rect!.width.value, Float(screenSize.height) - y0 - self.rect!.height.value)
// get absolute location of vector element
let bbLocation = self.rect!.getBoundingBox().location
let (currentX, currentY) = (Float(bbLocation.x),Float(bbLocation.y))
// create path and set start position
let path = SCDSvgPath()
path.elements.append(SCDSvgPathMove(x: currentX, y: currentY))
// set lines to move along by
[(x0,y1), (x0,y0), (x1,y0), (x1,y1), (currentX, currentY)].forEach{ path.elements.append(SCDSvgPathLine(x:$0.0, y:$0.1))}
// set to absolute movement (will be default to true in next build)
path.elements.forEach{ $0.isAbsolute = true }
// create motion path animation. the x,y points need to be translated to
let motionAnim = SCDSvgMotionAnimation()
motionAnim.path = path
return motionAnim
}
Using this animation, the rectangle moves around the screen easily.
PropertyAnimation - animate anything
The property animation is the most powerful, as any attribute of the vector element can be animated. Lets animate the stroke-width attribute. When you specify the key, i.e. the property to modify, use camelCase syntax (start with lowerCase, use upperCase for next part)
func getStrokeWidthAnimation() -> SCDSvgPropertyAnimation {
// the vector element attribute name needs to be specified in lowerCamel
// so stroke-width in the SVG source becomes strokeWidth
// we will look into changing this in the future, this inconsistency doesnt make sense
let strokeWidthAnimation = SCDSvgPropertyAnimation("strokeWidth",from:5,to:10)
strokeWidthAnimation.duration = 2
return strokeWidthAnimation
}
Example for properties and ids
Vector attribute | Key |
---|---|
fill-opacity | fillOpacity |
width | width |
stroke | stroke |
stroke-width | strokeWidth |
SCDSvgPropertyAnimation constructor
Constructor x | Description |
---|---|
SCDSvgPropertyAnimation(_ key:String,from:Any,to:Any>) | Move the property from to . We interpolate linearly with the frame rate over the duration of the animation. Use this as default for most smooth transition |
SCDSvgPropertyAnimation(_ key:String,values:[Float]) | Move property identified by key through the list of values specified in values within the specified duration. |
Animate color transition
As you can see, the constructor and the values for SCDSvgRGBColor are a little off standard. We address this shortly, other than that, code is pretty straight forward.
func getFillColorAnimation() -> SCDSvgPropertyAnimation {
let fromColor = SCDSvgRGBColor(r:1,g:0,b:0)
let toColor = SCDSvgRGBColor(r:1,g:1,b:0)
let fillColorAnimation = SCDSvgPropertyAnimation("fill",from:fromColor,to:toColor)
fillColorAnimation.duration = 3
fillColorAnimation.delay = 3
return fillColorAnimation
}
Grouping animations
The combination and parallel execution of multiple animations effects is done using the SCDSvgGroupAnimation. You add multiple animations in the animation attribute
func getGroupAnimationStrokeAndFill() -> SCDSvgGroupAnimation {
let ga = SCDSvgGroupAnimation()
ga.animations = [getRotateAnimation(),getFillColorAnimation()]
ga.delay = 2
return ga
}
Value & Time function
The animation api uses two concepts for providing detailed control about the animation called value function and time function:
-
the value function v(x) --> Any receives an input between 0..1 as an input, and returns any value that is compatible with the property specified. As you as the developer write the value function, the behavior itself is totally up to you.
-
the time function f(t) -> [0..1] receives a value 0..1 as an input from the animation system, and returns a value [0..1] as an output. Again you develop the function, and therefore control its behaviour, but you can also use predefined timing function for special effects. These functions are called easing functions and allow your for nice looking effects. Please see here for learning more about the easing functions Easing functions
Vitaly likes to explain it likes this. Image you have an old fashioned movie on a film roll. The value function specifies how each picture on the roll looks like. The time function determines how fast you you reach the picture. The time function can speed up, slow down, fast forward or even go backward.
Value function example
This value function will return color RED for the first 25% of animation duration, the color ORANGE for the following 50% of the animation duration and for the remaining 25%, the color BLUE.
func getPropertyWithValueFunctionAnimation() -> SCDSvgPropertyAnimation {
let v = {(x:Float) -> SCDSvgRGBColor in
switch x {
case 0..<0.25 : return SCDSvgRGBColor(r:1,g:0,b:0) // RED
case 0.25..<0.75 : return SCDSvgRGBColor(r:1,g:0.5,b:0) // ORANGE
case 0.75...1 : return SCDSvgRGBColor(r:0,g:0,b:0.5) // BLUE
default: return SCDSvgRGBColor(r:1,g:1,b:1)
}
}
let ani = SCDSvgPropertyAnimation("fill", valueFunction : v)
ani.duration = 3
return ani
}
OnComplete handler
As animation functions are invoked and execute asynchronously, the SCDSvgOnCompleteHandler allows you to call a function whenever the animation is finished
func getOnCompleteAnimation() -> SCDSvgGroupAnimation {
let animation = getGroupAnimationStrokeAndFill()
animation.onComplete = SCDSvgOnCompleteHandler{ _ in print("Done with Animation") }
return animation
}
Examples
Updated about 7 years ago