How to animate header to show based on scrolling in react native? - react-native

So Ideally, When i scroll down, I want the header to disappear(slide down) and when I scroll up I want it to show (slide up). Idc where im at in the page. I just want the animation to fire when those 2 events occur. I see some apps have this but I can't think of how to replicate it. please help me set a basis for this

You can use Animated.FlatList or Animated.ScrollView to make the scroll view, and attach a callback to listen onScroll event when it is changed. Then, using interpolation to map value between y-axis and opacity.
searchBarOpacityAnim is a component's state. By using Animated.event, the state will be updated when a callback is called. Also, don't forget to set useNativeDriver to be true. I've attached the link to document below about why you have to set it.
<Animated.FlatList
...
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: searchBarOpacityAnim } } }],
{ useNativeDriver: true },
)}
...
/>
Then, use Animated.View wraps your component which you want to animate it. Use .interpolate to map value between the state and component's opacity like the example below...
<Animated.View
style={{
opacity: searchBarOpacityAnim.interpolate({
inputRange: [213, 215],
outputRange: [0, 1],
}),
}}
>
<SearchBar />
</Animated.View>
You can read more information about useNativeDriver, .interpolate, and Animated.event here.
https://facebook.github.io/react-native/docs/animated#using-the-native-driver
https://facebook.github.io/react-native/docs/animations#interpolation
https://facebook.github.io/react-native/docs/animated#handling-gestures-and-other-events

You can use Animated from 'react-native'
here an example changing the Topbar height:
import { Animated } from 'react-native';
define maxHeight and minHeight topbar
const HEADER_MAX_HEIGHT = 120;
const HEADER_MIN_HEIGHT = 48;
initialize a variable with the scrollY value
constructor(props) {
super(props);
this.state = {
scrollY: new Animated.Value(
Platform.OS === 'ios' ? -HEADER_MAX_HEIGHT : 0,
),
};
}
on render you can interpolate a value acording the scrollY Value
render() {
const { scrollY } = this.state;
// this will set a height for topbar
const headerHeight = scrollY.interpolate({
inputRange: [0, HEADER_MAX_HEIGHT - HEADER_MIN_HEIGHT],
outputRange: [HEADER_MAX_HEIGHT, HEADER_MIN_HEIGHT],
extrapolate: 'clamp',
});
// obs: the inputRange is the scrollY value, (starts on 0)
// and can go until (HEADER_MAX_HEIGHT - HEADER_MIN_HEIGHT)
// outputRange is the height that will set on topbar
// obs: you must add a onScroll function on a scrollView like below:
return (
<View>
<Animated.View style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
backgroundColor: '#2e4265',
height: headerHeight,
zIndex: 1,
flexDirection: 'row',
justifyContent: 'flex-start',
}}>
<Text>{title}</Text>
</Animated.View>
<ScrollView
style={{ flex: 1 }}
scrollEventThrottle={16}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.state.scrollY } } }],
)}>
<View style={{ height: 1000 }} />
</ScrollView>
</View>
);
}

Related

How to press a component and it rotates 180 degrees in react native?

I have a Pressable component and inside it I have an icon. I want to press it and rotate it 180 degrees, how can I do that?
So to do this you must take use of the Animated library from react-native.
In which you make animated values and make functions to update them. Here's a full sample of you want (https://snack.expo.dev/#heytony01/grumpy-pretzel) and below is the explantion.
First import the library and make an Animated value
import { Text, View, StyleSheet,Animated,TouchableWithoutFeedback } from 'react-native';
const spinValue = React.useState(new Animated.Value(0))[0]; // Makes animated value
Next define functions to change the value
// When button is pressed in, make spinValue go through and up to 1
const onPressIn = () => {
Animated.spring(spinValue, {
toValue: 1,
useNativeDriver: true,
}).start();
};
// When button is pressed out, make spinValue go through and down to 0
const onPressOut = () => {
Animated.spring(spinValue, {
toValue: 0,
useNativeDriver: true,
}).start();
};
The tricky part is that in order to rotate in react-native you need to pass "0deg" or "12deg" etc..
<View style={{
transform: [
{ rotate: "45deg" },
]
}}>
</View>
So what you do is you interpolate the animated value to "0deg" to "360deg"
// spinDeg will be between '0deg' and '360deg' based on what spinValue is
const spinDeg = spinValue.interpolate({
useNativeDriver: true,
inputRange: [0, 1],
outputRange: ['0deg', '360deg']
})
Lastly you pass in the spinDeg into your button and your done
// The animated style for scaling the button within the Animated.View
const animatedScaleStyle = {
transform: [{rotate: spinDeg}]
};
return (
<View style={{flex:1,justifyContent:"center",alignItems:"center"}}>
<TouchableWithoutFeedback
onPress={()=>{}}
onPressIn={onPressIn}
onPressOut={onPressOut}
>
<View style={{justifyContent:"center",alignItems:"center",backgroundColor:"lightgray",padding:15,borderRadius:20}}>
<Text>PRESS ME</Text>
<Animated.View style={[styles.iconContainer, animatedScaleStyle]}>
<Ionicons name="md-checkmark-circle" size={32} color="green" />
</Animated.View>
</View>
</TouchableWithoutFeedback>
</View>
);

