I am trying to build a ReactNative Application with an animated button. The problem is that this animation does not work correctly the first time after the App is started. There is some white flickering. But after the animation ran wrong the first time everything works as expected:
I have already tried to preload the image in several ways, but without any success.
This is my minimal working example, note that if there are several different images the flickering occurs if a new image is loaded (e.g. I have two blue buttons and after I tapped the first one, the second one will work fine, but if I then tap an orange button it once again flickers for the first time, at least if I have not tapped another orange button after app start.):
import React, { Component } from 'react';
import {StyleSheet, Text, TouchableWithoutFeedback, View, Image, ScrollView,
Button, BackHandler} from 'react-native';
export default class Touchables extends Component {
constructor(props) {
super(props);
this.state = {alarm1: (<Image source={require("./assets/alarmoff.png")}
style={styles.imageButton}/>),
}
}
componentWillMount(){
//trying to preload all Images, but it does not help.
(<Image source={require("./assets/alarmon.png")} style=
{styles.imageButton}/>)
}
render() {
return (
<ScrollView style={styles.contentContainer}>
<View style={{flex: 3, flexDirection: 'row'}}>
<View style={styles.container}>
<TouchableWithoutFeedback onPressIn={() => this.setState({alarm1:
<Image source={require("./assets/alarmon.png")} style={styles.imageButton}/>})} onPressOut={() => this.setState({alarm1: <Image source={require("./assets/alarmoff.png")} style={styles.imageButton}/>})}>
<View style={styles.button}>
{this.state.alarm1}
</View>
</TouchableWithoutFeedback>
<Text style={styles.text}>This button flickers on first click. Restart App completly to see the issue. Reloading is not enough.</Text>
</View>
</View>
<View>
<Button
onPress={() => BackHandler.exitApp()}
title="Exit App"
color="#841584"
accessibilityLabel="Android only I guess."
/>
</View>
</ScrollView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 2,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
marginBottom: 30
},
button: {
backgroundColor: '#fff',
borderRadius: 20,
padding: 10,
marginBottom: 20,
shadowColor: '#303838',
shadowOffset: { width: 0, height: 5 },
shadowRadius: 10,
shadowOpacity: 0
},
contentContainer: {
paddingVertical: 20,
flex: 1,
backgroundColor: '#fff',
},
text:{
color: '#000',
marginBottom: 30
},
imageButton: {
flex: 1,
width: 240,
height: 200,
marginBottom: -15,
marginTop: 10,
resizeMode: 'cover'
}
});
So my question is how can I stop the image from flickering after app start?
The full version of the little demo app I have build to show my problem is available on my Github Repository
There may be a performance issue while loading different resolution images. You can use https://github.com/DylanVann/react-native-fast-image module to load images.
you can add and link it as below
# Install
yarn add react-native-fast-image
# Automatic linking. (other linking methods listed below)
react-native link react-native-fast-image
after that u can import it and use it using like below example
import FastImage from 'react-native-fast-image'
const YourImage = () =>
<FastImage
style={styles.image}
source={{
uri: 'https://unsplash.it/400/400?image=1',
headers:{ Authorization: 'someAuthToken' },
priority: FastImage.priority.normal,
}}
resizeMode={FastImage.resizeMode.contain}
/>
I copied this example from that repo. you can find documentation also there. Try it. It will increase image loading performance. Then most probably flickering issue will be resolved.
For me, it was causing flickering issues when I put the Image component in a FlatList ListHeaderComponent component. So,
Code causing flickering:
ListHeaderComponent={HeadComponent}
The HeadComponent was basically inside render and had the code const HeadComponent = () => { return (<Image...
Code that fixed flickering:
ListHeaderComponent={this.renderHeader}
The renderHeader is a function that returned the same thing as HeadComponent using code renderHeader () { return (<Image...
Hope this helps someone.
It was causing flickering issues when I put the Image component in a FlatList ListHeaderComponent component
In order to solve the issue I added useCallBack hook
const ListComponent = useCallBack(() => {
// your code
}, [])
ListHeaderComponent={ListComponent}
For me it solved the flickering issue
Well I have a workaround (sort of..).
In my componentDidMount() I do now set the button to its pressed state, wait for some time until the image is displayed and scaled, and then I set the state to off again, like so:
componentDidMount(){
this.setState({alarm1: <Image source={require("./assets/alarmon.png")} style={styles.imageButton}/>})
setTimeout(()=>{this.setState({alarm1: <Image source={require("./assets/alarmoff.png")} style={styles.imageButton}/>})}, 1000);
}
I tried to lower the timeout to less than a second, but then on my old (and slow) phone the flickering started again on first press after app load.
This obviously leads to the button state beeing changed after the app loaded, but if all buttons flicker once after app start, that is better than every button flickering on first press in my opinion.
I would however be glad if anybody could tell me the real way, how to resolve this.
For anyone who still have this problem : this is another way to fix
<Image
source={{ uri: your_path }}
defaultSource={{ uri: your_path }}
resizeMode="cover"
style={{width: 100,height: 100}} />
A stupid way:
<ImageBackground
defaultSource={require('./ui/pay0.png')}
source={require('./ui/pay0.png')}
style={{flex: 1, position: 'relative', zIndex: 9999, elevation: 5}}
/>
{/* Cache the remaining pictures to prevent flickering */}
<ImageBackground
source={require('./ui/pay1.png')}
style={{position: 'absolute', width: 1, height: 1}}
/>
If you are using expo, you can use Asset.loadAsync. See: https://docs.expo.io/versions/latest/sdk/asset.
In App.js, I like to wait for all static assets to be loaded before showing any screen.
Related
The KeyboardAvoidingView covers the TextInput UI while selecting emojis from the virtual keyboard.
Here is a sample code, KeyboardAvoidingView in this case works fine on entering the text from the virtual keyboard. however, on switching to the emoji picker in the virtual keyboard the automatically adjusted padding | height has no effect and the TextInput gets covered under the virtual keyboard.
import React from 'react';
import { View, KeyboardAvoidingView, TextInput, StyleSheet, Text,,TouchableWithoutFeedback, Keyboard } from 'react-native';
const KeyboardAvoidingComponent = () => {
return (
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
style={styles.container}
>
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View style={styles.inner}>
<Text style={styles.header}>Keyboard Avoiding View</Text>
...
<FlatList
...
<TextInput placeholder="Username" style={styles.textInput} />
</View>
</TouchableWithoutFeedback>
</KeyboardAvoidingView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1
},
inner: {
padding: 24,
flex: 1,
justifyContent: "space-around"
},
header: {
fontSize: 36,
marginBottom: 50
},
textInput: {
height: 40,
borderColor: "#000000",
borderBottomWidth: 1,
marginBottom: 36
},
});
export default KeyboardAvoidingComponent;
There seems to be a bug in the RN library itself. In our project, I just patched the library with the changes you can also find in the PR I submitted to RN.
https://github.com/facebook/react-native/pull/34749
This ensures that the height is calculated correctly when switching keyboards.
Here is my current solution for this problem:
on Android I set android.softwareKeyboardLayoutMode: "pan" in Expo app.json (docs: https://docs.expo.dev/versions/latest/config/app/#softwarekeyboardlayoutmode) and I dont use any keyboard-avoid solution at all. In my experience Android handles text Input just fine out of the box with this config.
on ios I use KeyboardAvoidingView with behavior: "padding", which works fine for all keyboards (including Emoji). Please beware of behavior: "height" - it is bugged at the moment in RN 0.7.5 (https://github.com/facebook/react-native/issues/35764)
Here is my code:
React.createElement(Platform.OS === 'ios' ? KeyboardAvoidingView : View, {
style: container,
behavior: Platform.OS === 'ios' ? "padding" : null,
},
... // children
)
Please also take a look at a great article explaining modern ways of keyboard avoiding for IOS, Android in different cases https://www.netguru.com/blog/avoid-keyboard-react-native
I'm looking for a way to tint the user's screen a certain shade, and change this tint overtime. (Think F.lux or Night Shift or any number of currently available blue light reducers). Is there any way to do this with React Native and/or Expo? I know iOS doesn't allow users to do this without Jailbreaking, but I believe this could be possible for Android at least? Thank you for your time.
App-wide filter solution (Android and web only, and the filter goes off if you switched to a different app)
You could make a filter with a <View/> React component that I named "filter" in the example below.
This does not work on iOS because I didn't find a way to let iOS know that all user clicks are for elements below the filter, not the filter itself.
In the example below, your whole app is represented by <YourApp/> which is in this example a simple button.
import React, { useState } from "react";
import { View, Button } from "react-native";
export default function () {
let [opacity] = useState(0.8);
const YourApp = () => (
<Button onPress={() => alert("clicked !!")} title="Click me" />
);
return (
<View
name="filterContainer"
style={{
flex: 1,
width: "100%",
height: "100%",
position: "absolute",
alignItems: "center",
justifyContent: "center",
}}
>
<View
name="filter"
style={{
flex: 1,
width: "100%",
height: "100%",
pointerEvents: "none",
position: "absolute",
elevation: 2,
zIndex: 2,
backgroundColor: "green",
opacity: opacity,
}}
></View>
<View
name="appContainer"
style={{
flex: 1,
position: "absolute",
elevation: 1,
zIndex: 1,
}}
>
<YourApp />
</View>
</View>
);
}
System-wide brightness change (but no tint change, and this is iOS and Android only)
If you're using Expo, you could use SYSTEM_BRIGHTNESS (https://docs.expo.io/versions/latest/sdk/permissions/#permissionssystem_brightness).
NB : The system brightness in most simulators and on web browsers does not work. It only works on real devices.
Here is an example how to set system brightness to 10% :
import * as Permissions from "expo-permissions";
import * as Brightness from "expo-brightness";
async function getAndSetSystemBrightnessAsync() {
const { status } = await Permissions.askAsync(Permissions.SYSTEM_BRIGHTNESS);
if (status === "granted") {
// Set system brightness to 10 %
await Brightness.setSystemBrightnessAsync(0.1);
const bright = await Brightness.getSystemBrightnessAsync();
console.log(bright);
} else {
// Web browsers
console.error("System brightness permission not granted");
}
}
useEffect(() => {
// Ask for system brightness permission
getAndSetSystemBrightnessAsync();
}, []);
I am trying to create a view post ui for a social media feature of our app. We have a post (the flatlist header component), the comments (the flatlist), and a fixed bottom text input that leverages zIndex to be placed over the flatlist to post comments. The problem is I cannot for the life of me figure out how to properly use a keyboard avoiding view to somehow push this fixed input up when the keyboard is shown. Is there any way to do this or maybe a simpler approach to this ui that I'm missing? I have tried using the keyboard height and adding it to the bottom positioning of the input but its slow and definitely isn't viable.
Code:
<View style={{flex: 1}}>
<View
style={{
...styles.container,
marginTop: 0,
zIndex: 1,
}}>
<FlatList
style={{minHeight: '100%'}}
data={new Array(15)}
renderItem={({item, index}) => <CommentRow index={index} />}
keyExtractor={(item, index) => `${index}`}
ListHeaderComponent={renderHeader}
contentContainerStyle={{paddingBottom: 70}}
/>
</View>
<View
style={{
position: 'absolute',
bottom: 0,
backgroundColor: 'white',
width: '100%',
zIndex: 2,
padding: 15,
borderTopWidth: 1,
borderColor: '#f2f2f2',
}}>
<View style={styles.inputContainer}>
<TextInput style={styles.input} placeholder="Add a comment.." />
<View style={styles.inputTextContainer}>
<Text style={styles.inputText}>Post</Text>
</View>
</View>
</View>
</View>
);
I'm not a fan of keyboard avoiding view because of some bad experiences I had. However, I'll give some tips on how to achieve the same effect with React Native core Animated Library.
Since your view is elevating (Style it as an absolute view) and when the keyboard appears, you can get the height of the keyboard with Keyboard addListener's callback.
componentDidMount(){
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow);
}
_keyboardDidShow(e) {
const keyboardHeight = e.endCoordinates.height,
}
Once you get the keyboard height, you can animate the absolute view using translateY to the position that aligns with the keyboard without any gaps. This will provide an nice animation similar to that you tried to accomplish with keyboard avoiding view. I've done that in few apps as well and it works great.
For anyone looking to do anything like this - what I ended up doing is using an useEffect hook to monitor when "keyboardWillShow" and then created a function to animate the view above the keyboard once its about to show. A lot of the answers I had seen used "keyboardDidShow" but it makes the UI slow and really ugly. Here's the code:
useEffect(() => {
Keyboard.addListener('keyboardWillShow', _keyboardWillShow);
Keyboard.addListener('keyboardWillHide', _keyboardWillHide);
// cleanup function
return () => {
Keyboard.removeListener('keyboardWillShow', _keyboardWillShow);
Keyboard.removeListener('keyboardWillHide', _keyboardWillHide);
};
}, []);
const _keyboardWillShow = (e) => {
const keyboardHeight = e.startCoordinates.height;
Animated.timing(topValue, {
toValue: -keyboardHeight,
duration: 50,
useNativeDriver: true,
}).start();
};
const _keyboardWillHide = (e) => {
Animated.timing(topValue, {
toValue: 0,
duration: 0,
useNativeDriver: true,
}).start();
};
Where topValue is a stateful Animated value. All you need to do after this is add the topValue variable to your animated view using transform and you're done!
I have a component that renders a WebView along with a Text component at the top and bottom of the screen:
return (
<Container>
<Text>TEXT TOP</Text>
<WebView source={{uri: url}} />
<Text>TEXT BOTTOM</Text>
</Container>
);
The issue that I am facing is that the "TEXT TOP" does not appear the first time I navigate to the specific component, while the "TEXT BOTTOM" appears just fine. In order to make the top text appear, I need to do one of the following:
Navigate to another component and come back
Change the phone's orientation
Interact with the WebView (scroll or press on an input field)
I have tried with both the WebView component from 'react-native' and also from 'react-native-webview' without managing to change the behaviour.
I have this example it renders both text on top and bottom along with webview in
middle.Hope it helps.Run this code on expo snack and view it on your device.
import * as React from 'react';
import { Text, View, StyleSheet, WebView } from 'react-native';
export default class App extends React.Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.paragraph}>
Change code in the editor and watch it change on your phone! Save to
get a shareable url.
</Text>
<WebView source={{ uri: 'https://github.com/facebook/react-native' }} />
<Text style={styles.paragraph}>
Change code in the editor and watch it change on your phone! Save to
get a shareable url.
</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
backgroundColor: '#ecf0f1',
padding: 8,
},
paragraph: {
margin: 24,
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
},
});
I had a similar issue. Any components above the WebView would not render and couldn't be forced to render until the WebView had finished loading.
I was able to work around this by having the WebView load a local html resource (source = require('xxx')). Then implement the WebView's onLoadEnd to flip a component state variable to load the real URI you want to show.
It seems that the layout of the screen is delayed or otherwise impeded by the first load of the WebView. I don't know if the first page being local causes the engine to compute the layout inline since it doesn't need to wait on an asynchronous fetch result.
Why the layout of the screen would need to change based on the contents of the WebView is unclear to me. Perhaps it just looks like that's what's happening.
Place all other content below the WebView and position using absolute.
I have been trying to resolve this issue for a while and found that this solution works like a charm!
<WebView source={{uri: url}} style={{ marginTop: 60 }} />
<Text style={{ position: 'absolute', top: 0, height: 60 }}>TEXT TOP</Text>
<Text>TEXT BOTTOM</Text>
I am working on a react native app which involves a video player (react-native-video), and some simple controls I set up myself. on iOS this works fine, but on Android the TouchableOpacity elements, which I use for controls and navigation, don't seem to detect touches. (Navigation is handles by react-native-fluid-transitions in my app). When I turn on the inspector, a screen-covering View seems to be on top of my controls. However, this is not the case on iOS and I have also not configured such a view.
I installed Atom to use it's inspector feature to see the actual order of my views. It looks as follows:
VideoView is the name of my component, Video is the actual video player and the TouchableOpacity I highlighted is the button I'm trying to get to work. In this view hierarchy, no views seem to be on top of anything. I have also compared this breakdown to other components where my buttons actually work and it looks the same.
My code looks as follows:
return (
<View style={internalStyles.container}>
<Video style={internalStyles.videoContainer}
ref={(ref) => {
this.props.player = ref
}}
source={{uri: url}}
controls={false}
onEnd={() => this.videoEnded()}
paused={this.state.paused}
muted={false}
repeat={false}
resizeMode={"contain"}
volume={1.0}
rate={1.0}
ignoreSilentSwitch={"obey"}
/>
{this.renderControls()}
{Renderer.getInstance().renderNavigationButton()}
</View>
);
where renderControls is a function that renders the pause button, and Renderer is a singleton component containing render function for items I use in more components of my app. This all works fine on iOS, but not on Android. react-native-video seems to be incompatible with react-native-fluid-transitions as everything works when I remove one of either.
Does anyone know what might cause this behavior? Any help would be highly appreciated.
Try removing the activeOpacity prop from TouchableOpacity component.
Or you can use platform specific code to set values for activeOpacity prop
import { Platform, TouchableOpacity } from 'react-native'
<TouchableOpacity
activeOpacity={Platform.OS==='android' ? 0 : 0.2}
>
<Text>submit</Text>
</TouchableOpacity>
Wrap the component in a view and disable pointer events.
<View pointerEvents="none">
<Video source={{ uri: source }} />
</View>
import {TouchableOpacity} from 'react-native';
<TouchableOpacity>some text</TouchableOpacity>
For me it was solved by putting zIndex:1000
<TouchableOpacity
onPress={this.handlePause}
style={{
position: "absolute",
alignItems: "center",
justifyContent: "center",
alignSelf: "center",
elevation: 2,
backgroundColor: "#FFF",
width: 60,
height: 60,
borderRadius: 30,
flex: 1,
zIndex: 1000,
}}
>