how to instantly change image on button click without rerendering component - react-native

i have 3 images and on button click I am changing those one by one but after I click the button a white screen is shown than the new image is shown how can I instantly change images without any delay. you can check out my output
video
my code:
export const Start = ({ navigation }) => {
const [image, setImage] = useState(images.start_1)
const [text, setText] = useState("Discover the\nmost beautiful\nroads in the\nworld")
const [count, setCount] = useState(1)
useEffect(() => {
if (count == 2) {
setImage(images.start_2)
setText("Make a road trip\nthrough fantastic\nlandscapes.")
}
else if (count == 3) {
setImage(images.start_3)
setText("Share your\nunique experiences\nwith others.")
}
else if (count == 4) {
navigation.replace('home')
}
}, [count])
return (
<ImageBackground
resizeMode='stretch'
source={image}
defaultSource={images.start_1}
style={{ flex: 1 }}>
<Text style={{
fontSize: 30,
color: 'white',
position: 'absolute',
bottom: 120,
left: 20
}}>{text}
</Text>
<TouchableOpacity
activeOpacity={0.8}
onPress={() => setCount(count + 1)}
style={{
position: 'absolute',
bottom: 55,
right: 20
}}>
<ImageBackground
resizeMode='cover'
source={images.button}
style={{
paddingVertical: 5,
paddingHorizontal: 40,
justifyContent: 'center',
alignItems: 'center'
}}
imageStyle={{ borderRadius: 20 }}>
<Text style={{ fontSize: 20, color: 'white' }}>Start</Text>
</ImageBackground>
</TouchableOpacity>
</ImageBackground>
)
}

If you are using an image from over the network, I'd probably recommend against using ImageBackground and instead try to use React Native's Image component or React Native Fast Image. Both Image and FastImage allow you to prefetch specific images - so they are in the cache before being used/shown to a user.
Alternatively, you could try embedding the images in the app/bundle and see if that gives you the same loading delay/flash.

Related

Why do I see display lag when I press a button or pressable to change two images?

https://youtu.be/DlT8yiPgEvQ
I'm new to expo and react native and beginning to grasp conditional rendering. The behavior demonstrated in the video clip, however, is extremely frustrating. Why is react native showing this kind of behavior? I am currently making an expo test app and testing it on my macbook pro with an ios simulator. The problem I have is that a conditionally rendered element, such as image, lags before rendering completely. As you can see in the video clip, The image wrapped in pressable should change both its source and position in the screen as soon as the state variable changes its value. However, it seems that it first displays the new pressable image at its original position and then moves the image according to top: decimal (also displaying the entire position-shifting process). What I want is the entire image to be displayed only after its position is adjusted as well. I don't want to see the display lag every time I press a button to change an image. I highly appreciate your insight, and below is my source code.
import { StatusBar } from 'expo-status-bar';
import { Pressable, StyleSheet, Text, TextInput, View, ScrollView, Image, Dimensions } from 'react-native';
import Constants from 'expo-constants';
import { Card } from 'react-native-paper';
import login from './login-screen.png';
import loginButton from './login-screen-button.png';
import loginButtonPressed from './loginButton2.png';
import ID_selected from './ID_selected.png';
import {useState} from 'react';
//Iphone 12 pro max resolution: 1284 x 2778 pixels
export default function App() {
const win = Dimensions.get('window');
const ratio = win.width / 428;
const [text, onChangeText] = useState();
const [isShowingImageID, setShowingImageID] = useState(false);
const [isShowingImagePW, setShowingImagePW] = useState(false);
const [pressed, onPressFunction] = useState(false);
console.log("Window.width: ", win.width);
console.log("Windows.height: ", win.height);
return (
<View style={styles.container}>
{/* style={{
flexGrow: 1,
}} */}
{/*428*/}
{/*926*/}
<Image
source={login}
style={{
width: 428, //428
height: 926, //926
//aspectRatio: 1,
}}
/>
<Pressable
onPressIn={() => onPressFunction(true)}
onPressOut={() => onPressFunction(false)}
style={{
position: "absolute"
//aspectRatio: 1,
}}
>
{
pressed ?
(
<Image
source={loginButtonPressed}
style={{
width: 428, //428
height: 926, //926
top: 50,
position: "relative"
}}
/>
) :
(
<Image
source={loginButton}
style={{
width: 428, //428
height: 926, //926
//aspectRatio: 1,
position: "relative"
}}
/>
)
}
</Pressable>
{
isShowingImageID ?
(
<Image
source={ID_selected}
//1184 x 140
style={{
width: 1184 / 3,
height: 140 / 3,
//aspectRatio: 1,
position: "absolute",
top: 295
}}
/>
) : (null)
}
{
isShowingImagePW ?
(
<Image
source={ID_selected}
//1184 x 140
style={{
width: 1184 / 3,
height: 140 / 3,
//aspectRatio: 1,
position: "absolute",
top: 338
}}
/>
) : (null)
}
<TextInput
style={textInputStyleID.container}
onChangeText={text => onChangeText(text)}
onPressIn={touchEvt => setShowingImageID(true)}
onPressOut={touchEvt => setShowingImageID(false)}
//value={text} //Since onChangeText is used for both ID and PW, using either text input
//will constantly update text, which only has one instance. Thus, setting value
//to text will change both text inputs.
placeholder="아이디"
placeholderTextColor="#A9A9A9"
keyboardType="email-address"
/>
<TextInput
style={textInputStylePW.container}
onChangeText={text => onChangeText(text)}
onPressIn={touchEvt => setShowingImagePW(true)}
onPressOut={touchEvt => setShowingImagePW(false)}
//value={text}
placeholder="비밀번호"
placeholderTextColor="#A9A9A9"
keyboardType="default"
secureTextEntry={true}
/>
<StatusBar style="auto" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
position: 'absolute'
},
});
const textInputStyleID = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
position: 'absolute',
top: 303,
left: 42,
width: 350,
height: 30
}
});
const textInputStylePW = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
position: 'absolute',
top: 346,
left: 42,
width: 350,
height: 30
}
});
Rather than this
<Pressable
onPressIn={() => onPressFunction(true)}
onPressOut={() => onPressFunction(false)}
style={{
position: "absolute"
//aspectRatio: 1,
}}
>
can you try this using just onPress?
<Pressable
onPress={() => onPressFunction(true)}
style={{
position: "absolute"
//aspectRatio: 1,
}}
>
Hope it helps. feel free for doubts

