React native svg heart size animation - react-native

I am trying to make a pulsing svg heart in ReactNative expo using an SVG image.
The only way I managed to make the heart to resize with an animated value is to change bind it to style: fontSize.
This seems to change size correctly, but the animation is really choppy.
Here is the code:
import React, { Component } from 'react';
import { Animated } from 'react-native';
import { SimpleLineIcons } from '#expo/vector-icons';
const AnimatedIcon = Animated.createAnimatedComponent(SimpleLineIcons);
const TARGET_FONT_SIZE = 16;
const GROWN_FONT_SIZE = 24;
class GrowingHeart extends Component<any, any> {
size = new Animated.Value(TARGET_FONT_SIZE);
constructor(props) {
super(props);
Animated.sequence([
Animated.timing(this.size, {
duration: 1000,
toValue: GROWN_FONT_SIZE
}),
Animated.timing(this.size, {
duration: 1000,
toValue: GROWN_FONT_SIZE
})
]).start();
}
render() {
return (
<AnimatedIcon
style={{ fontSize: this.size }}
size={20}
name="heart"
color="red"
/>
);
}
}
I tried also to bind width and height but they are also choppy + they change container size, rather than the icon.
Is there a better way of doing this? Thanks

Seems that Animated api is simply still rubbish (please correct me if I am getting this wrong)
I re-wrote this with reanimated and it works smooth. The code below is not complete but shows how heart is growing with no choppiness, but rather perfectly smooth.
import React, { Component } from 'react';
import { TouchableOpacity } from 'react-native-gesture-handler';
import Animated, { Easing } from 'react-native-reanimated';
import { SimpleLineIcons } from '#expo/vector-icons';
const {
createAnimatedComponent,
debug,
set,
get,
block,
eq,
Value,
cond,
greaterThan,
startClock,
timing,
Clock,
Code,
clockRunning,
stopClock
} = Animated;
const AnimatedIcon = createAnimatedComponent(SimpleLineIcons);
const TARGET_FONT_SIZE = 16;
const GROWN_FONT_SIZE = 20;
class GrowingHeart extends Component<any, any> {
size = new Value(TARGET_FONT_SIZE);
clock = new Clock();
updatingValue = new Value(0);
clockState = {
finished: new Value(0),
position: new Value(5),
time: new Value(0),
frameTime: new Value(0)
};
clockConfig = {
duration: new Value(500),
toValue: new Value(GROWN_FONT_SIZE),
easing: Easing.linear
};
constructor(props) {
super(props);
}
render() {
const { color } = this.props;
const { updatingValue, size, clock, clockConfig, clockState } = this;
return (
<>
<Code>
{() =>
block([
cond(
// animation not triggered
eq(0, updatingValue),
[],
[
cond(
clockRunning(clock),
[],
[
set(clockState.finished, 0),
set(clockState.time, 0),
set(clockState.position, TARGET_FONT_SIZE),
set(clockState.frameTime, 0),
set(clockConfig.toValue, GROWN_FONT_SIZE),
startClock(clock)
]
),
cond(
greaterThan(0, updatingValue),
// is decreasing
[debug('going down', updatingValue)],
// is growing
[
timing(clock, clockState, clockConfig),
set(size, clockState.position)
]
),
cond(clockState.finished, [stopClock(clock)])
]
)
])
}
</Code>
<TouchableOpacity
onPress={() => {
this.updatingValue.setValue(1);
}}
>
<AnimatedIcon
style={{ fontSize: this.size }}
name="heart"
color={color}
/>
</TouchableOpacity>
</>
);
}
}

Related

Implement smooth text transition in React Native

