How to add smooth transitions to the React Native elements - javascript

I know there is Animated API for React Native but as far as I've seen it's very complicated than what I want to implement in this particular situation.
Now I have a button that has a changing text state inside while scrolling to the page. The texts' length differ so button has to stretch or shrink. But of course this happens instantly, I want it to stretch or shrink smoothly. In web, we could easily do something like this in CSS ;
transition: all 0.3s ease;
When you don't want to add extra animations this can be enough. So what is the equvialent of this behaviour in React Native?

The equivalent to CSS transitions is LayoutAnimation from React Native. Before you make a state change regarding the button, you configure the LayoutAnimation. Then the next state update will be animated at that position. This can look like this:
export default function App() {
const [text, setText] = React.useState("This is an example");
React.useEffect(() => {
setTimeout(() => {
LayoutAnimation.spring();
setText("This is an another example");
}, 1500);
}, [])
return (
<View style={styles.container}>
<View style={{backgroundColor: 'red', padding: 20}}>
<Text>{text}</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
}
});
Prerequisite is always that it goes via a state change. Just play around with a sample View Box and give the width and height over the useState hook. If you configure your LayoutAnimation before you write setWidth, then the component with the state will be animated automatically.

Related

Keyboard covers input text (one long vertical input), text also runs off the screen without actively being scrolled to (react native)

It's been 7 hours, I've read all the docs and similar SO questions, so any help would be appreciated.
My goal: have an app screen where the top 1/3 of the screen is made up of custom components (not the problem), and then the bottom 2/3 of the screen is a giant full-width super-long scrollable textfield for the user to enter several paragraphs (the problem)
The problem: Either the on-screen keyboard covers the text from the textfield, the textfield isn't scrollable, or when typing and you go off the bottom of the screen you aren't scrolled along with what you're typing - or a mix of all the 3.
Here is a rough image of what I desire:
Currently, my code looks like this:
export default function Post() {
const [text, setText] = useState("");
return (
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<SafeAreaView >
<KeyboardAvoidingView enabled behavior='height' keyboardVerticalOffset={40} >
<View>
<TextInput scrollEnabled={true} placeholderTextColor={LIGHT} maxLength={300000} placeholder="enter text" multiline={true} onChangeText={(newVal) => setText(prev => {prev + newVal})} value={text}/>
</View>
</KeyboardAvoidingView>
</SafeAreaView>
</TouchableWithoutFeedback>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: WHITE,
alignItems: 'center',
justifyContent: 'flex-center',
alignItems: "center",
marginHorizontal: 15,
// height: "100%",
},
textfield: {
textAlign: "left",
textAlignVertical: "top",
backgroundColor: "lavender",
height: "100%",
flex: 1,
},
scroll: {
// width: "100%"
}
});
Couple of issues with your approach and code in general.
textfield isn't scrollable
Placing anything scrollable inside a touchable is really weird and rarely behaves as you expect it, so avoid if possible, or if you really need it then pass a onStartShouldSetResponder={() => true} to your inner scroll view. But in your case, I suspect you don't actually want the keyboard to close if the user clicks inside the textInput, do you? He probably wants to add a word to a previous sentence or fix a type. Instead I suggest catching the touch outside of the textInput only, e.g. in your top third. Something like this (scrabble snack) should work:
import React, { useState } from 'react';
import { Text, View, StyleSheet, Touchable, TouchableWithoutFeedback, SafeAreaView, KeyboardAvoidingView, TextInput, Keyboard } from 'react-native';
export default function App() {
const [text, setText] = useState("");
return (
<KeyboardAvoidingView style={{flex: 1, marginTop: 45}} behavior="padding">
<View style={styles.upperContainer} onTouchStart={Keyboard.dismiss}>
<Text>My content in top third </Text>
</View>
<View style={styles.lowerContainer}>
<TextInput
scrollEnabled={true}
placeholderTextColor={"lightgrey"}
maxLength={300000}
placeholder="enter text"
multiline={true}
onChangeText={(newVal) => setText(prev => {prev + newVal})}
value={text}
/>
</View>
</KeyboardAvoidingView>
);
}
const styles = StyleSheet.create({
upperContainer: {
flex: 1,
borderWidth: 2,
borderColor: "grey",
alignItems: "center",
justifyContent: "center"
},
lowerContainer: {
flex: 2,
borderColor: "black",
borderWidth: 2,
padding: 10
}
})
Notice how I pass flex 1 and 2 to the two child views to style them how you required it. The border is just added for better visibility.

