Skip to content
Niels van Hoorn edited this page Oct 6, 2016 · 9 revisions

New Animation API

We are about to release a new animation api.

Reference

Parts

  • General documentation
  • API documentation
  • Event documentation
  • Change (old/new) examples

===

API Overview

This is a current relevant overview of how the API will be from now on. For more information about what changed, see below.

layer.animate(properties: object | state: string, options={})

Returns: Animation object
  • properties or state: either an object with property values you want to animate to, or an existing state name.
  • options: options for the animation (see animationOptions).

This creates a new animation for a layer based on a state or new property values and starts it immediately. If the properties have an options key, it is used as options for the animation.

If there is currently an animation on the layer, it gets stopped if it is animating the same properties as the new animation. Simply put, no two animations can be ran on width at the same time, but you can have two separate animations, one running on width and the other on height.

Preferred Examples

Simple examples for animating of properties.

# Properties animation
layer.animate
	x: 200

# Properties spring animation
layer.animate
	x: 200
	options:
		curve: "spring"

Simple examples for animating with states.

layer.states.test =
	x: 200

# State animation
layer.animate "test"

# State animation with options
layer.animate "test",
	curve: "spring"

layer.animateStop()

Stop all animations for this layer.

layer.animationOptions

Animation objects manage animations that target a layer and properties. An animation will tween between a start and end value, with a curve. The start value is determined when the animation starts, and the end value is defined by properties. If the start values equal to end values, they won't animate.

  • curve — A string, set to ease by default. (Optional)
  • curveOptions — An object with the options of the set curve. (Optional)
  • time — A number, the duration in seconds. (Optional)
  • delay — A number, the delay of the animation. (Optional)
  • repeat — A number, the amount of times it repeats. (Optional)
  • colorModel — A string, the model to animate colors in. (Optional)
  • instant — A boolean, don't animate. (Optional)

layer.states (object)

The states object on a layer holds all the different states for a layer. They can be used to animate to, switch to, or cycled through. States are a great way to organize different visual properties of layers.

The properties of a state can be anything on a layer like x, y, etc. Properties that cannot be animated like html or visible can still be used but will not be animated. They will get set at the end of a transition.

States can have a special animationOptions key to hold animation options like curve. They will be used whenever layers get animated to that state.

There are two special states: current and previous. They refer to the current and last state that a layer is in. Additionally, the have a name key so you can check the current state name with layer.states.current.name.

Examples

# Set a single state
layer.states.stateA =
	x: 100

# Animate to the state	
layer.animate("stateA")

# Go to the state without animation
layer.stateSwitch("stateA")

Here is a common example where we set multiple states and cycle through them.

# Set multiple states at once
layer.states =
	stateA:
		x: 100
	stateB:
		x: 200

# On a click, go back and forth between states.
layer.onTap -> layer.stateCycle(["stateA", "stateB"])

layer.statesCycle(states: array<string>?, options={})

[todo]

new Animation(layer: Layer, properties: {}?, options: {}?)

The animation class explicitly creates an animation for a layer. This is the exact same as using layer.animate(), but it does not start the animation immediately, you explicitly have to call start.

layerA = new Layer()

animation = new Animation(layer, {x: 500}, {curve: "spring"})
animation.start()

Animation events

An animation has three events:

  • Events.AnimationStart → always gets called (if the animation starts).
  • Events.AnimationStop → always gets called, ether when animation fully finishes or gets stopped half way.
  • Events.AnimationEnd → only gets called when the animation completely finishes.

This is nice because you can be sure stop always gets called. Also stop will always be called before end.

State switching works the same way: StateSwitchStart, StateSwitchStop, StateSwitchEnd.

Changes overview

Animation

Change a property.

layer = new Layer


## After

layer.animate
  x: 100

## Before

layer.animate
  properties:
    x: 100

Change a property with options.

## Before

layer.animate
  properties:
    x: 100
  time: 0.5
  
## After

layer.animate
  x: 100
  options:
    time: 0.5

Change the animation curve.

## Before

layer.animate
  properties:
    x: 100
  curve: "spring(250, 50, 0)"
  
## After

layer.animate
  x: 100
  options:
    curve: "spring(250, 50, 0)"

States

Add a single state.

## Before

layer.states.add
  stateA:
    x: 100

## After

layer.states.stateA = 
  x: 100

Define multiple states at once.

## Before