I try to implement component that takes takes text property and depend on previous value shows smooth transition:
import React, { useEffect, useRef } from 'react'
import { Animated, StyleSheet } from 'react-native'
const usePrevious = (value) => {
const ref = useRef()
useEffect(() => {
ref.current = value
}, [value])
return ref.current
}
export default function AnimatedText({ style, children }) {
const fadeInValue = new Animated.Value(0)
const fadeOutValue = new Animated.Value(1)
const prevChildren = usePrevious(children)
useEffect(() => {
if (children != prevChildren) {
animate()
}
}, [children])
const animate = () => {
Animated.parallel([
Animated.timing(fadeInValue, {
toValue: 1,
duration: 1000,
useNativeDriver: true
}),
Animated.timing(fadeOutValue, {
toValue: 0,
duration: 1000,
useNativeDriver: true
})
]).start()
}
return (
<>
<Animated.Text style={[ style, { opacity: fadeInValue }]}>{children}</Animated.Text>
{
prevChildren &&
<Animated.Text style={[ style, styles.animatedText, { opacity: fadeOutValue }]}>{prevChildren}</Animated.Text>
}
</>
)
}
const styles = StyleSheet.create({
animatedText: {
position: 'absolute',
top: 0,
left: 0,
}
})
As the result I got smooth transition between component rendering with different children arguments. But there is a flickering due to some reasons related to animated value updates. Is there any way to avoid this problem or better solution to implement such component?
Found the solution with react-native-reanimated. It's not elegant implementation but it seems to work correctly without flickering:
import React, { useEffect, useRef } from 'react'
import { StyleSheet } from 'react-native'
import Animated, { useSharedValue, useAnimatedStyle, withTiming } from 'react-native-reanimated'
const usePrevious = (value) => {
const ref = useRef()
useEffect(() => {
ref.current = value
}, [value])
return ref.current
}
export default function AnimatedText({ style, children }) {
const fadeValue1 = useSharedValue(0)
const fadeValue2 = useSharedValue(1)
const toggleFlagRef = useRef(false)
const animatedTextStyle1 = useAnimatedStyle(() => {
return {
opacity: withTiming(fadeValue1.value, { duration: 1000 })
}
})
const animatedTextStyle2 = useAnimatedStyle(() => {
return {
opacity: withTiming(fadeValue2.value, { duration: 1000 })
}
})
const prevChildren = usePrevious(children)
useEffect(() => {
if (children != prevChildren) {
animate()
}
}, [children])
const animate = () => {
if (toggleFlagRef.current) {
fadeValue1.value = 0
fadeValue2.value = 1
} else {
fadeValue1.value = 1
fadeValue2.value = 0
}
toggleFlagRef.current = !toggleFlagRef.current
}
return (
<>
<Animated.Text style={[ style, animatedTextStyle1 ]}>{toggleFlagRef.current ? prevChildren : children}</Animated.Text>
{
prevChildren &&
<Animated.Text style={[ style, styles.animatedText, animatedTextStyle2 ]}>{toggleFlagRef.current ? children : prevChildren}</Animated.Text>
}
</>
)
}
const styles = StyleSheet.create({
animatedText: {
position: 'absolute',
top: 0,
left: 0,
}
})

how to drag the view in react native

I have an example code I put it on below ,this example show the circle to drag another location
import React, { Component } from "react";
import {
StyleSheet,
View,
PanResponder,
Animated
} from "react-native";
export default class Draggable extends Component {
constructor() {
super();
this.state = {
pan: new Animated.ValueXY()
};
}
componentWillMount() {
// Add a listener for the delta value change
this._val = { x:0, y:0 }
this.state.pan.addListener((value) => this._val = value);
// Initialize PanResponder with move handling
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: (e, gesture) => true,
onPanResponderMove: Animated.event([
null, { dx: this.state.pan.x, dy: this.state.pan.y }
])
// adjusting delta value
this.state.pan.setValue({ x:0, y:0})
});
}
render() {
const panStyle = {
transform: this.state.pan.getTranslateTransform()
}
return (
<Animated.View
{...this.panResponder.panHandlers}
style={[panStyle, styles.circle]}
/>
);
}
}
let CIRCLE_RADIUS = 30;
let styles = StyleSheet.create({
circle: {
backgroundColor: "skyblue",
width: CIRCLE_RADIUS * 2,
height: CIRCLE_RADIUS * 2,
borderRadius: CIRCLE_RADIUS
}
});
this code the circle to the drag but I have drag another location it goes on the starting location so have to set the current location value so please tell me

Unable to render a 3D model in Expo