React native animation progress bar with image

Today i want to make a progress bar with a image on the left. Unfortunately I can't position the image in the right place. For now it looks like that
I want to looks something like that -
My code so far -
<View
style={{
flexDirection: "row",
alignSelf: "center",
marginBottom: "20%",
marginTop: "10%",
}}
>
<View
style={{
width: "90%",
height: 30,
padding: 2.5,
backgroundColor: "#00000020",
borderRadius: 30,
}}
>
{/* <Animated.View
style={[
{
width: "100%",
height: 25,
borderRadius: 15,
backgroundColor: "#f5de41",
},
{
width: progressAnim,
},
]}
></Animated.View> */}
<Image
source={require("./assets/lion.png")}
style={{
height: 44,
height: 44,
position: "absolute",
}}
resizeMode="contain"
/>
</View>
</View>
I tried to add left: '-62%' in style of iamge but it not works. I am not sure how to move the lion to the left?
One approach would be to remove the absolute position and use flexbox to align the the image to end of the row:
const ProgressBar = ({imgSource,imgStyle,imgSize,style,progress,color})=>{
let loadingAnim = useRef(new Animated.Value(progress)).current;
const [{width,height},setViewDimensions] = useState({});
// get parent view size
const onLayout=({nativeEvent})=>{
setViewDimensions({
width:nativeEvent.layout.width,
height:nativeEvent.layout.height
})
}
const animatedWidth =loadingAnim.interpolate({
inputRange:[0,1],
outputRange:[0,width-imgSize],
extrapolate:'clamp'
})
const containerAnimation = {
margin:0,
padding:0,
width:Animated.add(animatedWidth,imgSize),
backgroundColor:color,
height:'100%',
justifyContent:'center',
overflow:'hidden'
}
useEffect(()=>{
Animated.timing(loadingAnim,{
toValue:progress,
duration:100
}).start()
},[progress])
return(
<View style={[styles.loadingContainer,{height:imgSize*1.5||null},style]} onLayout={onLayout}>
<Animated.View style={[styles.loadingContainer,containerAnimation]}>
<Animated.Image
source={imgSource}
style={[{height:imgSize,width:imgSize,alignSelf:'flex-end'},imgStyle,{}]}
/>
</Animated.View>
</View>
)
}
I found this approach to be slightly un-smooth. I think this is because the width of image's parent view was being animated, and not the actual position of the image.
Another approach would be to animate the image:
const ProgressBar = ({imgSource,imgStyle,imgSize,style,progress,color})=>{
let widthAnim = useRef(new Animated.Value(progress)).current;
const [{width,height},setViewDimensions] = useState({});
// get parent view width to determine progress view size
const onLayout=({nativeEvent})=>{
setViewDimensions({
width:nativeEvent.layout.width,
height:nativeEvent.layout.height
})
}
const animatedWidth = widthAnim.interpolate({
inputRange:[0,1],
outputRange:[0,width-imgSize],
extrapolate:'clamp'
})
const containerAnimation = {
// min width will be imgSize
width:Animated.add(animatedWidth,imgSize),
backgroundColor:color,
}
const imgAnimation = {
left:animatedWidth
}
// animate progress changess
useEffect(()=>{
Animated.timing(widthAnim,{
toValue:progress,
duration:100
}).start()
},[progress])
return(
<View>
<View style={[styles.loadingContainer,{height:imgSize*1.25||null},style]} onLayout={onLayout}>
<Animated.View style={[styles.progressBar,containerAnimation]}/>
</View>
<Animated.Image
source={imgSource}
style={[styles.image,{height:imgSize,width:imgSize},imgStyle,imgAnimation]}
resizeMode='contain'
/>
</View>
)
}