PanGestureHandler with functional component react native

I am trying to use a Gesture handler with a functional component. The problem is when I drag for the second time it's dragging from initial position again
This is my code below
let translateXRef = useRef(new Animated.Value(0)).current;
const onGestureEvent = useCallback(
Animated.event(
[
{
nativeEvent: {
translationX: translateXRef,
},
},
],
{ useNativeDriver: true }
),
[]
);
<View
style={{
backgroundColor: '#FFFFFF80',
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0,
height: 100,
}}
>
<PanGestureHandler
onGestureEvent={onGestureEvent}
onHandlerStateChange={onHandlerStateChange}
>
<Animated.View
// eslint-disable-next-line react-native/no-inline-styles
style={{
height: '100%',
width: 10,
backgroundColor: AppColors.buttonColor,
transform: [{ translateX: translateXRef }],
}}
/>
</PanGestureHandler>
</View>
You need to use the context in addition to the event in your callback.
I'm not sure why you're using the Animated.event. You should generate your callbacks using the useAnimatedGestureHandler.
Each of those callbacks onStart, onActive, onEnd, etc... take an event and context argument.
The context argument is an object that would let you set your previous position so that then next click would not reset the position.
Here's more info:
https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/events/#using-context
Also, a pretty good video that explains it:
https://youtu.be/4HUreYYoE6U

Remove/Fill background color - React Native Animation

