How to implement a half-manual, half-automatic animation in React Native in a performant way? - react-native

I need to create an animation in React Native which is best described as follows:
A user grabs some UI item and drags it from its original position.
While the user moves it, there is also some rotation, the UI item rotates around the Z-axis according to its motion on the XY plane.
When the user releases the item, both its position and its rotation are animated back to their original values (a "fire-and-forget" animation).
This is similar to a "pull down to refresh" UI widget. While the user is pulling down, we have both the Y translation and the rotation of the loading wheel coupled to the touch movement, and when the user lets go, there is a fire-and-forget animation without control by the user.
How would I implement this in a performant way?
I'm thinking of something like this:
We store the user's motion in the state of the component. With every move of the touch, the UI is re-rendered (not sure if that is performant).
When the user releases the touch, we use Animated.timing to generate the fire-and-forget animation.
Because of 2., the value in the state needs to be an Animated.Value instance. I'm not sure if 1. can be implemented in a performant way, using an Animated.Value as a state.

The answer is (of course): use react-native-reanimated. With this library, you have complete, declarative control over native animations and their interactions with gestures.
This is an example app which has what I was looking for:
Here is the source code.

Related

How can i make my TouchableOpacity's reaction to onPress immediate by changing its opacity so users can easily feel they actually press that button?

Sometimes my app is quite busy with other stuff so for a noticeable amount of time,
it becomes unresponsive to touch events bound to my TouchableOpacity components.
activeOpacity property makes the button changing its opacity with some delay (1-2 seconds) if that kind of heavy load being present so users do not feel that they actually press that button and keep pressing until they see a reaction. Of course it creates some frustration for them.
My understanding is that I think these opacity changing animation also requires some communication between JS and native side over the bridge. That's why it is affected by other bridge communication and becomes unresponsive for a while.
Is there any way to overcome this situation in React Native side and make the button immediately change its opacity?
Or can it be handled only by creating a new native button component for that purpose ?
Check out requestAnimationFrame https://reactnative.dev/docs/timers.
"requestAnimationFrame(fn) is not the same as setTimeout(fn, 0) - the former will fire after all the frame has flushed, whereas the latter will fire as quickly as possible (over 1000x per second on a iPhone 5S)."
Wrap your onPress logic in requestAnimationFrame so that the animation happens before the logic.

React Native - Dynamically change component position

I want to display several components (maybe up to 6) in my React Native app that continuously change their position, i.e. slowly but fluently float on the screen and change their moving direction randomly or based on an angle at which they approach the edge of their parent, if they do. What I don't know is how to change their position dynamically in a performance friendly way. Because I also want to let the user interact with them by tapping on them, it cannot be a video in a background or something similar.
If I were dealing with a standard React app running in a browser, I think I would simply manipulate the element's style attributes top and left. Since I want to implement this in React Native, I think there must be a solution that is way more performant and elegant, involving manipulation of the native elements that back the React Native views or using some animation mechanism, because re-rendering a view with like 6 components at least 30 times per second doesn't seem like an appropriate solution and an overhead in general.
Googling things like "react native dynamically move element" or "react native dynamically change element position" did not bring any obviously appropriate results. I came across a native element method setNativeProps, though, but couldn't find any information on what props I could set to change the element's position on the screen and how performant that would be. I also looked into React Native's Animated API, but could not quite relate my problem to the solutions that this API offers from a first glance.
Does anyone know a mechanism or maybe a library that would be appropriate to use in the situation that I described?

React Native - Dismiss Menu on Any Touch Event

I would like to have a small menu that closes if the user interacts with any other component. For example if the user tries to scroll or interact with any of the content in a scrollview behind the menu (see the image below for reference).
I have two ideas for how this might be achieved:
A transparent layer behind the menu with an absolute position and dimensions matching the device. If this layer registers a touch event the menu can be dismissed. The problem with this is that from the users perspective the touch event was totally ignored. So for this to work well I would need to be able to still pass the touch event through the absolute layer to the content behind it.
Add callbacks to every component that could be interacted with to notify the menu that it should close. This option seems like it would be very messy and because of the large number of components in my use case it is not practical to implement and maintain.
Is there an other proper way to solve this problem? Can any of the issues I raised with the ideas above be resolved or mitigated?
Wrap your view with a TouchableWithoutFeedback component and provide it a onPress callback that hides the menu if it's open. Depending on how top-level the 'expand' icon is, you may want to track the menu's visibility in redux and dispatch an action onPress to track globally.

React Native: ScrollView with auto scroll

I would like to create a carousel that scrolls automatically until the user scrolls / touches the ScrollView itself.
The auto-scrolling itself works fine with using scrollView.scrollTo but how could I detect if the user is interacting with the ScrollView? I took a look at the onScroll event but this does not seem to distinct between a user generated event and an event that was generated by calling scrollTo.
Also I'd like to know if it is possible to get the current scroll position from the ScrollView directly instead of reading it everytime from the onScroll event.
I'm very thankful for any tips and suggestions.
By digging into ScrollView's source code you can notice a few undocumented callbacks that will help you achieve what you're after, namely onTouchStart and onTouchEnd. These two callbacks are triggered only when user interacts with the ScrollView and not when you scroll programmatically.
You will probably want to clear your auto-scroll interval on onTouchStart and restart it after a delay on onTouchEnd.
Regarding your next question, the answer is no. As far as I know, no getter is currently exposed to retrieve the current scroll position. Therefore, you need to rely on the event passed to onScroll, retrieve event.nativeEvent.contentOffset['x' or 'y'], and store it in your component's state.
Note that if you're doing some heavy animations that need to follow scroll position closely (e.g. animated header or parallax image), it would be a good idea to use the native driver for Animated.event. You can learn more about it on React Native's blog.

React native: infinite panResponder pagination

I'll do my best to explain this without code, which, in my opinion will not add clarity to this question as PanResponder code is quite complicated, and i need only concept, not the code itself.
So, i've implemented pagination, something like in this gif.
The idea is to have only 3 "pages" rendered at a time. If user goes to the next scene (swipes from right to left), app fetches one more scene from server and renders it behind currently active scene. etc.
Once user swiped to the next scene, i need to reset panResponder: the "nextScene" should appear at the default position between new nextScene and prevScene and then user will be able to swipe again.
The problem is that i don't know how to make it smooth. Right now i'm just passing new props (new currentScene, nextScene, prevScene) and changing key property of panResponder component. If a new scene contains images, they seems to be refetched each time when i'm changin key. That, probably, fixable by proper caching, but this makes me doubt if my approach is correct. Is there another way to make the reset of panResponder component and put the new scene into default position between new nextScene and prevScene.