Reputation: 620
I have a Component Parent which has an Animated.value "this.progress" which takes values in [1, 3].
The Parent renders a Component Child and pass it the progress as a props:
// File Parent.js
import React from "react";
import { Animated } from "react-native";
import Child from "./Child.js"
class Parent extends React.Component {
constructor(props) {
super(props);
this.progress = new Animated.Value(1)
}
render() {
return (
<Child
progress = {this.progress}
/>
);
}
}
The Child position depends on the progress:
If an animation changes the value of the progress, the Child must move accoringly (e.g., if progress is animated from 1 to 2, the user must see the Child move from [1, 3] to [2, 2] in a smooth way).
To that end I use, two interpolations:
// In the constructor of Child.js
this.interpolateX = this.props.progress.interpolate({
inputRange: [1, 2, 3],
outputRange: [1, 2, 1],
});
this.interpolateY = this.props.progress.interpolate({
inputRange: [1, 2, 3],
outputRange: [3, 2, 1],
});
and I use them to fix the position of the Child using "transform":
// Render method of Child.js
render() {
return (
<Animated.View
style={[
{
transform: [
{
translateX: this.interpolateX,
},
{
translateY: this.interpolateY,
},
],
},
{ position: 'absolute' },
{ left: 0 },
{ top: 0 },
]}
/>
);
}
It works well, but now I also want to make the Child movable using the user finger. To that end I defined a panResponder. The problem is that the panResponder also uses a transform. I now have 2 transforms:
I don't know how to combine these two transform. Here is what I tried:
// File Child.js
import React from "react";
import { Animated, PanResponder } from "react-native";
class Child extends React.Component {
constructor(props) {
super(props);
this.interpolateX = this.props.progress.interpolate({
inputRange: [1, 2, 3],
outputRange: [1, 2, 1],
});
this.interpolateY = this.props.progress.interpolate({
inputRange: [1, 2, 3],
outputRange: [3, 2, 1],
});
this.offset = new Animated.ValueXY({ x: 0, y: 0 });
this._val = { x: 0, y: 0 };
this.offset.addListener((value) => (this._val = value));
// Initiate the panResponder
this.panResponder = PanResponder.create({
// Ask to be the responder
onStartShouldSetPanResponder: () => true,
// Called when the gesture starts
onPanResponderGrant: () => {
this.offset.setOffset({
x: this._val.x,
y: this._val.y,
});
this.offset.setValue({ x: 0, y: 0 });
},
// Called when a move is made
onPanResponderMove: Animated.event([
null,
{ dx: this.offset.x, dy: this.offset.y },
]),
onPanResponderRelease: (evt, gesturestate) => {
console.log(
"released with offsetX: " +
this.offset.x._value +
"/" +
this.offset.y._value
);
},
});
}
render() {
const panStyle = {
transform: this.offset.getTranslateTransform(),
};
return (
<Animated.View
{...this.panResponder.panHandlers}
style={[
panStyle,
{
transform: [
{
translateX: this.interpolateX,
},
{
translateY: this.interpolateY,
},
],
},
{ position: "absolute" },
{ left: 0 },
{ top: 0 },
]}
/>
);
}
}
export default Child;
It does not work. panStyle seems to be ignored. I guess you cannot have two transforms together.
I also tried to "add" the two expressions in the transforms without success (maybe its feasible).
My last idea was to have a boolean "isMoving" equal to true when the user moves the Child. If isMoving is false, I use the first transform, otherwise, I use the second one. It seems promising but I did not succeed in making it work.
Could you tell me how I could do that?
Upvotes: 0
Views: 1097
Reputation: 620
A boolean variable isMoving which indicates which transform to use was the good idea. The trick was:
Here is the result:
// File Child.js
import React from "react";
import { Animated, PanResponder } from "react-native";
class Child extends React.Component {
constructor(props) {
super(props);
this.interpolateX = this.props.progress.interpolate({
inputRange: [1, 2, 3],
outputRange: [1, 2, 1],
});
this.interpolateY = this.props.progress.interpolate({
inputRange: [1, 2, 3],
outputRange: [3, 2, 1],
});
this.offset = new Animated.ValueXY({ x: 0, y: 0 });
this.state = {
isMoving: false,
};
// Initiate the panResponder
this.panResponder = PanResponder.create({
// Ask to be the responder
onStartShouldSetPanResponder: () => true,
// Called when the gesture starts
onPanResponderGrant: () => {
this.setState({
isMoving: true,
});
this.offset.setOffset({
x: this.interpolateX.__getValue(),
y: this.interpolateY.__getValue(),
});
this.offset.setValue({ x: 0, y: 0 });
},
// Called when a move is made
onPanResponderMove: Animated.event([
null,
{ dx: this.offset.x, dy: this.offset.y },
]),
onPanResponderRelease: (evt, gesturestate) => {
this.setState({
isMoving: false,
});
},
});
}
render() {
const panStyle = {
transform: this.offset.getTranslateTransform(),
};
if (this.state.isMoving)
return (
<Animated.View
{...this.panResponder.panHandlers}
style={[panStyle, { position: "absolute" }, { left: 0 }, { top: 0 }]}
/>
);
else
return (
<Animated.View
{...this.panResponder.panHandlers}
style={[
{
transform: [
{
translateX: this.interpolateX,
},
{
translateY: this.interpolateY,
},
],
},
{ position: "absolute" },
{ left: 0 },
{ top: 0 },
]}
/>
);
}
}
export default Child;
Upvotes: 1