SDK Version: 37.0.0
Platforms(Android/iOS/web/all): Android/iOS.
I’m trying to load a 3D object in my application and followed many tutorials.
the model is loaded successfully and attached to scene variable but it doesn’t appear in the view.
this is a part of my graduation project and i have searched a lot the 2 months to answers
looking for a real working solution as most of the solution i found was too old and doesn’t work.
my component is as described below:
import * as React from 'react';
import { Ionicons } from '#expo/vector-icons';
import { View, StyleSheet,PixelRatio ,TouchableOpacity} from 'react-native';
import { ExpoWebGLRenderingContext, GLView } from 'expo-gl';
import { Renderer, TextureLoader } from 'expo-three';
import ExpoTHREE from 'expo-three';
import * as THREE from 'three'
import {
AmbientLight,
BoxBufferGeometry,
Fog,
Mesh,
MeshStandardMaterial,
PerspectiveCamera,
PointLight,
Scene,
SpotLight,
} from 'three';
export default class ModelScreen extends React.Component {
constructor(props) {
super(props);
let timeout;
this.state= {
loadingCompleted:true,
}
}
componentDidMount(){
THREE.suppressExpoWarnings(true)
clearTimeout(this.timeout)
}
componentWillUnmount(){
clearTimeout(this.timeout)
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity style={[styles.backButton]} activeOpacity={0.8}
onPress= {()=>{
this.props.setViewModel(false);
}}
>
<Ionicons style={styles.backButtonIcon} name="md-arrow-back"></Ionicons>
</TouchableOpacity>
<GLView
style={styles.viewer}
onContextCreate={async (gl: ExpoWebGLRenderingContext) => {
const { drawingBufferWidth: width, drawingBufferHeight: height } = gl;
const sceneColor = '#111111';
const scale = PixelRatio.get();
// Create a WebGLRenderer without a DOM element
const renderer = new Renderer({gl,alpha:true});
renderer.capabilities.maxVertexUniforms = 52502;
renderer.setSize(width/scale, height/scale);
renderer.setPixelRatio(scale);
renderer.setClearColor(0x000000,0);
const camera = new PerspectiveCamera(45, width / height, 1, 1000);
camera.position.set(0, 2, 5);
camera.lookAt(0,0,0);
const scene = new Scene();
scene.fog = new Fog(sceneColor, 1, 1000);
const ambientLight = new AmbientLight(0x101010);
scene.add(ambientLight);
const pointLight = new PointLight(0xffffff, 2, 1000, 1);
pointLight.position.set(0, 200, 200);
scene.add(pointLight);
const spotLight = new SpotLight(0xffffff, 0.5);
spotLight.position.set(0, 500, 100);
spotLight.lookAt(scene.position);
scene.add(spotLight);
var object = null;
const model = {
'thomas.obj': require('./../assets/models/thomas/thomas.obj'),
'thomas.mtl': require('./../assets/models/thomas/thomas.mtl'),
'thomas.png': require('./../assets/models/thomas/thomas.png'),
};
// Load model!
await ExpoTHREE.loadAsync(
[model['thomas.obj'], model['thomas.mtl']],
null,
name => model[name],
).then((obj)=>{
// // Update size and position
ExpoTHREE.utils.scaleLongestSideToSize(obj, 5);
ExpoTHREE.utils.alignMesh(obj, { y: 1 });
// Smooth mesh
ExpoTHREE.utils.computeMeshNormals(obj.children[0]);
// Add the mesh to the scene
scene.add(obj.children[0]);
}).catch((error)=>{
console.log(error);
});
console.log(scene.children.length)
function update() {
if (scene.children.length == 4)
scene.children[3].rotateY(0.03);
}
// Setup an animation loop
const render = () => {
this.timeout = requestAnimationFrame(render);
update();
renderer.render(scene, camera);
gl.endFrameEXP();
};
render();
}}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex:1,
justifyContent:"center",
alignItems:"center",
backgroundColor:"#111111",
},
backButton:{
position:'absolute',
top:30,
width:50,
height:50,
alignItems:"center",
justifyContent:"center",
borderRadius:100,
backgroundColor:"rgb(1,175,250)",
left:20,
alignSelf:"flex-start",
zIndex:10,
},
backButtonIcon:{
fontSize:25,
fontWeight:'900',
color:"#111111",
},
viewer:{
width:"80%",
height:"80%",
}
});
can anyone help me please?
Edit
it's working now without material i have changed
await ExpoTHREE.loadAsync([model['thomas.obj'], model['thomas.mtl']],null,name => model[name])
to
await ExpoTHREE.loadAsync(model['thomas.obj'],null,name => model[name])
but it doesn't render if i load the material and don't know what's wrong with it. i tried different objects and cannot render the material. the object disappears

Toggling the animated value in order to fade in/out the view

