I am trying to translate the x and y of each data point on the linear chart according to their position but I am always getting the position of the last item in the animation and if I try to save positions inside the component using usestate of parent, animations doesn't work correctly
const chart = useRef(null)
const dot = useRef()
const ax = new Animated.Value(0)
const ay = new Animated.Value(0)
const [pos, setPos] = useState([])
const [animateIndex, setAnimateIndex] = useState(0)
useEffect(() => {
dot.current.animateDot()
}, [animateIndex])
<LineChart
ref={chart}
data={{
datasets: [
{
data: [
3.5, 5, 5.5, 6.5, 7.2, 8, 8.7
]
}
]
}}
renderDotContent={({ x, y, index, }) =>
<Dot //want to save x and y of each dot
ref={dot}
x={x}
y={y}
index={index}
key={index}
ax={ax}
ay={ay}
setPos={setPos}
pos={pos}
animateIndex={animateIndex}
hiden={hiden}
setHiden={setHiden} />
}
xLabelsOffset={-8}
width={SIZES.width * 0.84}
height={SIZES.height * 0.20}
yAxisSuffix="k"
withVerticalLines={false}
yAxisInterval={1}
/>
dot component
const Dot = ({ x, y, index, hiden, setHiden, ax, ay, animateIndex, setPos, pos }, ref) => {
useImperativeHandle(ref, () => ({
// methods connected to `ref`
animateDot: () => { animateDot() }
}))
const animateDot = () => {
console.log('animate dot function', index, posi)
Animated.timing(ax, {
toValue: SIZES.width / 1.3 - x,
duration: 1000,
useNativeDriver: true
}).start()
Animated.timing(ay, {
toValue: -SIZES.height / 2 + y,
duration: 1000,
useNativeDriver: true
}).start()
}
return (
<Animated.View
style={{
position: 'absolute',
top: y - 7.5,
left: x - 7.5,
height: 15,
width: 15,
borderRadius: 10,
transform: [
{
translateY: (animateIndex === index) ? ay : 0
},
{
translateX: (animateIndex === index) ? ax : 0
}
],
borderWidth: 3.5,
borderColor: COLORS.primary,
backgroundColor: COLORS.secondary
}}>
</Animated.View>
)
}
export default forwardRef(Dot)
how can i save positions of each item
Related
I am trying to make Animated.ScollView to behave like a bottomSheet in Reactnative by using onScroll function in which I am getting contentOffsetY and based on that I make scrollView unmount/drag-down (works fine for IOS). But for Android it is not event triggering onScroll.
import * as React from 'react';
import { StyleSheet, Dimensions, Animated, View } from 'react-native';
const { width, height } = Dimensions.get('window');
import { Icon } from 'react-native-elements';
export const BottomSheet = ({
indicatorMargin,
heightFactor,
children,
gmRef,
dragging = true,
}) => {
const [alignment] = React.useState(new Animated.Value(height));
const [onAnimate, setOnAnimate] = React.useState(false);
const [isOpen, setIsOpen] = React.useState(false);
const scrollRef = React.useRef();
React.useEffect(
() => dragSheetUp(),
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
const dragSheetUp = React.useCallback(() => {
setOnAnimate(true);
setIsOpen(true);
Animated.timing(alignment, {
toValue: 0,
duration: 500,
useNativeDriver: false,
}).start(() => setOnAnimate(false));
scrollRef?.current?.scrollTo({
y: 0,
animated: true,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const dragSheetDown = React.useCallback(() => {
setOnAnimate(true);
Animated.timing(alignment, {
toValue: height,
duration: 500,
useNativeDriver: false,
}).start(() => {
setOnAnimate(false);
setIsOpen(false);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
React.useImperativeHandle(
gmRef,
() => ({
open: () => dragSheetUp(),
close: () => dragSheetDown(),
}),
[dragSheetDown, dragSheetUp]
);
const actionSheetBottomStyle = {
top: alignment,
marginTop: height * (heightFactor - 1),
};
const gestureHandler = (e) => {
if (onAnimate) return;
if (e.nativeEvent.contentOffset.y < -40 && dragging) {
dragSheetDown();
}
};
const calculatedHeight = height * (heightFactor - 1) - 100;
return (
<Animated.ScrollView
ref={scrollRef}
style={[styles.bottomSheetContainer, actionSheetBottomStyle]}
scrollEventThrottle={12}
showsVerticalScrollIndicator={false}
onScroll={(e) => {
gestureHandler(e);
}}
>
{isOpen && dragging ? (
<View
style={styles.closeBtn}
onStartShouldSetResponder={() => {
dragSheetDown();
}}
>
<Icon name="chevron-down" type="feather" color="black" size={28} />
</View>
) : (
<View
style={[
styles.indicator,
{
marginBottom: indicatorMargin ? indicatorMargin : 10,
},
]}
/>
)}
{children}
<View style={[styles.bottomSpace, { height: calculatedHeight }]} />
</Animated.ScrollView>
);
};
const styles = StyleSheet.create({
bottomSheetContainer: {
backgroundColor: '#fff',
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
height: height / 1.2,
width: width / 1,
borderTopRightRadius: 20,
borderTopLeftRadius: 20,
paddingVertical: 10,
},
indicator: {
width: 30,
borderTopColor: 'black',
borderRadius: 15,
borderTopWidth: 4,
alignSelf: 'center',
},
bottomSpace: {
backgroundColor: '#fff',
},
closeBtn: {
width: 40,
alignSelf: 'center',
},
});
(Note: I want to handle onScroll even when we are at the top of ScrollView and it works fine for IOS but not for android)
I have an animation that uses Animated.timing() which slides a component in to view when the condition mapIsCentered = true. When the condition isn't met the component just disappears ungracefully. I'd like for it to slide in and out as the condition changes.
One thing to note the mapIsCentered state is updated on a different screen and passed as a prop to the component I am working in. I have logged the state and it updates when the map is moved.
** the slide in works as expected
Thanks to #Ashwith for the first answer
const values = useRef(new Animated.ValueXY({ x: 0, y: 120 })).current;
useEffect(() => {
Animated.timing(values, {
toValue: mapIsCentered ? { x: 0, y: 0 } : { x: 0, y: 120 },
duration: 500,
useNativeDriver: false,
}).start();
}, [mapIsCentered]);
{!walkInProgress && !hasOnGoingWalks && (
<Animated.View
style={{
transform: [{ translateY: values.y }],
}}
>
<WeatherToast
translations={translations}
loading={loading}
weather={weather}
/>
</Animated.View>
Thanks in advance!
I have changed the structure hope it works for you...
snack: https://snack.expo.io/#ashwith00/excited-orange
App.js
const walkInProgress = false , hasOnGoingWalks = false;
export default function App() {
const { width } = useWindowDimensions();
const [mapCentered, setMapCentered] = React.useState(false)
const toggle = () => {
setMapCentered((ct) => !ct);
};
return (
<View style={styles.container}>
<WeatherToast mapCentered={mapCentered && !walkInProgress && !hasOnGoingWalks} />
<Button title="shift" onPress={toggle} />
</View>
);
}
WeatherTost.js
export default ({ mapCentered }) => {
const [visible, setVisible] = useState(mapCentered);
const { width } = useWindowDimensions();
const values = React.useRef(new Animated.ValueXY({ x: 0, y: 120 })).current;
React.useEffect(() => {
if (mapCentered) {
setVisible(true);
Animated.timing(values, {
toValue: { x: 0, y: 0 },
duration: 300,
}).start();
} else {
Animated.timing(values, {
toValue: { x: width, y: 0 },
duration: 300,
}).start(({ finished }) => {
if (finished) {
setVisible(false);
}
});
}
}, [mapCentered]);
const styles = [];
return visible ? (
<Animated.View
style={{
width: 200,
height: 200,
position: 'absolute',
backgroundColor: 'red',
transform: [
{
translateX: values.x,
},
{
translateY: values.y,
},
],
}}
/>
) : (
<View />
);
};
I want to implement this design using React Native. For navigation
purpose I am using React Navigation 5. I tried using react-native SVG. Not having good knowledge in svg designing.
Here is the sample code.
import Svg, { Path } from 'react-native-svg';
import * as shape from 'd3-shape';
const { width } = Dimensions.get('window');
const height = 64;
const tabWidth = width / 4;
const AnimatedSvg = Animated.createAnimatedComponent(Svg)
const left = shape.line()
.x(d => d.x)
.y(d => d.y)
([
{ x: 0, y: 0 },
{ x: width, y: 0 }
]);
const right = shape.line()
.x(d => d.x)
.y(d => d.y)
([
{ x: width + tabWidth, y: 0 },
{ x: width * 2, y: 0 },
{ x: width * 2, y: height },
{ x: 0, y: height },
{ x: 0, y: 0 },
])
const tab = shape.line()
.x(d => d.x)
.y(d => d.y)
.curve(shape.curveBasis)
([
{ x: width, y: 0 },
{ x: width + 5, y: 0 },
{ x: width + 10, y: 0 },
{ x: width + 15, y: height },
{ x: width + tabWidth - 15, y: height },
{ x: width + tabWidth - 10, y: 10 },
{ x: width + tabWidth -5, y: 0 },
])
const d = `${right} ${left} ${tab}`;
const Test = () => {
return (
<SafeAreaView style={{ flex: 1 }}>
<View style={{ flex: 1, backgroundColor: '#ea3345', justifyContent: 'flex-end' }}>
<View style={{ height }}>
<AnimatedSvg
style={{
transform:[
{
translateX:-100
}
]
}}
width={width*2}
{...{height}}>
<Path {...{ d }} fill="white" />
</AnimatedSvg>
</View>
</View>
</SafeAreaView>
)
}
export default Test
Please help me to achieve this kind of design. Thanks in advance.
I have several frames of an animation. I want to display the animation on a loop. I've read:
https://reactnative.dev/docs/animations
https://reactnative.dev/docs/animated
https://blog.bitsrc.io/making-animations-in-react-native-the-simplified-guide-6580f961f6e8
And I have tried implementing:
https://medium.com/react-native-training/react-native-animations-using-the-animated-api-ebe8e0669fae
But none of them cover multiple frames of animation and how to loop through them using simple code. I'm sure what I need to do is v simple but these tutorials are over-whelming.
Here's some code I'm using:
Just before the render
Images: [
{ id: 1, src: './assets//frame1.png', title: 'foo', description: 'bar' },
{ id: 2, src: './assets//frame2.png', title: 'foo', description: 'bar' },
{ id: 3, src: './assets//frame3.png', title: 'foo', description: 'bar' },
{ id: 4, src: './assets//frame4.png', title: 'foo', description: 'bar' },
{ id: 5, src: './assets//frame32.png', title: 'foo', description: 'bar' },
]
render() {
const items = this.state.Images.map((item, key) =>
<Image key={item.id}>{item.name}</Image>
...
<View>
{items}
</View>
That doesn't work - objects are not valid as a react child...
How would I simply display the first image of that array in the first place but then make it loop though each image (creating an animation).
Can anyone provide a simple block of code that demonstrates how to cycle/loop through several .png files in an assets folder as an animation on screen?
T
All you needed Interpolation through Opacity.
Just modify the data array like your Image array and display the images inside the Animating View.
Iterate through your Image array and set the opacity Values.
const data = ['red', 'green', 'blue', 'violet', 'pink', 'red'];
this.animations = new Animated.Value(0);
this.opacity = [];
data.map((item, index) => {
this.opacity.push(
this.animations.interpolate({
inputRange: [index - 1, index, index + 1],
outputRange: [0, 1, 0],
}),
);
});
Now this.opacity array will contain the corresponding opacity values for each item.
Now start the loop. (here I am using 2 sec to animate from one image to other )
Animated.loop(
Animated.timing(this.animations, {
toValue: length - 1,
duration: 2000 * length,
easing: Easing.linear,
useNativeDriver: true,
}),
).start();
Set opacity for each item inside the render
const opacity = this.opacity[index];
Full Code (example)
import React, {Component} from 'react';
import {View, StyleSheet, Animated, Easing} from 'react-native';
const data = ['red', 'green', 'blue', 'violet', 'pink', 'red'];
const length = data.length;
export default class App extends Component {
constructor() {
super();
this.animations = new Animated.Value(0);
this.opacity = [];
data.map((item, index) => {
this.opacity.push(
this.animations.interpolate({
inputRange: [index - 1, index, index + 1],
outputRange: [0, 1, 0],
}),
);
});
}
componentDidMount() {
Animated.loop(
Animated.timing(this.animations, {
toValue: length - 1,
duration: 2000 * length,
easing: Easing.linear,
useNativeDriver: true,
}),
).start();
}
render() {
return (
<View style={styles.container}>
{data.map((item, index) => {
const opacity = this.opacity[index];
return (
<Animated.View
style={[styles.item, {backgroundColor: item, opacity}]}
/>
);
})}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
item: {
height: 200,
width: 200,
position: 'absolute',
},
});
I hope it will help you.
I have a View that I want to shrink to the bottom right with some margin / padding on the bottom and right sides.
I was able to make it shrink, but it shrinks to the center. The video element is the one shrinking:
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
View,
PanResponder,
Animated,
Dimensions,
} from 'react-native';
const styles = StyleSheet.create({
container: {
flex: 1,
alignSelf: 'stretch',
backgroundColor: '#000000',
},
overlay: {
flex: 1,
alignSelf: 'stretch',
backgroundColor: '#0000ff',
opacity: 0.5,
},
video: {
position: 'absolute',
backgroundColor: '#00ff00',
bottom: 0,
right: 0,
width: Dimensions.get("window").width,
height: Dimensions.get("window").height,
padding: 10,
}
});
function clamp(value, min, max) {
return min < max
? (value < min ? min : value > max ? max : value)
: (value < max ? max : value > min ? min : value)
}
export default class EdmundMobile extends Component {
constructor(props) {
super(props);
this.state = {
pan: new Animated.ValueXY(),
scale: new Animated.Value(1),
};
}
componentWillMount() {
this._panResponder = PanResponder.create({
onMoveShouldSetResponderCapture: () => true,
onMoveShouldSetPanResponderCapture: () => true,
onPanResponderGrant: (e, gestureState) => {
this.state.pan.setOffset({x: this.state.pan.x._value, y: 0});
this.state.pan.setValue({x: 0, y: 0});
},
onPanResponderMove: (e, gestureState) => {
let width = Dimensions.get("window").width;
let difference = Math.abs((this.state.pan.x._value + width) / width);
if (gestureState.dx < 0) {
this.setState({ scale: new Animated.Value(difference) });
return Animated.event([
null, {dx: this.state.pan.x, dy: 0}
])(e, gestureState);
}
},
onPanResponderRelease: (e, {vx, vy}) => {
this.state.pan.flattenOffset();
if (vx >= 0) {
velocity = clamp(vx, 3, 5);
} else if (vx < 0) {
velocity = clamp(vx * -1, 3, 5) * -1;
}
if (Math.abs(this.state.pan.x._value) > 200) {
Animated.spring(this.state.pan, {
toValue: {x: -Dimensions.get("window").width, y: 0},
friction: 4
}).start()
Animated.spring(this.state.scale, {
toValue: 0.2,
friction: 4
}).start()
} else {
Animated.timing(this.state.pan, {
toValue: {x: 0, y: 0},
friction: 4
}).start()
Animated.spring(this.state.scale, {
toValue: 1,
friction: 10
}).start()
}
}
});
}
render() {
let { pan, scale } = this.state;
let translateX = pan.x;
let swipeStyles = {transform: [{translateX}]};
let videoScale = scale
let localVideoStyles = {transform: [{scale: videoScale}]};
return (
<View style={styles.container}>
<Animated.View style={[styles.video, localVideoStyles]}></Animated.View>
<Animated.View style={[styles.overlay, swipeStyles]} {...this._panResponder.panHandlers}>
</Animated.View>
</View>
);
}
}
AppRegistry.registerComponent('EdmundMobile', () => EdmundMobile);
Ok I figured out one solution. Kinda hands on, but I think it provides all the customizations I need.
So instead of transforming scale, I animate the width, height, bottom, top styles by attaching them to the state and changing them in response to the pan responder stuff.
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
View,
PanResponder,
Animated,
Dimensions,
} from 'react-native';
const styles = StyleSheet.create({
container: {
flex: 1,
alignSelf: 'stretch',
backgroundColor: '#000000',
},
overlay: {
flex: 1,
alignSelf: 'stretch',
backgroundColor: '#0000ff',
opacity: 0.5,
},
video: {
position: 'absolute',
backgroundColor: '#00ff00',
}
});
function clamp(value, min, max) {
return min < max
? (value < min ? min : value > max ? max : value)
: (value < max ? max : value > min ? min : value)
}
const MIN_VIDEO_WIDTH = 120;
const MIN_VIDEO_HEIGHT = 180;
export default class EdmundMobile extends Component {
constructor(props) {
super(props);
this.state = {
pan: new Animated.ValueXY(),
width: new Animated.Value(Dimensions.get("window").width),
height: new Animated.Value(Dimensions.get("window").height),
bottom: new Animated.Value(0),
right: new Animated.Value(0),
};
}
componentWillMount() {
this._panResponder = PanResponder.create({
onMoveShouldSetResponderCapture: () => true,
onMoveShouldSetPanResponderCapture: () => true,
onPanResponderGrant: (e, gestureState) => {
this.state.pan.setOffset({x: this.state.pan.x._value, y: 0});
this.state.pan.setValue({x: 0, y: 0});
},
onPanResponderMove: (e, gestureState) => {
let width = Dimensions.get("window").width;
let difference = Math.abs((this.state.pan.x._value + width) / width);
console.log(difference);
if (gestureState.dx < 0) {
const newVideoHeight = difference * Dimensions.get("window").height;
const newVideoWidth = difference * Dimensions.get("window").width;
if (newVideoWidth > MIN_VIDEO_WIDTH) {
this.setState({
width: new Animated.Value(newVideoWidth),
});
}
if (newVideoHeight > MIN_VIDEO_HEIGHT) {
this.setState({
height: new Animated.Value(newVideoHeight),
});
}
this.setState({
bottom: new Animated.Value((1- difference) * 20),
right: new Animated.Value((1 - difference) * 20),
});
return Animated.event([
null, {dx: this.state.pan.x, dy: 0}
])(e, gestureState);
}
},
onPanResponderRelease: (e, {vx, vy}) => {
this.state.pan.flattenOffset();
if (vx >= 0) {
velocity = clamp(vx, 3, 5);
} else if (vx < 0) {
velocity = clamp(vx * -1, 3, 5) * -1;
}
if (Math.abs(this.state.pan.x._value) > 200) {
Animated.spring(this.state.pan, {
toValue: {x: -Dimensions.get("window").width, y: 0},
friction: 4
}).start()
Animated.spring(this.state.width, {
toValue: MIN_VIDEO_WIDTH,
friction: 4
}).start()
Animated.spring(this.state.height, {
toValue: MIN_VIDEO_HEIGHT,
friction: 4
}).start()
Animated.timing(this.state.bottom, {
toValue: 20,
}).start()
Animated.timing(this.state.right, {
toValue: 20,
}).start()
} else {
Animated.timing(this.state.pan, {
toValue: {x: 0, y: 0},
}).start()
Animated.timing(this.state.width, {
toValue: Dimensions.get("window").width,
friction: 4
}).start()
Animated.timing(this.state.height, {
toValue: Dimensions.get("window").height,
friction: 4
}).start()
Animated.timing(this.state.bottom, {
toValue: 0,
}).start()
Animated.timing(this.state.right, {
toValue: 0,
}).start()
}
}
});
}
render() {
let { pan, width, height, bottom, right } = this.state;
let translateX = pan.x;
let swipeStyles = {transform: [{translateX}]};
let videoStyles = {
width: width,
height: height,
bottom: bottom,
right: right,
};
return (
<View style={styles.container}>
<Animated.View style={[styles.video, videoStyles]}></Animated.View>
<Animated.View style={[styles.overlay, swipeStyles]} {...this._panResponder.panHandlers}>
</Animated.View>
</View>
);
}
}
AppRegistry.registerComponent('EdmundMobile', () => EdmundMobile);