From Anthony: Animating Xojo, Part 4

In Part 1 we covered the history of animating UI effects in Xojo. Part 2 we dug in to linear interpolation. Part 3 saw the addition of support for concurrent animations and color changes. In Part 4 we’ll cover easing, which can make the most drab User Interface interactions beautiful.

Easing

What is Easing?

To quote Paul Lewis:

Nothing in nature moves linearly from one point to another. In reality, things tend to accelerate or decelerate as they move. Our brains are wired to expect this kind of motion, so when animating, you should use this to your advantage. Natural motion makes your users feel more comfortable with your apps, which in turn leads to a better overall experience.

https://developers.google.com/web/fundamentals/design-and-ux/animations/the-basics-of-easing

Based on that, we can define easing as a more natural-appearing change in value between a start and end point which is designed to make a user feel more comfortable with changes in the user interface.

Types of Easing Functions

Easing functions can be broken down in to three basic types:

  • In — An animation that starts slowly and increases in speed over time
  • Out — An animation that starts quickly and decreases in speed over time
  • InOut — A combination of InOut that begins and ends quickly with a slow transition in the middle.

What Does Easing Look Like?

There a number of currently standard UI easing functions(as seen in CSS, various JavaScript frameworks, and even OS-provided implementations), but one of the more interesting sets is called Elastic. Elastic easing functions simulate the motion you might observe if you stretched out a piece of elastic then released it. You get a dramatic change as the elastic fibers quickly contract, then a slower series of changes as the momentum dissipates. To visualize that in a diagram:

This can be used to provide some really enthralling UI transitions that’ll make both you and your users much happier as it just feels more natural than making Element A of your UI disappear and Element B appear, or using a linear animation to swap the two elements.

What Does an Easing Function Look Like

Easing functions can be incredibly simple, as you saw in our lerp method in Part 2, or as complex as you can imagine. Our example of Elastic easing, while not the most complex, is pretty interesting mathematically (if you’re terrible at math like I am).

Private Function easeInElastic(startValue as Double, endValue as Double, alphaValue as Double) as Double
  Return ((endValue - startValue) * (0.04 * alphaValue / (alphaValue - 1) * Sin( 25 * (alphaValue - 1) ))) + startValue
End Function

Basically what this method is doing, and my grasp of the math is admittedly pretty thin, is it’s taking the start and end value and creating a diminishing sine wave to simulate the change over time. I normally would say that this should be broken down in to multiple lines to make it easier to read, possibly by using no fewer than three variables, but we really want it to be quick.

If we did break it down, it would look more like this:

Private Function easeInElastic(startValue as Double, endValue as Double, alphaValue as Double) as Double
  dim changeValue as Double = endValue - startValue
  dim differenceValue as Double = (0.04 * alphaValue / (alphaValue - 1) * Sin( 25 * (alphaValue - 1) ))
  Return (changeValue * differenceValue) + startValue
End Function

Which may be easier to read for some of us, but those variable declarations have a cost.

Many of the easing functions you’ll see in this part of the series are adapted for Xojo (with some heavy modification in most places) from Robert Penner’s Easing Functions (BSD License). There’s a note on the Animated Canvas project item if you wish to explore the sources.

What Types of Easing Functions Should I Use?

UI animations are, typically, something you want to happen fairly quickly. Given that, you likely won’t use InOut easing functions very often as they can feel slower to your end user.

In functions can be off putting to users as the natural expectation of seeing the most movement at the end of an animation is antithetical to In functions and may feel slow or confusing for some users.

Based on those theories, you’ll typically employ Out functions in your UI animations, as they’re both quick and easier for our brains to understand in a UI setting. That said, there will be times when other types of easing functions are more suited to an individual task and you must use your best judgement and listen to your QA/users if they complain about it. Animation type and length are a delicate balance.

Easing Lengths

As I alluded to earlier, the timing of your chosen easing function is incredibly important. The following table should be a good starting point for appropriate animation lengths for different types of operations.

Color (In)100-250ms
Color (Out)100-250ms
Color (InOut)500-1000ms
Dimensions/Position (In)800-1200ms
Dimensions/Position (Out)800-1200ms
Dimensions/Position(InOut)1500-2500ms

These are the values I typically start with, and alter them based on the responsiveness of the UI and the desired effect. Your mileage may vary.

Code Changes

While I won’t cover the addition of each of the example easing functions that I’ve provided in the example project, I will cover the functional changes to our existing codebase and the addition of the Elastic easing functions.

AnimationEasing Property

Our first step is to create a new property on our AnimatedCanvas class called AnimationEasing. In our demo window we’ll set this value based on selection in a PopupMenu to make it easy to switch between different easing functions and view their output. It’s going to public and computed so we can show it in the Inspector.

Private Property mAnimationEasing as Easings

Public Property AnimationEasing as Easings
Get
  Return mAnimationEasing
End Get

Set
  mAnimationEasing = value
End Set

End Property

Easings Enumeration

Next we to create an enum for our various easing functions. It’s pretty straight-forward, and we’ll use it to tell our animation controller code what to do in the animation operations we define in our Dictionary objects.

Public Enum Easings
Linear
InQuad
OutQuad
InOutQuad
InCubic
OutCubic
InOutCubic
InElastic
OutElastic
InOutElastic
End Enum

Dictionary Definition

We are, again, modifying our Dictionary object definition. This time we’re passing along the desired easing effect. Our new definition looks like this:

new Dictionary("op" : <OperationToComplete>, "l" : <LengthOfAnimation>, "m" : <EasingFunction>, "t" : <StartTime>, "s" : <StartValue>, "e" : <EndValue>)

You can see that all we’ve really done from Part 3 is add a Key/Value pair for the easing. I assigned this to the “m” key to represent “method of change”, but you could use anything you like here.

MouseEnter and MouseExit

We actually don’t need any changes here for our color animation, as this will continue to use the lerpColor function we previously defined. Color change animations should typically be quick and applying anything but a linear easing to them can both look and feel bad if done improperly, so I won’t cover it here. When in doubt, for color animation, stick with linear and keep it around 100-250ms.

MouseUp

MouseUp, likewise isn’t changing much. We’re basically just adding the easing Key/Value to our existing Dictionary objects like so:

animQueue_Add(new Dictionary("op" : "width", "l" : manimTime, "m" : mAnimationEasing, "t" : Microseconds, "s" : Width, "e" : If(isExpandedWidth, 10, expandedWidth)))

doEasing Function

We need a function to take our Easings enumeration value and perform the specified easing function. In this example I’m using a Select Case block, but I’ve also implemented a nice jump table using delegates in other projects.

Private Function doEasing(easing as Easings, startValue as Double, endValue as Double, alphaValue as Double) as Double
  select case easing
  case easings.Linear
    Return lerp( startValue, endValue, alphaValue )
  case easings.InQuad
    Return easeInQuad( startValue, endValue, alphaValue )
  case easings.OutQuad
    Return easeOutQuad( startValue, endValue, alphaValue )
  case easings.InOutQuad
    Return easeInOutQuad( startValue, endValue, alphaValue )
  case easings.InCubic
    Return easeInCubic( startValue, endValue, alphaValue )
  case easings.OutCubic
    Return easeInOutCubic( startValue, endValue, alphaValue )
  case easings.InOutCubic
    Return easeInOutCubic( startValue, endValue, alphaValue )
  case easings.InElastic
    Return easeInElastic( startValue, endValue, alphaValue )
  case easings.OutElastic
    Return easeOutElastic( startValue, endValue, alphaValue )
  case easings.InOutElastic
    Return easeInOutElastic( startValue, endValue, alphaValue )
  end select
End Function

As you can see, it takes the easing parameter representing the desired function, and the values we need to animate, passes all of that along to the requested easing function, and returns the value. A call to this method will replace our previous call to lerp in our Timer’s Action event handler.

Our Easing Functions

As I said earlier, I won’t cover all of the functions in the article, but I will include the Elastic functions here for your perusal. To see all of the easing functions included, be sure to download the example project at the end of the article.

Private Function easeInElastic(startValue as Double, endValue as Double, alphaValue as Double) as Double
  Return ((endValue - startValue) * (0.04 * alphaValue / (alphaValue - 1) * Sin( 25 * (alphaValue - 1) ))) + startValue
End Function

Private Function easeOutElastic(startValue as Double, endValue as Double, alphaValue as Double) as Double
  Return ((endValue - startValue) * ((0.04 - 0.04 / alphaValue) * Sin( 25 * alphaValue ) + 1)) + startValue
End Function

Private Function easeInOutElastic(startValue as Double, endValue as Double, alphaValue as Double) as Double
  dim opChange as Double = Abs(endValue - startValue)
  
  if alphaValue <= 0.5 then
    return easeInElastic(startValue, opChange /  2, alphaValue * 2)
  else
    return easeOutElastic(opChange / 2, endValue, Abs(1 - (alphaValue * 2)))
  end if
End Function

We’ve already discussed purpose and expectation for In/Out, but take a look at easeInOutElastic. We accomplish this effect by splitting the run-time of the animation in to two distinct parts and executing the appropriate easing function based on where we are in the animation. In happens at <= 50% of our total animation time, and Out happens at > 50% of our animation time. This gives us a nice, seamless transition in our InOut animation as the value at 50% should be close — if not identical — in both easing functions.

animTimer_Action

Our changes in animTimer_Action are also pretty minimal. Since we’re not applying one of the more advanced easing functions to our previously discussed color animation, I’ll only cover the changes we’re making to the Width and Height operations.

First, we need to add a new variable declaration to the top of our method. This is where we’ll store the easing type.

Dim animOpEasing As Easings

Then, after our Else at line 87, where we get our values from the current animOp Dictionary, we will assign the current operation’s easing (or “m” key) value to our new variable.

animOpEasing = animOp.Value("m")

Now that we know which easing function we want to apply to the current operation, and we’ve already defined our doEasing function, all we need to do is swap out our line (line 100) that previously used the lerp function to assign the appropriate value to animStepDouble, to use our new doEasing function.

animStepDouble = doEasing(animOpEasing, animStartDouble, animEndDouble, timePercent)

Series Finale

I do hope that, throughout this series, you’ve learned a little something about how and when to implement animation in user interfaces, some decent practices for doing so in an encapsulated way, and maybe picked up a few other pointers.

I had a lot of fun writing this and interacting with readers who reached out with questions and suggestions, and I hope to do more guest posts on the Xojo blog in the future. I’d like to thank Team Xojo not only for their great product and support, but also for allowing me to stand at their podium for a short time and share with you all.

If you would like to see more easing functions, or how I’ve implemented a full animation controller and the delegate jump table, GraffitiAnimator will soon be coming to GraffitiSuite Desktop Edition, and can be acquired directly, via the Xojo Extras store, or for a limited time as part of the Omega Bundle.

Finally, you can download the example project for Part 4 here.

Thank you all for following along with me!