I'm trying to toggle view's opacity with animated value, by handling the button click, but I'm not getting the desired result, except the first time button is clicked, it fades out (opacity = 0) but when I press the button again nothing happens and I can't see my view. Here's the code:
export default class App extends React.Component {
state = {
animation: new Animated.Value(1)
}
startAnimation = () => {
const { animation } = this.state
Animated.timing(animation, {
toValue: animation === 0 ? 1 : 0,
duration: 1000
}).start()
}
render() {
const animatedStyle = {
opacity: this.state.animation
}
return (
<View style={styles.container}>
<Animated.View style={[styles.box, animatedStyle]} />
<Button title="Toggle fade" onPress={this.startAnimation} />
</View>
);
}
} .
Does anybody know what am I doing (understanding) wrong?
Thanks!
I think it is because you don't change the state for your animated values, and this const { animation } = this.state will have always the same value, and toValue: animation === 0 ? 1 : 0, will have the same value too. I try to show you how I did this in my projects, but you have to update it for your needs.
export default class App extends React.Component {
state = {
animation: new Animated.Value(1),
isVisible: false //add a new value to check your state
}
startAnimation = () => {
const { isVisible } = this.state
Animated.timing(animation, {
toValue: isVisible === 0 ? 1 : 0,
duration: 1000
}).start(() => {
this.setState({ isVisible: !this.state.isVisible });//set the new state, so the next click will have different value
})
}
render() {
const animatedStyle = {
opacity: this.state.animation
}
return (
<View style={styles.container}>
<Animated.View style={[styles.box, animatedStyle]} />
<Button title="Toggle fade" onPress={this.startAnimation} />
</View>
);
}
} .
I am using this:
let val = this.state.sliderOpen ? 0.8 : 0;
Animated.timing( // Animate over time
this.state.sliderAnimation, // The animated value to drive
{
toValue: val, // Animate to opacity: 1 (opaque)
duration: 5, // Make it take a while
}
).start();
this.setState({
sliderOpen : !this.state.sliderOpen
})
Maybe try to extract the value to be changed.
Thanks to #oma I was able to get it work, here's the snack:
Toggle opacity in React Native
Besides that, I've found a nice article on this where this feature can be reused:
Animating appearance and disappearance in React Native
And here's the snack of the working example, with slight modification.
Animate opacity
This solution looks pretty well, hope you can benefit from it.
I made a node package react-native-fade-in-out that toggles a view's opacity with an animated value. You can look at the source code to see how it is accomplished, but here's a simplified version:
import React, {PureComponent} from 'react';
import {Animated} from 'react-native';
export default class FadeInOut extends PureComponent {
state = {
fadeAnim: new Animated.Value(this.props.visible ? 1 : 0),
};
componentDidUpdate(prevProps) {
if (prevProps.visible !== this.props.visible) {
Animated.timing(this.state.fadeAnim, {
toValue: prevProps.visible ? 0 : 1,
duration: 300,
}).start();
}
}
render() {
return (
<Animated.View style={{...this.props.style, opacity: this.state.fadeAnim}}>
{this.props.children}
</Animated.View>
);
}
}

How to make Animated timing/spring refresh with the new start?

I was trying to include the Circular Progress module to my project. Everything work well except that When the Fill value change from 100 to 0 ,the Animated.spring/ timing function will draw back. Is there any idea to make circular progress bar refresh with the start point when it hit end point?
import React, { PropTypes } from 'react';
import { View, Animated } from 'react-native';
import CircularProgress from './CircularProgress';
const AnimatedProgress = Animated.createAnimatedComponent(CircularProgress);
export default class AnimatedCircularProgress extends React.Component {
constructor(props) {
super(props);
this.state = {
chartFillAnimation: new Animated.Value(props.prefill || 0)
}
}
componentDidMount() {
this.animateFill();
}
componentDidUpdate(prevProps) {
if (prevProps.fill !== this.props.fill) {
this.animateFill();
}
}
animateFill() {
const { tension, friction } = this.props;
Animated.spring(
this.state.chartFillAnimation,
{
toValue: this.props.fill,
tension,
friction
}
).start();
}
performLinearAnimation(toValue, duration) {
Animated.timing(this.state.chartFillAnimation, {
toValue: toValue,
duration: duration
}).start();
}
render() {
const { fill, prefill, ...other } = this.props;
return (
<AnimatedProgress
{...other}
fill={this.state.chartFillAnimation}
/>
)
}
}
AnimatedCircularProgress.propTypes = {
style: View.propTypes.style,
size: PropTypes.number.isRequired,
fill: PropTypes.number,
prefill: PropTypes.number,
width: PropTypes.number.isRequired,
tintColor: PropTypes.oneOf([PropTypes.string, PropTypes.object]),
backgroundColor: PropTypes.oneOf([PropTypes.string, PropTypes.object]),
tension: PropTypes.number,
friction: PropTypes.number
}
AnimatedCircularProgress.defaultProps = {
tension: 7,
friction: 10
};
.start() takes an optional callback which will be called once the animation is finished, so you can recursively call your animation:
animateFill() {
const { tension, friction } = this.props;
this.state.charFillAnimation.setValue(0); //reset to 0
Animated.spring(
this.state.chartFillAnimation,
{
toValue: this.props.fill,
tension,
friction
}
).start(() => this.animateFill());
}