React Native - animate image height without scaling (reanimated v2) - react-native

I'm trying to create an effect in my React Native app whereby a pixelated version of an image grows in height over another image. I'm using react-native-reanimated v2. The issue I'm having is that I can't get the image to animate in height without the image scaling/re-positioning.
To illustrate the effect I'm currently getting, and what I want, see this image:
I've tried different resizeMethod inputs and the closest I've found is repeat however this performs inconsistently over different device sizes. Ideally I'd just want the image to not scale but that's likely just ignorance on my side in terms of understanding the way images are rendered.
Current code:
const AnimatedDemo = () => {
const overlayPicHeight = useSharedValue(0);
useEffect(() => {
overlayPicHeight.value = withTiming(100, {
duration: 5000,
});
}, []);
const animatedHeight = useAnimatedStyle(() => ({
height: `${overlayPicHeight.value}%`,
}));
return (
<View style={styles.container}>
<Animated.Image
source={require("./dog2.jpeg")}
style={[{ width: "100%", height: "100%" }]}
/>
<Animated.Image
source={require("./dog2-pixel.jpeg")}
style={[
{ width: "100%", position: "absolute", zIndex: 1 },
animatedHeight,
]}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
height: "90%",
alignItems: "center",
},
});
Note - if I can achieve the same kind of effect without needing to use the 2nd , I'm all ears, as this would likely resolve my issue too

Related

How to make expo-av video to take needed inside a flatlist?

I am developing an instagram-like app where I needed to render images/videos. I am using flatlist to prevent memory lost, and using expo-av package to render the video.
Here is a screenshot of what I want to achieve:
So my goal here is to render videos with original ratio.
However, I am struggling to render the flatlist that contains the videos, it just doesn't render the component at all but I can still hear the video playing.
This is my FlatList:
<FlatList
style={{ width: "100%", height: "100%", backgroundColor: "yellow" }}
data={[0, 1, 2, 3, 4]}
keyExtractor={item => item}
renderItem={renderItem}
/>
And my renderItem callback:
const renderItem = ({ item }) => {
return <Post post={item} />;
}
Post item code:
export default ({ post }) => {
const videoRef = useRef(null);
const [status, setStatus] = useState({});
return (
<View style={{ width: "100%", height: "100%", backgroundColor: "blue" }}>
<Video
ref={videoRef}
source={{
uri: 'http://commondatastorage.googleapis.com/gtv-videosbucket/sample/VolkswagenGTIReview.mp4',
}}
style={{ width: "100%", height: "100%", backgroundColor: "blue" }}
resizeMode="contain"
autoplay
isLooping
shouldPlay={true}
onPlaybackStatusUpdate={status => setStatus(() => status)}
/>
</View>
);
}
Result (yellow background: flatlist area, post item should appear blue but not showing up):
The video would display if I give it a static width and height instead of values like 100%, but since I needed the renderItem to look original and take as much space as needed across all kinds of devices, so the only thing I could think of is a percentage.
If there is a way to know the aspect ratio or the width and height of the video, I can do dynamic calculations to achieve my goal, but I don't know if expo-av provide this information~
The renderItem would automatically take the max width inside a FlatList, but the height is default 0.
I figured that we can pass the video's natural aspect ratio to the style property so that it renders itself naturally with max size.
To get the video's natural aspect ratio, we have to define a function for the video's onReadyForDisplay property, it provides information of the video once the first frame is loaded.
To do that, we set the default ratio to the screen ratio:
import { Dimensions } from "react-native";
const defaultScreenRatio = Dimensions.get("window").width / Dimensions.get("window").height;
And then inside the component:
// Use the screenDefaultRatio to render the video before the video is loaded
const [videoRatio, setVideoRatio] = useState(screenDefaultRatio);
// Update the videoRatio right after we know the video natural size
const updateVideoRatioOnDisplay = (videoDetails) => {
const { width, height } = videoDetails.naturalSize;
const newVideoRatio = width / height;
setVideoRatio(newVideoRatio);
}
Code for Video item:
<View>
<Video
ref={videoRef}
source={{
uri: 'http://commondatastorage.googleapis.com/gtv-videosbucket/sample/VolkswagenGTIReview.mp4',
}}
style={{ aspectRatio: videoRatio, backgroundColor: "blue" }}
resizeMode="contain"
autoplay
isLooping
shouldPlay={true}
onPlaybackStatusUpdate={status => setStatus(() => status)}
// Update the video Ratio once done loading the first frame of the video
onReadyForDisplay={updateVideoRatioOnDisplay}
/>
</View>
So, I don't know how to make it dynamic to rach video, but you just can't have percentages for height and width

react native snap carousel, next card preview same height