Image in custom header with react-navigation is flickering / blinking on navigating

I have my own HeaderComponent with an image:
const CustomHeaderLogo = () => {
return (
<View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
<Image
source={require("../assets/logo.png")}
style={{ width: 160, height: 30 }}
/>
</View>
);
};
export default CustomHeaderLogo;
which I use in navigationOptions in stackNavigator on all my screens like this:
navigationOptions: {
headerTitle: () => <HeaderLogo />
}
It works but the problem is, that the image is flickering / blinking when navigating from screen to screen. That's especially noticeable on iOS.
Is there any way to prevent the image from flickering?
EDIT: Need to edit my question since I now know where the flickering is coming from.
It is the animation effect in the header. I still don't know how to disable it since animationEnabled setting to false doesn't change anything. https://reactnavigation.org/docs/4.x/stack-navigator#animationenabled

React Native : Subviews of KeyboardAvoidingView have lag / move at different speeds

I would like them all the subviews to move in one clean motion, but if you look at the gif, you can see that the Text subview and the TextInput subview overlap and move at different speeds. It looks like the Text subview adjusts its position instantly where as the button and TextInput subviews adjust their position in more of an Ease in Ease out manner.
Main exported component
class SearchScreen extends React.Component {
state = {search:""}
render(){
getArguments = {
search: this.state.search
}
return (
<KeyboardAvoidingView behavior="padding" style={styles.container}>
<Text style={styles.searchTitle}>Search for a Movie in the OMDB Database</Text>
<TextInput style={styles.searchField} onChangeText={text => this.setState({search:text})} ></TextInput>
<SearchButton navigation = {this.props.navigation} getArguments={getArguments}/>
</KeyboardAvoidingView>
)
}
}
Styling
const styles = StyleSheet.create({
container: {
flex:1,
backgroundColor: '#C8FEFE',
alignItems: 'center',
justifyContent: 'center'
},
searchButton: {
marginTop: 20,
backgroundColor: '#24D9E8',
borderRadius: 5,
padding: 5
},
searchField: {
backgroundColor: '#FFF',
textAlign: 'center',
width: 200,
borderRadius: 5,
margin: 20,
height: 30
},
searchTitle:{
fontWeight: 'bold',
fontSize: 20,
textAlign:'center'
}
});
Github
Full project on github
Solution 1: Quick fix for iOS
You can wrap your elements in a View, which will make them react to keyboard the way you want:
// styles
contentContainer: {
alignItems: 'center',
}
// SearchScreen
<View style={styles.contentContainer}>
<Text style={styles.searchTitle}>Search for a Movie in the OMDB Database</Text>
<TextInput style={styles.searchField} onChangeText={text => this.setState({search:text})} ></TextInput>
<SearchButton navigation = {this.props.navigation} getArguments={getArguments}/>
</View>
However, this will only work on iOS. Keyboard works slightly differently on Android. So solution 2 is a more solid way to do things.
Solution 2: Animations
Keyboard avoidance is quite tricky, Spencer Carli's article that Dominik referred to is a great resource for solutions using KeyboardAvoidingView. Those usually give you what you need. But if you want a smooth, controlled transition between keyboard states, you should use animations. The flow would go like this:
Add Keyboard event listeners to your component (keyboardDidShow and keyboardDidHide)
Wrap the content you want to move in an Animated.View
on keyboardDidShow, animate the y position of the Animated.View by the offset you want. The event returned (of type KeyboardEvent), along with the Dimensions API have all the measurements you need. Tip: use an Animated.timing animation with bezier Easing to control how the view moves.
on keyboardDidHide, repeat the animation but in the opposite direction.
Hope this helps!
<KeyboardAvoidingView keyboardVerticalOffset={Platform.OS === 'ios' ? 40 : 0}>
...
<KeyboardAvoidingView/>
You can try adding some offset to the view.
I would definitely style it with flexbox, try styling your container like this:
(it should move them at once, as flexbox will fit them on current width and height)
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100vw;
height: 100vh;