Touchable Opacity messes up width in row container

I'm trying to make these two Card components appear next to each other in a row as shown
here which seems to work when I wrap the component in a View, but appears like this with a bunch of unnecessary space in between when I try it with a TouchableOpacity.
Here is my code for the Card component (currently with TouchableOpacity on and the View wrapper commented out):
export const NavCard = ({
title,
height = 200,
onPress = null,
background = null,
}) => {
return (
<TouchableOpacity
onPress={onPress}
style={[
{ height: height },
background ? styles.cardImage : styles.noImage,
]}
>
{/* <View
style={[
{ height: height },
background ? styles.cardImage : styles.noImage,
]}
> */}
<Image
source={background}
resizeMode="cover"
style={{
height: height,
width: "100%",
borderRadius: 15,
position: "absolute",
top: 0,
right: 0,
}}
/>
<View style={{ padding: 15 }}>
<Text style={styles.title}>{title}</Text>
<Image
style={styles.arrow}
source={require("../assets/arrow-right.png")}
/>
</View>
{/* </View> */}
</TouchableOpacity>
);
};
const styles = StyleSheet.create({
cardImage: {
flexGrow: 1,
margin: "2%",
borderRadius: 15,
},
noImage: {
flexGrow: 1,
margin: "2%",
borderRadius: 15,
backgroundColor: "#333436",
},
title: {
fontSize: 24,
color: "#E4E5EA",
fontWeight: "bold",
shadowColor: "#000000",
shadowOffset: { width: 2, height: 2 },
shadowOpacity: 1,
shadowRadius: 4,
},
arrow: {
width: 15,
height: 15,
resizeMode: "contain",
position: "absolute",
top: 22,
right: 20,
},
});
Here is the code for the screen:
const HomeScreen = ({ navigation }) => {
return (
<View style={styles.container}>
<View style={styles.rowContainer}>
<NavCard
title="Map"
height={180}
onPress={() => navigation.navigate("Map")}
background={require("../assets/pvdx1.png")}
></NavCard>
<NavCard
title="CAD"
height={180}
background={require("../assets/pvdx1.png")}
onPress={() => navigation.navigate("CADScreen")}
></NavCard>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
paddingTop: 10,
padding: 4,
flex: 1,
},
rowContainer: {
flexDirection: "row",
justifyContent: "space-between",
},
body: {
paddingTop: 10,
fontSize: 16,
color: "#E4E5EA",
},
});
export default HomeScreen
Does anyone know why it's messing up the width of both components if the styles of the View and TouchableOpacity are exactly the same? I'm using React Native.
Edit: Have updated to use Image instead of ImageBackground (code now reflects this), but the result is the same.
I figured out the issue: I was importing TouchableOpacity from react-native-gesture-handler instead of react-native. Changed it and it works just fine.
Probably the problem is in your <ImageBackground /> , Because I replaced that with native <Image /> and it's working. here is how my Image looks, you can compare with your code:
<Image
style={{
height: height,
borderRadius: 15,
position: "absolute",
top: 0,
right: 0,
width: "100%"
}}
resizeMode="cover"
source={{ uri: "https://via.placeholder.com/250x150" }}
/>
Here you can check the working code
for me i had to also set the width on the TouchableOpacity (and the view within)
<TouchableOpacity
style={{flex: 1, width: '100%'}}