I am using react native snap carousel and I am trying to have the preview of the next card look the same height as the current card. Originally the preview is set to look centered but smaller. I would like the preview to look the same exact size.
I tried setting the containerCustomStyle to alignItems center which made it look closer to the result I wanted but the sizes are not the same. If you remove the contrainerCustomStyle you can see an exaggerated version of what I do NOT want.
I have a snack expo recreating my problem here as well as some code below. If I need to add a picture to clarify the result I would like, let me know!
I appreciate any insight at all more than you know.
renderCarouselItem = ({ item }) => {
return <View style={styles.cardContainer}>
<Text style={styles.name}>{item.name}</Text>
</View>;
};
render() {
return (
<Carousel
ref={(c) => {
this._carousel = c;
}}
data={this.state.coordinates}
renderItem={this.renderCarouselItem}
containerCustomStyle={styles.carousel}
sliderWidth={Dimensions.get('window').width}
itemWidth={300}
removeClippedSubviews={false}
/>
);
}
}
const styles = StyleSheet.create({
cardContainer: {
backgroundColor: 'red',
height: 100,
width: 300,
borderRadius: 10,
},
name: {
color: 'black',
fontSize: 22,
},
carousel: {
alignItems: 'center',
}
});
react-native-snap-carousel is deprecated. You should try react-native-reanimated-carousel

Left-align a scaled View/Text with react-native's Animated API

I a trying to build a 'Material-UI-like' TextInput with a large label that shrinks down when the field is selected.
I am having issues with scaling the label. Applying a transform: [{scale: ...}] shrinks the Text, but does so around the center of the field. I am failing to keep the label left-aligned during the scaling process, as I'd need to dynamically be able to access the view's width to offset it, but I can't seem to be able to get it using normal means(i.e. onLayout, which does not seem to be triggered during the animation).
Here is an example demonstrating the issue:
import React from 'react';
import { View, Text, TextInput, Animated } from 'react-native';
export const F = (): JSX.Element => {
const scale = React.useRef(new Animated.Value(0.0)).current;
React.useEffect(() => {
const animation = Animated.timing(scale, {
toValue: 1.0,
duration: 1000,
useNativeDriver: true,
});
animation.start();
}, []);
return (
<View style={{ backgroundColor: 'red' }}>
<Animated.View
style={{
transform: [
{
scale: scale.interpolate({
inputRange: [0, 1],
outputRange: [1, 0.5],
}),
},
],
backgroundColor: 'yellow',
}}
onLayout={(e) => console.log({ view: e.nativeEvent.layout })}>
<Text onLayout={(e) => console.log({ text: e.nativeEvent.layout })}>
Label
</Text>
</Animated.View>
<TextInput style={{ backgroundColor: 'blue' }} />
</View>
);
};
Example after the text has been scaled by half:
Note how the yellow view (Text) is no longer left aligned because of the scaling.
I've created a stack to explain what I am trying to accomplish:
https://snack.expo.dev/#bertrand-caron/trembling-beef-jerky
I'd like the Label View (yellow) to stay left align when scaled, instead of being shrunk centered inside the red view.
I have two solution:
1. For this I have created a snack: https://snack.expo.dev/PFN07UigC .
Let me know if it fits. For left aligning I just used justify-content:'flex-start' to its container.
2. If you wish to calculate it you know the initial size with onLayout and you have the scale let us assume that scale you are using is 0.5 and the onLayout gave you 100 then the margin-left that you are wanted is (width-(width*scale))/2 which is in our case 25. But I don't see any need for that.
In my opinion, you should make the <Animated.Text/> rather than <Animated.View/>. But it will also work.
If you don’t use alignSelf: 'flex-start' it will take the same space as the Parent View.
Here is my solution: https://snack.expo.dev/#fanish/textinput-animation
<Animated.Text
style={{
...textStyle,
transform: [{ scale }],
alignSelf: 'flex-start',
}}>
Label
</Animated.Text>

React Native + Android: borderRadius alternative?

I need to make my View circular on a non solid colored background.
I tried using borderRadius and overflow: 'hidden', but it isn't working. I see that this is a known issue with React Native: https://github.com/facebook/react-native/issues/3198
There seems to be workarounds for images that are on top of a solid background, but my background is dynamic and on top of an image so therefore I can't hardcode it.
Are there any alternatives to get something like this to work?
The black square should be a circle:
Here's the code (P.S. I'm using react native webrtc, that's where I'm getting RTCView, but I think this works with a plain old View):
const styles = StyleSheet.create({
remoteVideoContainer: {
borderRadius: 50,
height: 100,
marginBottom: 20,
width: 100,
overflow: 'hidden',
backgroundColor: 'green',
},
video: {
flex: 1,
resizeMode: 'cover',
backgroundColor: 'blue',
},
});
export default VideoView = ({
videoURL,
}) => {
return (
<View style={styles.remoteVideoContainer}>
<RTCView
style={styles.video}
streamURL={videoURL}
mirror={true}
objectFit="cover"
/>
</View>
);
};
I'm not even sure if you can style the RTCView. The code doesn't expose a styling prop. https://github.com/oney/react-native-webrtc/blob/master/RTCView.js

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