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
Related
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.
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;
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();
}
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
I'm embedding YouTube videos in a React Native app and when that component mounts, YouTube opens a full screen video to play. That part is great. I want a full screen video.
The problem is that it puts it on top of every other single React Native component. I can't get any interaction feedback from the user. Even when I put a component at the top-most level with the highest z-index possible, the YouTube video still covers it.
I'd like the user to be able to swipe left or right to go to another video (I have the swiping part figured out, though it's not shown here). Is this even possible? How can I do this?
[Edit: Also, does anyone have any resources about how YouTube can put itself in front of all other components, including its iframe's parents?]
The code:
import React from 'react';
import { StyleSheet, Text, View, WebView } from 'react-native';
export default class App extends React.Component {
render() {
return (
<View id={'webRoot'} style={styles.container}>
<WebView
ref={'webview'}
automaticallyAdjustContentInsets={false}
source={{uri: 'https://www.youtube.com/watch?v=43w7rcYPUnI'}}
javaScriptEnabled={true}
domStorageEnabled={true}
decelerationRate='normal'
startInLoadingState={true}
style={{zIndex: -1}} />
<Text style={{
position: 'absolute',
height: 300,
width: 300,
top: 0,
zIndex: 8999999999999999999
}}>
{"Won't be visible"}
</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});