Learn React Native Gestures and Animations - Tutorial

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[MUSIC PLAYING] Hello, Free Code Campers. Welcome to this workshop on declarative gestures and animations in React Native. I'm William, maker of the "Can it be done in React Native" YouTube series In this workshop, I would like us to do five things. First, to discuss why the topic of gestures and animations is so peculiar in React Native, and what kinds of strategies and APIs we are going to use, in order to implement user interactions that run smooth as butter. From there, we can look at transitions. Transitions as the easiest way to animate components in React Native. And we're going to build a component with two states. One state-- the cards are overlaid on top of each other. Second state-- they are nicely fanned out. And we are going to declare a transition to animate nicely from one state to the other. And then, we're going to build the simple timing animation that we can loop, pause, resume, pause, resume, using Bare Metal animation APIs. Getting to know these low level APIs would be the key to success for us to harness gestures and animations in React Native. And then, it will be time to add gestures into the mix. And we are going to build this nice wallet user experience. So we can swipe for cards nicely. And they animate very nicely depending on their position. Finally, I would like to show you what these gestures and animations integrate very nicely with SVG, and we're going to build this really cool circular slider using gestures and SVG animations. This workshop will only be scratching the surface of the exciting wall of gestures and animation in React Native. And to conclude, I would give you all the resources that I know of, in case you are interested to go further with this topic. In the video description, you will find the GitHub repository with all the code examples as well as the boilerplate files, in case you want to code along in the video. And also available in the video description are all the timestamps for each of the different chapters of the workshop. So Free Code Campers, are you ready to discover the powerful world of declarative gestures and animations in React Native? Let's get started. [UPBEAT MUSIC] When building gestures and animations, the key to success is to avoid frame drops. We want the user interaction to run at 60 frames per second, which means that we only have 16 milliseconds to compute everything. And this is a very simple diagram of the React Native architecture. We have the JavaScript Thread, which runs the react code, and the UI Thread which interacts with all the native components. And they talk to each other using what we call the async bridge. They asynchronously send each other JSON messages. So if your gesture or animation relies on communication between these two threads, it is very hard to guarantee that the animation frame can be computed within 16 milliseconds, and you are likely to draw up frame. There are a couple of reasons for that. First, because of these asynchronous messages that needs to be passed and serialized, you might drop a frame on the low end device, and the JavaScript Thread might be busy doing something else. For instance, processing the response of an HTTP request. So maybe you have a gesture. So the UI Thread says to the JavaScript Thread, should I grant the gesture? And the JavaScript Thread might be busy processing the response of an HTTP request. So is not able to reply within these 16 milliseconds. And you're going to drop a frame, and the user experience is not going to feel very smooth. So the way we are going to circumvent this problem is by declaring all of our gestures and animations before hand, which means that there is no communication between the JavaScript Thread and the UI Thread. So it doesn't matter if the JavaScript Thread is busy doing something else, because the UI Thread is going to be able to do all the tasks he needs to do for each frame. And plus, because there is no communication between the two Threads, we are sure that we are able to compute the animation frame within these 16 milliseconds. And I would like to show you an example of what this means concretely. So here I am dragging around this component. And we implemented this example using the default gesture and animation API provided by React Native. I call it a vanilla animation APIs. And as you can see here, the gesture depends-- relies on communication between the JavaScript Thread and the UI Thread. So to grant as a gesture, we need to execute some JavaScript Thread. When we move the view around here, everything is done on the-- because we use these animated events, everything is done on the UI Thread. So there is no communication between the JavaScript and the Native side. And when we reduce the gesture, we also rely on JavaScript code. And so because we have this mix of imperative code and declarative code, if the JavaScript Thread is busy, this user interaction is not going to run smoothly. And in fact, what I can do is to modify the code, so that we observe JavaScript Thread-- we make the JavaScript Thread busy. So I'm going to loop 5,000 time and maybe like print something like [? JS ?] Thread busy. And now, you see I cannot move the ball around because the JavaScript Thread is busy. And so we are not going to use these APIs, because these APIs rely on communication between the UI Thread and the JavaScript Thread. And what we're going to use instead is two libraries, React Native Reanimated for animations and React Native Gesture Handler for gestures. And what these libraries do is that they enable you to implement all of the gestures and animations declaratively on the UI Thread, which means that when you have your gesture and animation running, there is no communication between the two Threads that is involved. So here, if I make the JavaScript Thread busy, it doesn't matter because there are no communications between the UI Thread and the JavaScript Thread. So this is the same implementation but using declarative gestures and animations using React Native Gesture Handler and React Native Reanimated. And now, so everything is done declaratively. And now I can make the JavaScript Thread busy. So [? JS ?] Thread busy. So now the loop is running, but because I don't rely on the JavaScript Thread, you see I can still move the ball around the smoothly. And so this is the heart of the matter declaring all the gestures and animations before. And this will guarantee us to build user experiences that will run at 60 FPS even on low end devices. [UPBEAT MUSIC] In this example, I would like to show you the power of transitions in React Native. Transitions are the easiest way to animate components in React Native, and the way it works is that we can attach an animation value to a change of state in a component. So here, I have a very simple component with a very simple state, toggled is true or false. If the toggle is true, we apply here a rotation, which is index minus 1 times alpha. So alpha is [? 30 ?] degrees. Pi is 180 degrees divided by 6. And here I do index minus 1, minus 1 times alpha. So if index is 0, 0 minus 1 is minus 1 times [? 30 ?] degrees. So we'll have a minus [? 30 ?] degrees. If index is 1, so the count in the middle, we would have 1 minus 1, 0 times [? 30 ?] degrees, 0. So there won't be any rotation. If it's the [? third ?] card, we will have index [? 2 ?] [? to ?] minus 1 is 1. So we'll have [? 30 ?] degrees. So we see here. Here we have the cards constants, which contains six cards. We select 3. And we calculate the rotation. When pressing this button, we toggle the state-- so to its opposite value. And so if toggled is false, we have a rotation of 0. And what we can do here is to use a hook called useTransition. That will give us an animation value that will go from 0 to 1 when toggled changes. So if toggled is false, the animation value will be 0. If toggled is 1, the animation value will be 1. And the way the transition from 0 to 1, 1 to 0 [? goes ?] can be a timing function, can be a spring function, and can be any configuration of the animation functions that you want. So let's get this animation value. Here we're going to call it transition. And we're going to use a function called useTransition. useTransition comes from a package called react-native-redash. So we are going to use react-native-reanimated and React Native Gesture Handler for the declarative gestures and animations. And I created an open source package called react-native-redash. I see it as being the [INAUDIBLE] of Reanimated that provides us with a lot of utility functions, such as this useTransition hook. But you don't have to be intimidated by [? it. ?] These are just helper functions on top of Reanimated and Gesture Handler. You can look at the source code of some of these functions to see how it works behind the scene. But these are usually very simple helper functions. So this function takes two arguments. The first is the state we want to transition on. And here it's toggled. And then we can pass an animation configuration. So for instance, by default, useTransition is using a timing function. And maybe I want the duration to be [INAUDIBLE].. You can set some easing. You can have a spring function instead. So here we have our transition. And what we want to do is to interpolate the rotation based on the transition. So here, you see we have the View component. Here we use Animated.View. Animated.View is an animation wrapper around view so that in these style properties we can use animation values. So here we pass a string, which is a rotation value in radians. But now because we use Animated.View, this style property can also accept animation values. So here, rotate is going to become an animation value. And we are going to interpolate it using transition. So we can write it using interpolate from react-native-reanimated. So the animation value is [? a ?] transition. Input range is 0, 1. And output range is if toggled is 0, the rotation is 0. If toggled is 1, the rotation is index minus 1 times alpha. So I need to import interpolate. Let's have a look. So you see here it nicely transitions from one state to the other. It looks very cool. Again, speaking of helper functions in redash, we are going to have a lot of these animation values that go from 0 to 1. And we're going to interpolate always from 0 to 1. There is a helper function called mix in redash. So here we import mix from redash. And it's the same implementation as mix in [? OpenGL. ?] So we can pass directly the output range like this. So from 0 to index minus 1 times alpha. So just a nice shortcut. And here it is. And the last thing we can do is to change the transformation of origin. So here you see by default is the center of the card. And the way we transform the origin in react-native using the transform API is that we are going to translate to where-- so from the center of origin to where we want the new origin to be, do the transformation. And translate back. So, here we want to translate so the default origin is half of the width of the card, half of the height of the card. We want to translate to minus half of the width of the card. Do the transformation. Translate back to half of the width of the card. So we're going to write translate x is minus card width divided by 2. We do the transformation, which is rotate. And translate back. So translate x is card width divided by 2. And so here you see it nicely changes the transformation of origin. And here again, we have a nice utility function in Redash to perform such a transformation of origin. And it's called transform origin. And the first parameter is the new transformation origin. So here we have x, which is minus card divided by 2. Y is 0. We don't change it. And the transformation is rotate. And so here it is. So transitions are a great way to animate components in react-native with little to zero knowledge of gestures and animations. So just a great way to animate changes in your component states. [MUSIC PLAYING] In this example, we are going to build a simple timing function using some bare metal APIs from Reanimated, and getting to know these low level APIs will enable us to build incredible user experiences. So here, I have a chat bubble component that takes a progress property, which goes from 0 to 1. So this is the state at 0. We can look at the state at 0.5, 1. And so we're going to go from 0 to 1, 1 to 0. And there is a state play, which is true or false. So we want this looping timing function to be pausable and resumable nicely. And in order to implement this timing function, we are going to use all the concepts that react-native-reanimated has to offer, or almost all of the concepts. And one of these concepts are clocks. So clocks are animation values which update themselves across time. So by default, the value of a clock is 0. You can invoke a function called start clock on it. And every frame, the clock animation value will update itself with a timestamp. And this will enable us to build animations across time. Then there is a stop clock function to stop the clock. So the clock animation value will stop updating itself. And there is a function called clock running to check if the clock is running or not. And so the way you can create a clock is like this. So it's clock from Reanimated. We want here because we're going to have many [? re-renders ?] when the state changes. We don't want to recreate a new clock every time. We want the clock identity to match the lifecycle of the component. So we want to always have the same clock for every instance of our timing component. And so we can use a helper function for that. Again, from Redash, which is useClock. And here for progress we can use an animation value. So you would do new value [? is 0 ?] from again, React Native Reanimated. But we also want the identity to be preserved across [? re-renders. ?] So we're going to use useValue from Redash. And these are simple wrappers using [? a lazy ?] [? useRef ?] to have-- sorry. I need to import from Redash. So what we are going to do is that we're going to use a hook called useCode, which allows us to declare animation nodes to be run for every frame on the UI Thread. So the function signature-- the hook signature-- is very similar to use [? effect. ?] The first argument is a callback that returns here an array of animation node. The second parameter are the dependencies. Here we have none. We want these animation nodes to have also the same lifecycle [INAUDIBLE] timing component. And so what we're going to do is use the clock to control the animation. If the clock is not running, the animation is paused. If the clock is running, the animation is running. And so by default, here, we're going to want the animation to run. So we're going to use startClock. And what we're going to do is assign to the progress value some timing function. So some function that we are going to call runTiming and pass the clock as parameter. So let's create it here. So runTiming is a function that receives a clock as parameter. And we're going to return some progress value. Execute also a bunch of animation nodes. So you can use block in order to do that. Takes an array of animation nodes as parameter and will execute these animation nodes sequentially. So maybe I return 0. So the last node in the array's value [? that is ?] returned. And so you know-- you see here I'm not writing progress equals runTiming. Because these are not imperative instructions that are run on the JavaScript Thread, but declarative animation nodes to be executed on the UI Thread. So this is-- here it's not a code that is executed, but it's a declaration for code to be executed on the UI Thread. So we're not going to use the if else syntax from JavaScript. We are going to use the condition animation node. We're not going to use equal, but set animation node. And so on. So Reanimated provides us with all the animation nodes we need in order to declare complex animation states and interactions on the UI Thread. So lets write our runTiming function. So I need to import this one from Reanimated. So if we look at the timing function from react-native-reanimated, we see that it takes three parameters-- the clock, so to you know, see where-- how we evolved across time, the animation state. So finished position. [INAUDIBLE] position will be from 0 to 1, right? Time, and frame time. [? So ?] [INAUDIBLE] last clock values for the last evaluation and 0 to 1 if the animation is finished or not. And then the animation configuration. So the target value duration easing. So you see, if you are familiar to the vanilla animated API from React Native, it's a bit more complex to execute a simple timing function. But this-- so the barrier to entry is higher, but this is way more powerful functions. So this is why I really want us to look at the low level of how these functions work. Because this will enable us, down the road, to go much, much further. Because these functions are incredibly powerful. So let me copy paste here, the function. So timing from React Native Reanimated. And so the clock we get as parameter. Let's create the state. So I'm going to create it here. State. So I'm going to assign an animation value for each state. Position, frame time, time. And we have the config. So toValue, we're going to update it, depending-- toValue is if the position is 0, the position is going to be our starting value. If the starting value is 0, the destination value is 1. And if the position is 1, the destination value is going to be 0. Duration, I don't know. Let's put 3 seconds. And easing we can put whatever, I think. In, out. And it does not like the configuration. So I have [? how ?] is the syntax of the easing. Here I'm importing easing from vanilla react-native. But I need to import the one from Reanimated. So that should be good. This is where [INAUDIBLE] [? script ?] is so useful. So here we execute the timing function for every frame. And what we want to return is the position. So state.position. So we see it animates nicely from 0 to 1. At 1, nothing happens. So at 1, what we need to do-- so if state.finish equals 1-- so you see here, we are not [? write-- ?] again, we are not writing if else, because this is not just code that is executed imperatively. This is a declaration. So we use a condition animation node. That is a declaration to run on the UI Thread. So if the state of the animation is finished, we want to reset these animation values. Position we will leave. But we need to update the [? toValue. ?] So let me reset the state. So is finished is 0. We want to loop. FrameTime time we reset as well. Because we are restarting the animation. Position we keep. If it's 1, it's still 1. We want to continue where we were. So state.time. But what we want to change is the destination value. So config toValue is going to be the opposite of state.position. If position is 0, the [? toValue ?] is 1. If position is 1, the [? toValue ?] is 0. So here I need to import these nodes. So you see it loops nicely from 1 to 0 and now from 0 to 1. Now let's make the animation interruptible. So you see here, you might be under the impression that this is a lot of boilerplate code to write a simple looping animation. There are utility animation functions in Redash that allows you to do these in a few lines of code. Because all this boilerplate can be done for you. But I find it to be extremely important to understand how these clocks and animation evaluation work. Because this can really unlock the power of complex declarative gestures and animations as we run at 60 FPS. So this is why here we are doing all the boilerplate manually. So here we have the state play which becomes true or false. And the first thing I want to do is to have an animation value that matches the state of play. So if play is true, I want my animation value to be 1. If play is false, my animation value to be 0. So here, we're going to create an animation value called isPlaying. So default value is 0. [INAUDIBLE] default state is false. So that's good. And we're going to create a hook. Again, a useCode hook. That we set the animation value isPlaying to match the state of the play variable. And you see here, I could write it here, in useCode. But I'm having different dependencies now. So let me show you. So I do isPlaying. So if play is true it's 1. If not, 0. And here the dependencies is play. So when play changes, this instruction changes. Because it depends on the play variable. And this is why I'm putting it into a separate useCode block. Because I don't want to have a dependency on the play variable here. What I'm going to write the instructions-- I'm going to declare here are valid for the whole lifetime of the component. So I don't want to recreate these animation nodes if the play variable has changed, only isPlaying. So now, we should have the isPlaying variable that matches the state of the component. And so we're going to [INAUDIBLE] actually. So we're going to write two conditions. So the first condition is if the state of the animation is playing and the clock is not running. So not clockRunning. And here you see you have to count the parentheses. We want to start the clock. We want to start the animation. And the other way around. If the animation is not playing, and the clock is running, we want to stop the clock. So I need to import stopClock and [? the and. ?] Let's have a look. So [INAUDIBLE] play. And I can pause. Resume. And you see, it doesn't resume at the proper state. But I can pause. And I can start the animation. But when I restart the animation, the state of the animation is screwed up, pardon my French. So here if the clock is not running. So if we pause the animation, we want to reset the time variable of the state, so that when it resumes, it resumes the animation properly. And if not, we run the timing function. So let's pause. And now it resumes exactly where it was [? paused. ?] Super. And it loops nicely. So a really cool timing function [? that-- ?] so, very low level APIs, clocks and all these complex animation states. But these are really-- these low level primitives are really worth it in order to build complex user interactions. And we've seen how we don't write imperative JavaScript code, but [INAUDIBLE] animation nodes, which are declarations on the UI Thread. And so all the primitives we have in JavaScript, if, and, not-- we can-- the semicolon to have sequential instructions. So we have the block here. So we have an equivalent for all the JavaScript constructs as animation nodes. And the only construct that is-- so you can have variable assignments, sequentials, executions, conditional nodes. The only primitive that is not available are loops. But here-- but that's OK. Because here it's [? code ?] to be evaluated for every frame. So I hope you enjoyed this example. If this makes sense to you, then you really have unlocked the power of declarative gestures and animations. If you are able to switch from the imperative mindset to the declarative mindset, and understand how you can use clocks and animation states in order to build different user interactions, then the world of animations and gestures in React Native is your oyster. I think you can really go very far with these primitives. [MUSIC PLAYING] So we have looked at transitions, animations. Now let us play around with gestures. We have a bunch of cards here. And let's assign some gesture handler to these cards. And play around, see what we can do. And so here I am looping over all my cards. And maybe what I can do here is wrap a PanGestureHandler from React Native Gesture Handler. So we can maybe move these cards around. And we need to pass two properties for the gesture handler to work. And we're going to use again [INAUDIBLE].. So it's quite some boilerplate. There is a lot of animation values to be created [? to ?] properties to be assigned in PanGestureHandler. So we are going to use utility function, again from Redash in order to have all this boilerplate done for us. But you know, again, don't be intimidated. You can look at the Redash source code or at the react-native gesture handler source code to see what kind of boilerplate is required for these gesture handlers to work. But we're going to get the gesture handler from a function called usePanGestureHandler. And it gives us a couple of variables, a gesture handler that we can assign here. And the translation vector of the card, the velocity. So when we release the gesture-- [INAUDIBLE] velocity of the gesture, and the state of the gesture. Is the gesture active? Is the gesture [INAUDIBLE] and-- so here we assign the PanGestureHandler. I probably need to move key here. And the PanGestureHandler takes a single [? children ?] which needs to be an Animated.View from Reanimated. So here we use Animated.View from react-native-reanimated. So we get the translation vector here. I think we can assign it to this transformation to see if we can move the card around. So we're going to use a transform. And there is a utility function in Redash called translate where we can give it a vector. And it will apply translate x, translate y. A simple shortcut. So here I can move the cards around. But I should create one PanGestureHandler for each gesture handler. So I'm just going to move it here. Ah-ha. So you see I can move the card. But now if I start the gesture again, it starts from the original position. So we need to save the position in an offset value when the gesture ends. And again in Redash, we have a utility function for that. So I'm going to create translate x equals withOffset. And the first parameter is animation value. And the second parameter is the state of the gesture. So that we know when to save the offset. So translate x. We're going to do the same for y. And here we're going to apply the transform. So translate x. Translate y. It's translation. So here I can move the card around. And it remembers its position across time. And you see, when I stop the gesture, it's kind of abrupt. There is no like physical momentum. So what we can do here instead of using withOffset, we can use withDecay, which will add some nice decay. And this is where the velocity comes into play. Because the decay is calculated according to the velocity. So value is translation x. Velocity is velocity dot-- oops. velocity.x. State is [? the ?] state. So same for y. So now you see you have this nice momentum when moving the card. And you see it is interruptable as well. So now let's have a nice wallet animation using these concepts of PanGestureHandler and decay. So I'm going to move this outside the loop. And we're going to move the PanGestureHandler to be unique to [INAUDIBLE] [? view. ?] And we want to translate only on the x-axis, actually. So we just want to scroll. Oops. We just want to nicely scroll [? our ?] cards. So something is not working. Because we forgot, as I mentioned, the single child needs to be an animated [INAUDIBLE].. But what I'm going to do, because I want to keep this one, I think I'm going to wrap it here. And put an Animated.View. Let's see how it looks. [LAUGHING] It's not translate x, but translate y. OK. So it translates nicely. And we have the nice momentum effect from decay. So now we want to clamp the values. We're going to use a function called diffClamp. So it clamps-- diffClamp clamps of value with a min max. But we call it diffClamp, meaning that, you know, if we go to minus [? 100 ?] pixels, just a delta of one pixel will be taken into account. So it's much more responsive if you want-- if you scrolled far back in one of the lower or upper bound and want to go back to one of the-- within the bounds of the [INAUDIBLE] clamping. So we're going to add diffClamp here. And here there is a trick. So there's currently a bug in the diffClamp implementation of Reanimated. So we're going to use diffClamp from Redash. So in order to not have this bug. So the maximum value is 0. Right. When we are at this state here. And the minimum value is going to be so-- the length-- so the height of all these cards minus this height here. So this would be minus cards.length times card height, which we have defined here, perfect-- minus the container height, which we calculate using onLayout. So let's have a look. So here, so you see this is where diffClamp comes in. If I use only clamps, so here I'm scrolling, scrolling, scrolling, scrolling, scrolling. Then if I want to scroll back like this, I would have to do all the opposite scrolling that I did. But even [INAUDIBLE] maybe I'm like 500 pixels right now. I can just immediately go back up. And this is what diffClamp does. I mean, I can show you quickly with clamp. So I'm here I'm scrolling down, scrolling down, scrolling down, scrolling down, scrolling down. If I want to scroll up again, I need to scroll up, scroll up, scroll up, scroll up, scroll up. And now it scrolls up. With diffClamps, it's going to be automatic. Scroll down, scroll down, scroll down. But you see immediately scroll up. And let's look. So the lower bound is not correct. It's plus container height. Sorry. So 0 and perfect. And very cool. So now let's animate our cards. So maybe we want to add-- so we want, actually, now-- we're going to clamp. So I'm going to call this y. So if the card has reached the top, I don't want it to go above. I want to clamp its value. So translate y. We're going to interpolate from the y animation value. So the input range goes from 0 and if we translate to-- so for the card to be at the top, if it's 0, it's 0. If it's-- minus card width, so that would be minus card-- so card height, sorry-- times index. So this would be our clamping value. So the output range would be the same value. But we're going to use extrapolate clamp, meaning-- so this should be the maximum value the card can go. So do not go outside the screen. And so by using-- so here we use linear interpolation. [? These ?] values match one to one. But when we have a value, for instance, that is lower than minus card height times index, we want to be at minus card height times index, not a lower value. So we are going to write extrapolate-- extrapolate.clamp. Let's have a look. So we see the card moves on top of each other. You can even add, maybe, just, you know, if you want to build some effects, here. Maybe it's minus card height plus 16. Or minus 16, sorry. You [? see here-- ?] 24 [INAUDIBLE].. So you already have here a nice wallet animation. So now we want to calculate the position of each card. And so the position y is y plus the index times card height. And we're going to use this value to do a couple of interpolations. So if the card is disappearing, its position is at least minus height. So from minus 0 to minus height. Minus card height. Sorry. Right. So here, the card, its position is 0. So if the card is on top, its position is 0. If the card is at the bottom here-- so what is the position on bottom? It's the number of visible cards in the screen times the height of the card. So that would be visible cards times card height. What are the number of visible cards? So visible cards-- we do a math floor of the container height divided by card height. And so this is the number of visible cards. So this is-- so minus 1 for the range and is appearing is the number of visible cards times card height. So is bottom, is appearing. So now we can create some scale transformation. So the scale of the card-- so we interpolate from position y. So the input range is disappearing is on top, is on bottom, and is appearing. And the animation values are-- so 0, 5, it's disappearing. 1, 1 if it's in the visible range. And 0, 5, when it's appearing. Let's try that. So here you see it disappears nicely in the background. And it appears nicely here. What we can do is add some clamping. That looks good. Let's add some opacity. So same story for opacity. So I can just add it here. And here, 1-- oh, actually, the same value. No. What we want to do here is to not clamp, I think. So we see here it disappears nicely. And so one thing we can fix is, you see when the card appears, it's too far away from the top card. It's not super smooth. So we can add some extra translation here. So here we can add. So you see here, we don't do a plus. Again, we use [INAUDIBLE] [? add ?] animation node. So let's call it extra translation y. So which we're going to interpolate from position y. So actually let me move these up. So we interpolate from position y. Oops. So input range. So we go from isOnBottom on bottom to isAppearing. Output range. So at bottom, the translation is 0. At isAppearing, we know that at isAppearing, the size is-- scale 0, 5. So the height is card height divided by 2. And we want to move it by half of the card height. So it's going to be card height divided by 4. And of course, here we want to clamp. So we don't want to go outside these values. Extrapolate clamp. Let's have a look. No. It's not. It's minus card height divided by 4. And you see now it looks very cool. And one thing we can do here [INAUDIBLE] the scroll is not completely over. So we don't want to stop the scroll on-- I'm being very picky here. But it doesn't feel nice that the end scroll position is this. So the last card should be at least as the full position. So here, maybe instead of container, we can do visible cards. Now what do we need to do? So we container height. Let's try visible cards times card height. Yes. So that looks good. So here our end scroll position looks much nicer. So you see a pretty cool user interaction using the PanGestureHandler from react-native GestureHandler. But there of course are tons of different user interactions you can build using these APIs. But I hope that this nice [INAUDIBLE] user interactions really gave you some inspiration on the kind of gestures and animations you can build using these APIs. [MUSIC PLAYING] These gestures and animations we just looked at integrate seamlessly with react-native-svg. And this allows for an interesting range of fun and creative user experiences and components. And this is thanks to an incredible community and open source work. Right? So we looked at react-native gesture handler, react-native-reanimated, which allows us to declare our animations on the UI Thread. And then there has been a tremendous amount of open source and community work which has been spent in order for these libraries to nicely integrate with react-native-svg. And so I would like to show you a simple example, which is going to be a circular slider. So here we have a svg circle. Here the circular progress. And so we have the white one here. And we're going to animate the stroke of this one to show some progress. And here we have a cursor component, which we are going to be able to move around. And its value will always fit the bounds of the circle. So here we have a theta animation value. And we are going to move the cursor around to update this value. So theta goes from 0. This is the position at 0. Or actually, here we have a rotate of minus 90 degrees. So 0 is going to be here. Let me remove this one for now. So we're going to have 0 here. We move pi minus pi 0. And so maybe here we're going to normalize. Because this goes from 0 to pi minus pi 0. We're going to normalize it to go from 0 to 2 pi. 360 degrees. And the way we're going to do it-- we are going to set-- so we have a theta value, 0. This will set the position of the cursor on to the circle. How are we going to do it? Well, we have three coordinate systems to work with in react-native. There is a [INAUDIBLE] coordinate system. So there is an x-axis which goes from the left to right, and the y-axis, which goes from top to bottom. This is [INAUDIBLE] coordinate system. Then there is a Cartesian coordinate system, where we have a center of origin somewhere. Here our center is going to be the middle of the circle. And the y-axis goes from 0 from bottom to top. So the opposite of the [INAUDIBLE] axis, which goes from top to bottom. And then we have the x-axis, which is identical. It goes from left to right. This is our Cartesian coordinate system. The third coordinate system is a polar coordinate system. So here we also have an origin. And given an x and y value in the Cartesian coordinate system, we are going to get an angle in a theta value in radians and a radius. So we take the angle. We convert it to the-- so we have a polar coordinate. So we have a radius and an angle. We can convert it to a [INAUDIBLE] coordinate, which would give us x and y positions [INAUDIBLE].. So that will position the cursor on to the circle. Then we move the circle around. So we have an x and y-coordinate which we can transform to a polar coordinate in order to know the angle theta. And so we can assign the theta value, which will drive the animations of the stroke value. And also maybe we can animate the color of the progress. So maybe you want green at the beginning, red at the end. And so if you go on my YouTube channel, I will put the links in the video description. I have videos on how we can convert from one coordinate system to the other. Here we're going to use again utility functions from Redash to seamlessly go from one to the other. So let's get started. So we are going to start with our cursor. And once we have-- so the cursor is going to [? write ?] the data value. And once we have it working, we will look into animate the circular progress component that we have here. So let's look at the cursor. So the first thing we're going to do is to wrap a PanGestureHandler so we can move it around. So PanGestureHandler from react-native gesture handler. And we need the gesture handler. We're going to use the helper function from Redash. So we have gesture handler. usePanGestureHandler. And we're going to get a translation vector and the state of the gesture. Oops. Some [? typo ?] here. And so here we're going to add some translate. But what-- so what we want to do-- OK. Let's add some translate. So translate x, we're going to use-- so we want to remember the position across different gestures. So we're going to use withOffset. So translate x state and translate y. Translate y. [INAUDIBLE] translation. Let me apply the transformation. Translate x. Translate y. So I can move the cursor around. Super. But now it needs to be, you know, whatever is the position within the bounds of the circle. So this is where we need to convert these values into polar coordinates. So I'm going to create x, y. And we have a center vector, which is the middle of the screen. So let me create a vector. So center is x is width divided by 2. y is height divided by 2, which we need to import from the dimensions API. So we have width, height from dimension get window. So let's convert these into polar coordinates. So we have polar equals-- so these are Cartesian. So we're going to use Cartesian to polar. The math behind this function is super interesting. I have some videos on this topic. So if you're interested, I definitely recommend you check it out. The first argument is a point. So we have x, y. And we probably need to [INAUDIBLE] polar-- no, Cartesian to polar. That's correct. And so we get the theta angle, which we can assign to this animation value. The radius-- here the radius doesn't matter. What we need to do is to set the radius to be the radius of our circle and convert back these coordinates to Cartesian coordinates. So we are going to have x translate x, y translate y. So we are going to convert polar to Cartesian. Theta is polar.theta. theta. No problem. But radius is not the radius of our cursor, but the radius of the circle. So that will bound the cursor to only move around the circle. And the second parameter should be polar to coordinate to Cartesian. It looks good. Is this radius-- radius [INAUDIBLE].. So you see we have an issue. So translate x with the center. The origin of-- so I think I should set center here. Sorry. OK. It's not Cartesian to polar. But [INAUDIBLE] to polar. My mistake. So [INAUDIBLE] to polar. And we can set the center [? of ?] origin here. So the center. And here it's convert to polar to [INAUDIBLE].. I was confused why it didn't need to use the center of origin-- the origin-- that didn't make any sense. So now still actually, it doesn't work. We have a weird-- so actually here, it's the center because it's not-- the container, so it's not the window, but the content, which is the size here of this container, of the circle [INAUDIBLE] I put background color red. So you see our center is not height of the screen divided by 2, but radius and the radius. So let me update this. So center is radius. And so now it animates nicely. So now we need to set the theta value. And so you could set the value to be polar.theta directly. But polar.theta goes from 0 to pi and minus pi to minus 0. So here the value would be minus pi divided by 2. And not 3/4 of 2 pi. So we want to normalize the value to go from 0 to 2 pi. So we're going to create useCode and we're going to assign [INAUDIBLE] and we're going to assign theta to be polar dot theta. But we need to normalize theta. So normalize theta. Or we take theta-- so as an animated node. So if theta is less than 0, so it's minus 0 minus pi, we need to add 2 pi. So theta is less than 0. It's theta plus 2 pi. If not, it's the theta value. So from 0 to pi theta is OK. And now if let's say we have minus pi minus pi plus 2 pi, would be 2 pi 3/4 of 2 pi. So it's going to be 2 pi minus half of 1 pi. So this should give us proper values for theta. And here, let's see. We're going to interpolate the [? color ?] on theta to see what we get. So let's use interpolate. So colors are numbers. And we have a interpolate color function in Redash. So we can interpolate on theta. And so input range-- so theta goes from 0 to 2 pi. So we have 0. Let's say pi and 2 pi. We're going to use three colors. Output range-- we can use style guide, palette, secondary, primary, and primary. And tertiary. Let's have a look. So we see here the color updates nicely. So now let's animate our svg circle. And we're going to use a dash stroke array to animate the progress of the circle. So we want dash stroke array to be circumference of the circle. So we want one empty stroke to be the circumference of the circle. One full stroke to be the circumference of the circle-- so we can animate, really, from 0 completely empty to 1 completely full. So the circumference of the circle is 2 pi times the radius. And the length of the arc of circle is going to be radius times the theta, right? And so this is going to be the offset-- the dash stroke offset. So let's-- so we need to calculate the circumference. Which is going to be 2 pi times the radius. Which we get-- [INAUDIBLE] as property. So we can assign dash stroke array to be-- so circumference, circumference. And we can calculate the-- so here, OK. So it's dash stroke offset. And here-- so we're going to have a couple of animation values. It's not a circle, but an animated circle. An animated circle. We use create animated components in order to have the animated wrapper that can accept animation values as property. So now we need to calculate stroke dash offset. Which is actually the same formula [INAUDIBLE] here. But instead of having the full angle, which is 2 pi, we use theta. So it's going to be multiply theta by radius. Let's have a look. Looks good. But also very strange. So the stroke color appears to be good. Background color. We need to add stroke width. And now there is a strange offset value here. So here it looks like at 0, it's correct. And then there is-- you see some offset value that goes further and further. And I think here I wrote it down because I wrote radius is the radius minus the stroke width divided by 2. That makes sense. Because we want the center to be here. So I should probably replace radius here. Yeah. Now that looks good. It's fun, isn't it? I could play with this always [INAUDIBLE].. But really a nice example of how gestures and animations seamlessly integrate with each other. [MUSIC PLAYING] Free Code Campers, I hope you enjoyed this workshop on declarative gestures and animations in react-native. Let me know what you think in the comments below. If you're interested to go further with this topic, I will link to resources in the video description. We discussed the heart of the matter. Why is this topic so [INAUDIBLE] in React Native and the APIs and strategies to use in order to build these very smooth user experiences. We looked at transitions, the easiest way to animate react-native components. And then we implemented a simple timing function using the bare metal reanimated API. And we've built this timing function to be interruptable, so we can pause, resume. But also we can also loop the function across time. Then we built our first gesture. So we had these cards and we could move them around and swipe them. And we've used the gesture API to build a nice wallet user interaction. And finally, we've looked at how svg can be used to seamlessly integrate with these gestures and animations. And that allows for a range of really fun and creative user interactions to be built. If you go on my YouTube channel, I have dozens of videos on this topic. For instance, in the case of the svg example, we used functions which leverage trigonometry behind the scene, right, where we converted from polar coordinate system to [INAUDIBLE] and vice versa. I have videos where I really explain the math behind the scene. And trigonometry is very important in animations, because as soon as you have something that has some sort of rotation, it's going to involve trigonometry. And I have quite some videos about this topic. And I have also more advanced videos on the topic of svg animation. For instance, you can use svg animations to morph from one svg path to the other. And there is also really interesting examples with Bezier curves. That is really-- we only scratched the surface. There is really tons of great things you can build using svg animations. We looked at the PanGestureHandler. But there are, of course, different kinds of gestures in React Native-- the tap gesture handler, the pinch gesture handler, the rotate gesture handler. And I also have videos on these topics. I will link to these resources in the video description. And we used the transform API to move things around. And we even did transformation of origin. The transform API from react-native is incredibly powerful. And we've not even looked at all the things it has to offer. And I also have videos on this topic, on how to build advanced 2D transformations, how to save the state of complex transformations across gestures. And I even have a video where we go to the third dimension. So if you are interested, I hope that you will check it out. So I'm really looking forward to talk to you soon. And in the meantime, happy hacking.
Info
Channel: freeCodeCamp.org
Views: 68,519
Rating: undefined out of 5
Keywords:
Id: wEVjaXK4sYQ
Channel Id: undefined
Length: 79min 57sec (4797 seconds)
Published: Fri Jun 05 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.