React Native - Animate width shrink

In the header of my React Native app, I have a conditional icon and a Searchbar.
static navigationOptions = ({ navigation }) => {
const { params = {} } = navigation.state;
return {
headerTitle: (
<View
style={{
flex: 1,
backgroundColor: Platform.OS === 'ios' ? '#e54b4d' : '',
alignItems: 'center',
flexDirection: 'row',
paddingHorizontal: 10,
height: StatusBar.currentHeight,
}}>
{params.isIconTriggered && <Icon name="chevron-left" size={28} />}
<SearchBar
round
platform={'default'}
placeholder="Search"
containerStyle={{
flex: 1,
backgroundColor: 'transparent',
}}
/>
</View>
),
headerStyle: {
backgroundColor: '#e54b4d',
},
};
};
Normally the Searchbar will take the full width of the header which is what I want. If the condition isIconTriggered is true, an icon will appear in front of the Searchbar and the width of the SearchBar will shrink enough so that the icon is visible next to it.
However, there is no transition or animation when this happens and it does not feel nor look nice. I would like to add an animation to the Searchbar so the width shrinks gradually and smoothly when the condition is triggered and the icon appears.
Is that possible to achieve and how can I achieve this?
Try to learn Animated API of react native.
Here is how i done it with button trigger.
import React, {Component} from 'react';
import {StyleSheet, View, TextInput , Button, SafeAreaView, Animated} from 'react-native';
import FA from 'react-native-vector-icons/FontAwesome5'
const AnimatedIcon = Animated.createAnimatedComponent(FA)
// make your icon animatable using createAnimatedComponent method
export default class Application extends Component {
animVal = new Animated.Value(0);
// initialize animated value to use for animation, whereas initial value is zero
interpolateIcon = this.animVal.interpolate({inputRange:[0,1], outputRange:[0,1]})
interpolateBar = this.animVal.interpolate({inputRange:[0,1],outputRange:['100%','90%']})
// initialize interpolation to control the output value that will be passed on styles
// since we will animate both search bar and icon. we need to initialize both
// on icon we will animate the scale whereas outputRange starts at 0 end in 1
// on search bar we will animate width. whereas outputRange starts at 100% end in 90%
animatedTransition = Animated.spring(this.animVal,{toValue:1})
// we use spring to make the animation bouncy . and it will animate to Value 1
clickAnimate = () => {
this.animatedTransition.start()
}
// button trigger for animation
//Components that will use on Animation must be Animated eg. Animted.View
render() {
return (
<SafeAreaView>
<View style={styles.container}>
<View style={styles.search}>
{/* our icon */}
<Animated.View style={{width: this.interpolateBar}}>
<TextInput placeholder='search here' style={styles.input}/>
</Animated.View>
<AnimatedIcon name='search' size={28} style={{paddingLeft: 10,paddingRight:10, transform:[{scale: this.interpolateIcon}]}}/>
</View>
<Button title='animate icon' onPress={this.clickAnimate}/>
</View>
</SafeAreaView>
);
}
}
const styles = StyleSheet.create({
container: {
backgroundColor:'#F79D42',
// flex: 1,
height:'100%',
paddingTop:20,
flexDirection: 'column',
// justifyContent: 'center',
alignItems:'center'
},
input:{
width: '100%',
height:40,
backgroundColor:'gray',
textAlign:'center'
},
search:{
flexDirection:'row-reverse',
width:'90%',
height:40,
alignItems:'center'
}
});
Solution using react-native-elements SearchBar component.
Wrapped the SearchBar Component inside Animated.View.
to explicitly animate the search bar
Like This:
<Animated.View style={{width: this.interpolateBar}}>
<SearchBar
placeholder="Type Here..."
containerStyle={{width: '100%'}}
/>
</Animated.View>
You can achieve this using Animated API of React Native.
You can check this tutorial for an overview of changing the size of elements with animation.
React-Native-Animatable is super cool!
Try this one out:
Create A custom animation object
import * as Animatable from 'react-native-animatable';
Animatable.initializeRegistryWithDefinitions({
const myAnimation = {
from: {
width: 200
},
to: {
width: 100
}
}
})
Use is as Animation value within a view or as a reference within a function call.
Within a view:
<Animatable.View useNativeDriver animation={myAnimation}/>
As a reference variable:
<Animatable.View useNativeDriver ref={ref=>(this.testAnimation = ref)}/>
Method:
testMethod = () => {
this.testAnimation.myAnimation();
}

