Toll Free: +1-855-656-NUVI

Is it possible to animate height and width in NativeScript? Yes!

Is it possible to animate height and width in NativeScript? Yes!
  • Share:

TL;DR Yes! but…

For a long time, NativeScript developers have been begging for the ability to animate height and width using standard animation techniques that are well documented. I’ll show you examples of how to achieve this right now, and tell you why these aren’t yet in the NativeScript core.

There are two methods for animating objects in NativeScript, and these are pretty well documented. You can animate objects imperatively, using the Animation class’s static functions or the animate function that every UI widget has on it. You can also animate declaratively, by assigning CSS classes that trigger keyframe animations.



As of NativeScript version 3.1.x, neither of these options allow you to animate the height and width properties of UI widgets. There are serious performance considerations that need to be taken into account when animating height and width, due to the way layout recalculating takes place. This could easily be abused, causing poor performance and negating one of the key reasons to use NativeScript over Ionic, for example: smooth, buttery, native animations.



But, since you’re still reading, you hereby affirm that you are willing to take a risk and use this technique sparingly, and only on one-off animations.

Sign here______________________________________________ Date________________



I go into deep details about all these animation techniques in my NativeScript Animation Techniques course on Pluralsight. You can read more about it here.





Recently I’ve started using RxJS Observables to drive JavaScript height and width animations in NativeScript, which leads to better reusability of code, more elegant code, less hit-you-over-the-head-with-my-javascript code.



Here is how we can accomplish this kind of simple height animation. You can also find this sample code in this GitHub Repo.

tns-animating-dimantions-demo

For starters, we need a thingie we want to animate. So in a new NativeScript with Angular project, I’ve added this template to a component.

    
        
    


The label is what we’re going to animate, but it could really be any UI widget, since they all inherit their style property from the same base class. We have a tap event handled by the onTap(lbl) function in the code, where we will trigger the animation. Notice that we’re passing the label reference into the tap handler.
Now the tap handler can use the label to animate it.



onTap(lbl: Label) {
    duration(2000)
        .map(elasticOut)
        .map(amount(250))
        .subscribe(curFrame => {
            lbl.style.height = curFrame;
        },
        er => console.error(er),
        () => console.log(‘booya!’)
        );
}



Whoa! What’s going on here? We are creating a subscription to an observable that provides values to us over time (2000ms). This code will work, but for those with a keen RxJS eye will know there is a small problem with this code (see NOTE below).

So we need a duration function that takes in the number of milliseconds to run the animation.

const timeElapsed = Observable.defer(() => {
    const start = Scheduler.animationFrame.now();
    return Observable.interval(1)
        .map(() => Math.floor((Date.now() – start)));
});
const duration = (totalMs) =>
    timeElapsed
        .map(elapsedMs => elapsedMs / totalMs)
        .takeWhile(t => t <= 1);



The duration function relies on the timeElapsed function to generate the observable based on the start time of the animation and the “now” time. There is quite a bit more going on here, but that’s for another post.

We also need to let the observable know how much to move the widget; how wide to expand it. This is done using the amount factory function, which is a simple multiplication of current time fraction (t) and total distance (d).

const amount = (d) => (t) => t * d;



And finally, while it’s not necessary to have an animation curve, the animation wouldn’t look as natural without it. We need a single function that will take in a current linear time fraction and transform it based on a mathematical formula. I also show example of these in my course. I this case I use an elasticOut formula to simulate a rubberband-like effect, but you can use any other formula as long as it converts a single time fraction. A simple one to start with is just return t to the power of 2.

const elasticOut = (t) =>
    Math.sin(-13.0 * (t + 1.0) *
        Math.PI / 2) *
    Math.pow(2.0, -10.0 * t) +
    1.0;


Make sure you can actually see the label by giving it some styling.

.wrapper {
    text-align: center;
}
.thelabel {
    background-color: red;
    color: white;
}




This is great and all, but we are hardly using the power of RxJS with this example. One of the nice things about Observables is that they can be observed by multiple listeners. So we can animate multiple objects using the same observable or even mappings of one observable to another.

Instead of the beating-over-the-head method of setting the “height” property in code, let’s bind our label’s height to an observable. And in fact, let’s just have TWO labels bind to the same observable.


    
    



Notice that now we don’t need to pass in the lbl reference into our “onTap” handler since we are binding directly to the “blah$” observable and using the “async” pipe to subscribe each label to it. (I know, blah$ is a terrible name, by the way). Here’s the CSS to go along with this.

.wrapper {
    text-align: center;
}
.thelabel1 {
    background-color: red;
    color: white;
}
.thelabel2 {
    background-color: green;
    color: white;
}


Finally, let’s create the observable and store it in the “blah$” variable.

export class AppComponent {
    blah$: Observable = Observable.of(25);
    onTap() {
        this.blah$ = duration(2000)
            .map(elasticOut)
            .map(amount(150));
}}



Here we’ve created an initial observable that first receives that value 25 – the initial height of the labels. Then onTap a new observable (again see my NOTE below) will be assigned to blah$ with the timing and transforming properties for blah$. Since the heights of both labels are bound to blah$, they will just both happily tag along for the ride.

Nice!



NOTE:  This code is just meant to demonstrate what’s possible and shouldn’t be used as is in production. The problem is that we are creating multiple Observables, we are doing this on every tap event. We should really be creating only ONE Observable when the component is initialized, and then unsubscribing when the component is destroyed. That’s for a later article. I’ll also be going into more details about this in my NativeScript with Angular Pro course that is available for presale right now.

Alexander Ziskind
Alexander Ziskind

From the latest tech in web development to the latest electronic music hardware and software, Alex loves to get his hands on new stuff and hack on it. Follow this feed on Nuvious related news; so web and cloud stuff here.

4 Comments

Leocrawf Stewart
August 10, 2017 Leocrawf Stewart

Please, show some love to the vanilla NativeScript developers?

Alexander Ziskind
August 10, 2017 Alexander Ziskind

I hear you Leocrawf (cool name by the way),

Love to core folks shown here: https://github.com/alexziskind1/tnsheightanimationdemo/tree/master/core
You'll find a core project (no angular) using RxJS for animation.

Alex

Alex
August 16, 2017 Alex

Can this be used anyhow with ListView items (i.e. expanding on tap)?

Alexander Ziskind
August 24, 2017 Alexander Ziskind

Hi Alex,

Please DON'T. Do not change the size of one cell in a list view. ListViews are special because they are highly optimized by the underlying OS to be as performant as possible. Each list item is rendered from a template - if you mess with that template and start scrolling the list, you will see some really messy problems such as other items suddenly inheriting the new size - something you probably don't want. I'm not just saying this - I've foolishly tried it myself.
If you want to achieve the effect of expanding one cell as in a master/detail type of scenario, you'll have to be creative about doing it in other ways, such as displaying an overlay view that is absolutely positioned, for example.
There are also workaround that you may be able to find of people doing this on the native platform. You could use these techniques to write platform specific native code.

Leave your comment