layer.states.add
  stateB:
    x: 200
  stateC:
    x: 400
    
## After

layer.states =
  stateB:
    x: 200
  stateC:
    x: 400

Notice the subtle difference between calling a function and setting a property. This means that where previously it was possible to add multiple states multiple times, in the new API we will override the existing states when calling layer.states = ... again. However, this is really unlikely and one could still achieve this by doing:

layer.states =
  stateA:
    x: 100
  stateB:
    x: 200

layer.states = _.extend layer.states,
  stateC:
    x: 300
  stateD:
    x: 400

Animate to state.

## Before

layer.states.switch "stateA"

## After

layer.animate "stateA"

Animate to state with options.

## Before

layer.states.add
  stateE:
    x: 200

layer.states.switch "stateE",
  curve: "ease-in"
    
## After

layer.states.stateE =
    x: 200

layer.animate "stateE",
	curve: "ease-in"

States can now also include animation options in the state itself. This is handy for declaring how to animate to a state as well. This has the exact same result as above.

layer.states.stateE =
    x: 200
    animationOptions:
    	curve: "ease-in"
    	
layer.animate "stateE"

Instantly switch to a state

Switching instantly will become an option of the animation

## Before

layer.states.switchInstant "stateB"

## After

layer.stateSwitch "stateB"

## Which will be a shorthand for:

layer.animate "stateB",
    instant: true

This means it can also be defined directly in a state itself:

layer.states = 
   stateA:
     x: 100
     animationOptions:
         instant: true

Cycling through state

Next has been renamed to cycling, because next was pretty ambigious depending on the context.

## Before

layer.states.next()
layer.states.next("stateB","stateC")

## After

layer.stateCycle()
layer.stateCycle(["stateB", "stateC"])  # Preferred
layer.stateCycle("stateB", "stateC")    # Also valid

# With options
layer.stateCycle ["stateB", "stateC"],
    time: 0.5

Notice how we use an array of states names here, to support animation options as second argument

Special states

There are three special states that will be set automatically and can't be overridden:

  • layer.states.default - The state the layer had upon creation.
  • layer.states.previous - The previous state the layer was in
  • layer.states.current - The current state the layer is in

These states contain the actual values and not (as is the case with layer.states.current now) the state string. The name of the previous an current states will still be available through layer.states.previous.name and layer.states.current.name. The name key only gets added in the context of these special states, so the special state objects are not the same as the actual state object. They are completely equal, with the name key added.

Notice the absence of layer.states.next, this functionality will be provided by layer.stateCycle() as described above.

Listing all the states

We will add a new property layer.stateNames that lists all the names of states currently defined on a layer. This list will contain layer.states.initial, but won't contain default and current.

layer.states = 
    left:
        x: Align.left
    right:
        x: Align.right

layer.stateCycle()

print layer.states.current.name # "left"
print layer.stateNames # ["default", "left", "right"]

The states on a layer is pretty much a normal object. So you can use different ways to loop through the states.

layer.states = 
    left:
        x: Align.left
    right:
        x: Align.right

# All thsese print ["default", "left", "right"]
print layer.stateNames # ["default", "left", "right"]
print Object.keys(layer.states)
print _.keys(layer.states)
print (k for k of layer.states)

# But beware of this one, it is empty: []
print (k for k in layer.states)

===

Older stuff:

Some refactoring for the new animation api

  • Move state switching responsibility back to the LayerStateMachine.
  • Clean up animation events; AnimationStart, AnimationStop and AnimationEnd.
  • Deprecate StateWillSwitch and StateDidSwitch in favor of StateSwitchStart, StateSwitchStop and StateSwitchEnd.
  • Refactored LayerStates to use prototype properties, so we don't make them in constructor.

Changes

  • You cannot override the deprecated names anymore (like .add) to use as a state name, because they would not show up in state names.
  • Remove the initial state and keep default as is.

Worries

  • Ideally Object.keys(layer.states) should be the same as for k in layer.states but that does not seem to be possible.
  • Maybe we need to change layer.states.current back from the name to the actual properties and add a name if you return them so you can do layer.states.current.name. This would lose comparison by reference, though.
  • There is a (double) circular dependency between LayerLayerStatesLayerStateMachine and back. Not the end of the world, but I'm not sure if the GC is happy about this.

See: https://github.com/motif/company/issues/2633, https://github.com/motif/company/issues/2635