Is this doable? Check this link for example.
I've been trying to make it work. This is my current code:
constructor(props) {
super(props);
this.state = {
animationValue: new Animated.Value(0)
};
}
<Animated.View
style={{ flex: 0.15, backgroundColor: `rgba(0,0,0,${background}` }}
onPress={this.removeBackground}
>
<Text style={[{ textAlign: 'right' }>{hometeam.result} - {awayteam.result}</Text>
</Animated.View>
The background is a variable that looks like this:
const background = this.state.animationValue.interpolate({
inputRange: [0, 1],
outputRange: [0, 1]
});
What I want to happen is when you click on the animated view, I want the background color to fade. So I was trying to use rgba and change the alpha based on the background variable, which gave me an error.
This is my function to remove the background:
removeBackground = () => {
this.state.animationValue.setValue(0);
Animated.timing(this.state.animationValue, {
toValue: 1,
duration: 2000,
}).start();
}
Any clues how I could do this?

How to make a looped image background in React Native with Animated

I originally used setInterval() to make a looped image background by having two images that one starts at x:0 and another starts at x: imageWidth, then update them in the following way:
_updateBackgroundImage = () => {
this.setState({
background1Left: this.state.background1Left > (-this.backgroundImageWidth) ? this.state.background1Left-3 : this.backgroundImageWidth,
background2Left: this.state.background2Left > (-this.backgroundImageWidth) ? this.state.background2Left-3 : this.backgroundImageWidth,
})
}
It worked just fine but setInterval() was causing conflicts with another component from an online library, so I switched to using Animated API and have the following code:
this.translateValue = new Animated.Value(0)
translate() {
this.translateValue.setValue(0)
Animated.timing(
this.translateValue,
{
toValue: 1,
duration: 14000,
easing: Easing.linear
}
).start(()=>this.translate())
}
const translateBackgroundImage1 = this.translateValue.interpolate({
inputRange: [0, 1],
outputRange: [0, -this.backgroundImageWidth]
})
const translateBackgroundImage2 = this.translateValue.interpolate({
inputRange: [0, 1],
outputRange: [this.backgroundImageWidth, -this.backgroundImageWidth]
})
return (
<View style={{flex:1}}>
<Animated.Image
style={{
flex: 1,
position: 'absolute',
left: translateBackgroundImage1,
}}
resizeMode={Image.resizeMode.cover}
source={this.backgroundImage}
/>
<Animated.Image
style={{
flex: 1,
position: 'absolute',
left: translateBackgroundImage2,
}}
resizeMode={Image.resizeMode.cover}
source={this.backgroundImage}
/>
To apply the same logic I used for setInterval() I would have translateBackgroundImage1 to start at x:0 in the first loop and then starts at x: ImageWidth
I'm not sure how to implement this with Animated
I eventually found a solution. It's not very clean but works.
I essentially have two Animated.image loaded from the same image. Then I have a translateValue1, which controls the left position of the first image. Then based on translateValue1, we have translateValue2 that has an offset of the imagewidth. translateValue2 controls the left position of the second image.
When the first image about to exit from the screen, it will go to the far right of the screen, and at the same time the second image will be moved to the front of the first image, so we need to change the offset to -imagewidth. Therefore there's a setState method in each of the two animation functions.
Inside the constructor I have these variables:
constructor(props) {
super(props);
this.backgroundImage = require('../assets/images/graidenttPastel.jpg');
this.backgroundImageWidth = resolveAssetSource(this.backgroundImage).width;
this.translateXValue1 = new Animated.Value(-1);
this.translateXValue2 = new Animated.Value(0);
this.animationLength = 20000;
this.state = {
translateXValue2Offset: this.backgroundImageWidth,
stopAnimation: false,
}
Then I have these two functions, each controls half of the loop:
translateXFirstHalfLoop() {
this.translateXValue1.setValue(-1);
this.setState({translateXValue2Offset: this.backgroundImageWidth});
this.firstHalfLoop = Animated.timing(
this.translateXValue1,
{
toValue: -this.backgroundImageWidth,
duration: this.animationLength/2,
easing: Easing.linear
}
).start(() => {
if(this.state.stopAnimation === false) {
this.translateXSecondHalfLoop()
}
})
}
translateXSecondHalfLoop() {
this.translateXValue1.setValue(this.backgroundImageWidth);
this.setState({translateXValue2Offset: -this.backgroundImageWidth});
this.secondHalfLoop = Animated.timing(
this.translateXValue1,
{
toValue: 0,
duration: this.animationLength/2,
easing: Easing.linear
}
).start(() => {
if(this.state.stopAnimation === false) {
this.translateXFirstHalfLoop()
}
})
}
Finally in the render() method, I have two Animated.Image like below:
render() {
this.translateXValue2 = Animated.add(this.translateXValue1, this.state.translateXValue2Offset);
return (
<SafeAreaView
style={[{backgroundColor: THEME_COLOR, flex: 1}]}
forceInset={{ bottom: 'never' }}>
<Animated.Image
style={{
position: 'absolute',
left: this.translateXValue1,
}}
resizestate={Image.resizeMode.cover}
source={this.backgroundImage}
/>
<Animated.Image
style={{
position: 'absolute',
left: this.translateXValue2,
}}
resizestate={Image.resizeMode.cover}
source={this.backgroundImage}
/>
<View style={{flex:1}}>
{this._renderScreenContent()}
</View>
</SafeAreaView>
);
}
Since each half loop function calls the other, we need to stop them before this component is unmounted, so we have this additional step below:
//clean up animation first
this.setState({stopAnimation: true}, () => {
this.props.navigation.goBack()
})
If you are looking to animate an ImageBackground, try
var AnimatedImage = Animated.createAnimatedComponent(ImageBackground)

How to set background color of view transparent in React Native

This is the style of the view that i have used
backCover: {
position: 'absolute',
marginTop: 20,
top: 0,
bottom: 0,
left: 0,
right: 0,
}
Currently it has a white background. I can change the backgroundColor as i want like '#343434' but it accepts only max 6 hexvalue for color so I cannot give opacity on that like '#00ffffff'. I tried using opacity like this
backCover: {
position: 'absolute',
marginTop: 20,
top: 0,
bottom: 0,
left: 0,
right: 0,
opacity: 0.5,
}
but it reduces visibility of view's content.
So any answers?
Use rgba value for the backgroundColor.
For example,
backgroundColor: 'rgba(52, 52, 52, 0.8)'
This sets it to a grey color with 80% opacity, which is derived from the opacity decimal, 0.8. This value can be anything from 0.0 to 1.0.
The following works fine:
backgroundColor: 'rgba(52, 52, 52, alpha)'
You could also try:
backgroundColor: 'transparent'
Try this backgroundColor: '#00000000'
it will set background color to transparent, it follows #rrggbbaa hex codes
Surprisingly no one told about this, which provides some !clarity:
style={{
backgroundColor: 'white',
opacity: 0.7
}}
Try to use transparent attribute value for making transparent background color.
backgroundColor: 'transparent'
You should be aware of the current conflicts that exists with iOS and RGBA backgrounds.
Summary: public React Native currently exposes the iOS layer shadow
properties more-or-less directly, however there are a number of
problems with this:
1) Performance when using these properties is poor by default. That's
because iOS calculates the shadow by getting the exact pixel mask of
the view, including any tranlucent content, and all of its subviews,
which is very CPU and GPU-intensive. 2) The iOS shadow properties do
not match the syntax or semantics of the CSS box-shadow standard, and
are unlikely to be possible to implement on Android. 3) We don't
expose the layer.shadowPath property, which is crucial to getting
good performance out of layer shadows.
This diff solves problem number 1) by implementing a default
shadowPath that matches the view border for views with an opaque
background. This improves the performance of shadows by optimizing for
the common usage case. I've also reinstated background color
propagation for views which have shadow props - this should help
ensure that this best-case scenario occurs more often.
For views with an explicit transparent background, the shadow will
continue to work as it did before ( shadowPath will be left unset,
and the shadow will be derived exactly from the pixels of the view and
its subviews). This is the worst-case path for performance, however,
so you should avoid it unless absolutely necessary. Support for this
may be disabled by default in future, or dropped altogether.
For translucent images, it is suggested that you bake the shadow into
the image itself, or use another mechanism to pre-generate the shadow.
For text shadows, you should use the textShadow properties, which work
cross-platform and have much better performance.
Problem number 2) will be solved in a future diff, possibly by
renaming the iOS shadowXXX properties to boxShadowXXX, and changing
the syntax and semantics to match the CSS standards.
Problem number 3) is now mostly moot, since we generate the shadowPath
automatically. In future, we may provide an iOS-specific prop to set
the path explicitly if there's a demand for more precise control of
the shadow.
Reviewed By: weicool
Commit: https://github.com/facebook/react-native/commit/e4c53c28aea7e067e48f5c8c0100c7cafc031b06
Adding reference of React-Native Version 0.64
Named colors
Named Colors: DOCS
In React Native you can also use color name strings as values.
Note: React Native only supports lowercase color names. Uppercase color names are not supported.
transparent#
This is a shortcut for rgba(0,0,0,0), same like in CSS3.
Hence you can do this:
background: {
backgroundColor: 'transparent'
},
Which is a shortcut of :
background: {
backgroundColor: 'rgba(0,0,0,0)'
},
In case you have hex color, you can convert it to rgba and set the opacity there:
const hexToRgbA = (hex, opacity) => {
let c;
if (/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)) {
c = hex.substring(1).split('');
if (c.length === 3) {
c = [c[0], c[0], c[1], c[1], c[2], c[2]];
}
c = `0x${c.join('')}`;
return `rgba(${[(c >> 16) & 255, (c >> 8) & 255, c & 255].join(',')},${opacity})`;
}
throw new Error('Bad Hex');
};
const color = '#1f8b7f'; // could be a variable
return (
<View style={{ backgroundColor: hexToRgbA(color, 0.1) }} />
)
source that helped me
This will do the trick help you,
Add one View element and add style as below to that view
.opaque{
position:'absolute',
backgroundColor: 'black',
opacity: 0.7,
zIndex:0
}
The best way to use background is hex code #rrggbbaa but it should be in hex.
Eg: 50% opacity means 256/2 =128, then convert that value(128) in HEX that will be 80,use #00000080 80 here means 50% transparent.
Here is my solution to a modal that can be rendered on any screen and initialized in App.tsx
ModalComponent.tsx
import React, { Component } from 'react';
import { Modal, Text, TouchableHighlight, View, StyleSheet, Platform } from 'react-native';
import EventEmitter from 'events';
// I keep localization files for strings and device metrics like height and width which are used for styling
import strings from '../../config/strings';
import metrics from '../../config/metrics';
const emitter = new EventEmitter();
export const _modalEmitter = emitter
export class ModalView extends Component {
state: {
modalVisible: boolean,
text: string,
callbackSubmit: any,
callbackCancel: any,
animation: any
}
constructor(props) {
super(props)
this.state = {
modalVisible: false,
text: "",
callbackSubmit: (() => {}),
callbackCancel: (() => {}),
animation: new Animated.Value(0)
}
}
componentDidMount() {
_modalEmitter.addListener(strings.modalOpen, (event) => {
var state = {
modalVisible: true,
text: event.text,
callbackSubmit: event.onSubmit,
callbackCancel: event.onClose,
animation: new Animated.Value(0)
}
this.setState(state)
})
_modalEmitter.addListener(strings.modalClose, (event) => {
var state = {
modalVisible: false,
text: "",
callbackSubmit: (() => {}),
callbackCancel: (() => {}),
animation: new Animated.Value(0)
}
this.setState(state)
})
}
componentWillUnmount() {
var state = {
modalVisible: false,
text: "",
callbackSubmit: (() => {}),
callbackCancel: (() => {})
}
this.setState(state)
}
closeModal = () => {
_modalEmitter.emit(strings.modalClose)
}
startAnimation=()=>{
Animated.timing(this.state.animation, {
toValue : 0.5,
duration : 500
}).start()
}
body = () => {
const animatedOpacity ={
opacity : this.state.animation
}
this.startAnimation()
return (
<View style={{ height: 0 }}>
<Modal
animationType="fade"
transparent={true}
visible={this.state.modalVisible}>
// render a transparent gray background over the whole screen and animate it to fade in, touchable opacity to close modal on click out
<Animated.View style={[styles.modalBackground, animatedOpacity]} >
<TouchableOpacity onPress={() => this.closeModal()} activeOpacity={1} style={[styles.modalBackground, {opacity: 1} ]} >
</TouchableOpacity>
</Animated.View>
// render an absolutely positioned modal component over that background
<View style={styles.modalContent}>
<View key="text_container">
<Text>{this.state.text}?</Text>
</View>
<View key="options_container">
// keep in mind the content styling is very minimal for this example, you can put in your own component here or style and make it behave as you wish
<TouchableOpacity
onPress={() => {
this.state.callbackSubmit();
}}>
<Text>Confirm</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() => {
this.state.callbackCancel();
}}>
<Text>Cancel</Text>
</TouchableOpacity>
</View>
</View>
</Modal>
</View>
);
}
render() {
return this.body()
}
}
// to center the modal on your screen
// top: metrics.DEVICE_HEIGHT/2 positions the top of the modal at the center of your screen
// however you wanna consider your modal's height and subtract half of that so that the
// center of the modal is centered not the top, additionally for 'ios' taking into consideration
// the 20px top bunny ears offset hence - (Platform.OS == 'ios'? 120 : 100)
// where 100 is half of the modal's height of 200
const styles = StyleSheet.create({
modalBackground: {
height: '100%',
width: '100%',
backgroundColor: 'gray',
zIndex: -1
},
modalContent: {
position: 'absolute',
alignSelf: 'center',
zIndex: 1,
top: metrics.DEVICE_HEIGHT/2 - (Platform.OS == 'ios'? 120 : 100),
justifyContent: 'center',
alignItems: 'center',
display: 'flex',
height: 200,
width: '80%',
borderRadius: 27,
backgroundColor: 'white',
opacity: 1
},
})
App.tsx render and import
import { ModalView } from './{your_path}/ModalComponent';
render() {
return (
<React.Fragment>
<StatusBar barStyle={'dark-content'} />
<AppRouter />
<ModalView />
</React.Fragment>
)
}
and to use it from any component
SomeComponent.tsx
import { _modalEmitter } from './{your_path}/ModalComponent'
// Some functions within your component
showModal(modalText, callbackOnSubmit, callbackOnClose) {
_modalEmitter.emit(strings.modalOpen, { text: modalText, onSubmit: callbackOnSubmit.bind(this), onClose: callbackOnClose.bind(this) })
}
closeModal() {
_modalEmitter.emit(strings.modalClose)
}
Hope I was able to help some of you, I used a very similar structure for in-app notifications
Happy coding