How to achieve drop-shadow with no blur in React Native

I'm new using React Native and I'm trying to map the following component (made in web) but for React Native with no success:
Elevation and shadow properties does not do the trick because they add some blur to the resulting shadow. Which would be the proper way to handle this?
Regards
Use react-native-cardview
import React, { Component } from 'react';
import {
View,
ScrollView,
TextInput,
} from 'react-native';
import CardView from 'react-native-cardview';
import styles from './styles';
export default class Signup extends Component {
render() {
return (
<View style={{ flex: 1, backgroundColor: colors.whiteColor }}>
<ScrollView contentContainerStyle={styles.signupContainer}>
<View style={styles.signupInputs}>
<CardView
style={styles.cardStyle}
cardElevation={2}
cardMaxElevation={2}
cornerRadius={5}
>
<TextInput
underlineColorAndroid="transparent"
style={[styles.signupInput, styles.commonsignupStyle]}
placeholder="Nom *"
placeholderTextColor={colors.primaryColor}
/>
</CardView>
<CardView
style={styles.cardStyle}
cardElevation={2}
cardMaxElevation={2}
cornerRadius={5}
>
<TextInput
underlineColorAndroid="transparent"
style={[styles.signupInput, styles.commonsignupStyle]}
placeholder="Prénom *"
placeholderTextColor={colors.primaryColor}
/>
</CardView>
</View>
</ScrollView>
</View>
);
}
}
Edit:
For dynamic height, two lines or more of text, as asked for in the comments, I had to use another workaround.
https://snack.expo.io/7bVXvbmE0
const Label = () => {
return <View style={{width: 100, height: 50}}>
<View style={styles.topView}>
<Text>Hello world</Text>
<Text>Hi world</Text>
</View>
<View style={styles.shadowView} >
<Text style={{color: 'transparent'}}>Hello world</Text>
<Text style={{color: 'transparent'}}>Hi world</Text>
</View>
</View>;
}
Whatever dynamic text you have on the label, duplicate for the shadow label, but make it transparent. That way you are guaranteed that the shadow follows the top view.
Also, get rid of the hardcoded heights in the styles. For both top view and shadow view, their heights are informed by the text input, and the wrapper container's height is informed by the two views.
Lastly, change shadow view style's top to be just a few points above 0 to make sure you it peeks from under topview. You can adjust borderRadius of the shadow view to fit your preferences.
const styles = StyleSheet.create({
topView: {
width: '100%',
position: 'absolute',
top: 0, backgroundColor: 'white',
justifyContent: 'center',
alignItems: 'center',
borderRadius: 25,
},
shadowView: {
position: 'absolute',
top: 3,
width: '100%',
zIndex: -10,
borderRadius: 17,
backgroundColor: '#ddd'}
});
Previous Post
A little bit hacky, but you can do this if you absolutely don't want any blur.
https://snack.expo.io/pWyPplcm3
const Label = () => {
return <View style={{width: 100, height: 30}}>
<View style={styles.topView}>
<Text>Hello world</Text>
</View>
<View style={styles.shadowView} />
</View>;
}
styles:
const styles = StyleSheet.create({
topView: {
height: 25,
width: '100%',
position: 'absolute',
top: 0, backgroundColor: 'white',
justifyContent: 'center',
alignItems: 'center',
borderRadius: 15,
},
shadowView: {
position: 'absolute',
top: 0,
height: 28,
width: '100%',
zIndex: -10,
borderRadius: 13,
backgroundColor: '#ddd'}
});

React Native Button onPressIn animation request

I want to achieve an animation each time a user taps on a button this shrinks in a smaller button.
GIF HERE
For this use TouchableHighlight component from react-native. It has onPressIn and onPressOut on which you can change buttons width and height.
e.g.
export const TouchableHighlightExample = () => {
const [BtnSize, setBtnSize ] = useState({ height: 40, width: "100%" });
const zoomIn=()=>{
setBtnSize({ height: 35, width: "90%",marginHorizontal:"5%" })
}
const zoomOut=()=>{
setBtnSize({ height: 40, width: "100%" })
}
return (
<View style={styles.container}>
<TouchableHighlight underlayColor="#ffffff00" onPressIn={zoomIn} onPressOut={zoomOut}>
<View style={[styles.button,BtnSize]}>
<Text style={{color: "white"}}>Touch Here</Text>
</View>
</TouchableHighlight>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingHorizontal: 10,
},
button: {
alignItems: 'center',
backgroundColor: 'red',
padding: 10,
borderRadius:40
},
});