How to put a sticky Touchable within an horizontal ScrollView in React Native?

I am trying to make a simple application/game to learn and practice React Native. Currently, I am trying to create a sticky TouchableNativeFeedback that moves with the screen as I am using the ScrollView.
The idea is that left half of the screen would move the character to the left and the right half of the screen would move it to the right. I want these controls to be fixed in the display and not move.
This is how it starts
This is after moving the scrollView a bit
I've initially tried to change value of style.left as I scrolled but that doesn't seem to be a good/stable solution.
Here is the current code:
render() {
return (
<ScrollView
//onScroll={this._onScroll}
ref={(scrollView) => { this.scrollView = scrollView; }}
style={styles.container}
horizontal= {true}
snapToAlignment={"center"}
>
<View style={styles.wrapperView}>
<ImageBackground
style={styles.container}
source={require('../images/second.png')}
>
<TouchableNativeFeedback onPressIn={this._onPressButton} onPressOut={this._onPressButton}>
<View style={
{
width: width/2,
height: '100%',
position: 'absolute',
left: this.state.touchableLeft,
backgroundColor: 'red',
}
}>
</View>
</TouchableNativeFeedback>
... (code about the character)
</ImageBackground>
</View>
</ScrollView>
);
}
and the styles code
const styles = StyleSheet.create({
container: {
width: '100%',
height: '100%',
},
wrapperView:{
width: width*3 + 300,
},
});
and just to have it as a reference, this is what I originally tried:
_onScroll = (event) => {
this.setState( {
touchableLeft: this.state.touchableLeft + event.nativeEvent.contentOffset.x
} )
}
I've looked at the following questions and articles but I couldn't really get to a solution that would help me. Usually people use flex to make their headers sticky above a ScrollView and that is incredibly handy but in this situation I am unsure about how to continue. Articles/Questions:
How to Get ListView Section Header to Stick
http://docs.nativebase.io/docs/examples/StickyHeaderExample.html
Sticky Component inside scrollview
What solved my problem was to take the TouchableNativeFeedback outside of the class. The class in the question was called Background and it was rendered in the class called App.
<View style={styles.container}>
<Background />
<TouchableNativeFeedback onPressIn={this._onPressButton} onPressOut={this._onPressButton}>
<View style={
{
width: '50%',
height: '100%',
position: 'absolute',
left:0,
//left: this.state.touchableLeft,
//backgroundColor: 'red',
}
}>
</View>
</TouchableNativeFeedback>
</View>
As you can see once I moved it to here, it was positioned right where I wanted, even if I move the screeen. What would be good practice, is to take it to another component class and then just call an instance of it.
Thanks to John Ruddell for the help while coming up with the solution

Categories

Resources