Using PanResponder with Animated.event - animation

I've been trying to get PanResponder to work in the way that I need.
I have a View that contains a circle, and as I move my finger on that view, I want the circle to follow me.
The structure
<View {...this.panResponder.panHandlers}>
<Animated.Circle />
</View>
Now, all of the tutorials show the following
onPanResponderMove: Animated.event([
null, {dx: this.state.pan.x, dy: this.state.pan.y},
]),
And this works absolutely fine, my problem is that I need to fit more logic within the onPanResponderMove, and every time I try to use Animated.event within the function it does nothing. Here's an example of the function:
onPanResponderMove: (e, gestureState) => {
/* some other logic that I need here */
const { dx, dy } = gestureState;
Animated.event([null, {dx: this.state.pan.x, dy: this.state.pan.y}]);
},
How could I make this work?
Thanks in advance

https://github.com/facebook/react-native/issues/15880
Reason was because Animation.event() only generates the function which is used from onPanResponderMove, it also needs the default arguments evt, gestureState
The following works:
onPanResponderMove: (evt, gestureState) => {
// do whatever you need here
// be sure to return the Animated.event as it returns a function
return Animated.event([null, {
dx: this.state.pan.x,
dy: this.state.pan.y,
}])(evt, gestureState)
},

The solution was the following
onPanResponderMove: Animated.event([null, {dx: this.state.pan.x, dy: this.state.pan.y}], {
listener: (event, gestureState) => {
/* my own logic */
},
}),

Related

How to call scrollTo function from react-navigation tab bar

I have create a function which scrolls a ScrollView to a set position when it is called (near top). I would like for the function to be called from the react-navigation tab bar. Calling the function is easy, but I am struggling to get it to communicate with the scrollRef from the screen component.
Here's my snack: https://snack.expo.dev/#dazzerr/scroll-to-top-function
You'll find this function in App.js which is called when the tab bar is pressed:
const onTabPress = () => {
scrollRef.current?.scrollTo({ // how do I get ref={scrollRef} from component.js ScrollView?
y: 0,
animated: true,
});
};
and in component.js is the ScrollView in question:
<ScrollView
ref={scrollRef}
style={styles.container}
scrollEventThrottle={16}
>
{ScreenContent()}
</ScrollView>
How can I get the scrollRef from component.js called from inside the onTabPress function from app.js? 🤔
You'll need to pass the function through react-navigation setParams like this:
This goes inside your createBottomTabNavigator:
tabBarOnPress: (scene, jumpToIndex) => {
scene.navigation.state.params.scrollToTop(); // Here's the magic!
},
And this inside your component:
useEffect(() => {
props.navigation.setParams({
scrollToTop: () => {
onTabPress();
},
});
}, []);
const onTabPress = () => {
scrollRef.current?.scrollTo({
y: 600, // whatever number you want here
animated: true,
});
};
Of course don't forget to add the scrollRef to your ScrollView, and make sure that your ScrollView is from react-native and not react-navigation. You might also want to add conditionals inside the tabBarOnPress such as:
if (
isFocused &&
typeof route.routes[0]?.params?.onTabBarPress !== "function"
)
But that's your doing 👍
Here's a working snack: https://snack.expo.dev/#dazzerr/ontabpress-scrollto

How use MediaFilePicker and PhotoEditor plugins in Nativescript

I am trying to use MediaFilePicker on nativescript and at the same time use the PhotoEditor plugin to crop/edit the photo taken from the camera but I don't make it work... here is part of my code:
let options: ImagePickerOptions = {
android: {
isCaptureMood: true, // if true then camera will open directly.
isNeedCamera: true,
maxNumberFiles: 1,
isNeedFolderList: true
}, ios: {
isCaptureMood: true, // if true then camera will open directly.
maxNumberFiles: 1
}
};
let mediafilepicker = new Mediafilepicker();
mediafilepicker.openImagePicker(options);
mediafilepicker.on("getFiles", function (res) {
let results = res.object.get('results');
let result = results[0];
let source = new imageSourceModule.ImageSource();
source.fromAsset(result.rawData).then((source) => {
const photoEditor = new PhotoEditor();
photoEditor.editPhoto({
imageSource: source,
hiddenControls: [],
}).then((newImage) => {
}).catch((e) => {
reject();
});
});
});
The result object of the FilePicker comes like:
{
"type": "capturedImage",
"file": {},
"rawData": "[Circular]"
}
I believe if the picture was taken from the camera, then use the rawData field, but I dont know which format is coming and how to give it to PhotoEditor pluging to play with it.
Any suggestions?
Thanks!
The issue was at this line source.fromAsset(result.rawData) here, result.rawData is not an ImageAsset but it's PHAsset. You will have to create an ImageAsset from PHAsset and pass it on to fromAsset. So it would look like,
import { ImageAsset } from "tns-core-modules/image-asset";
....
....
imgSource.fromAsset(new ImageAsset(img)).then((source) => {
const photoEditor = new PhotoEditor();
console.log(source === imgSource);
photoEditor.editPhoto({
imageSource: source,
hiddenControls: [],
}).then((newImage: ImageSource) => {
console.log('Get files...');
// Here you can save newImage, send it to your backend or simply display it in your app
}).catch((e) => {
//reject();
});
});

drag and drop with auto scrolling (dom-autoscrolling)

I have a list of text elements and want to automatically scroll my list to the bottom when I'm dragging my new element.
This example below works properly once I drag-and-dropped one time an element in a list.
I believe I need to call once an observable before the drag.
I'm using dragula and dom-autoscrolling.
import {takeUntil} from "rxjs/internal/operators/takeUntil";
import * as autoScroll from 'dom-autoscroller';
const drake = this.dragulaService.find(this.dragulaBagName);
this.dragulaService.drag.pipe(
takeUntil(this.destroyed$),
).subscribe(([bag, movingEl, containerEl]) => {
autoScroll(containerEl.parentNode, {
margin: 20,
pixels: 10,
scrollWhenOutside: true,
autoScroll: function () {
return this.down && drake && drake.drake && drake.drake.dragging;
}
});
});
Apparently, this.down in callback autoScroll is set to false at the beginning... once drag-and-dropped one time, it works correctly.
Any ideas?
try use (mousedown)="initAutoScroll()"
initAutoScroll(){
const drake = this.dragulaService.find(this.dragulaBagName);
this.scroll = autoScroll(
containerEl.parentNode,
{
margin: 30,
maxSpeed: 25,
scrollWhenOutside: true,
autoScroll: function () {
return this.down && drake.drake.dragging;
}
});
}
this.dragulaService.dragend.asObservable().subscribe(value => {
if (this.scroll) {
this.scroll.destroy(); // destroy when don't use auto-scroll
}
});

React native state change transitions

What is the best pattern, in react native, to animate components on state change?
For example I have a list of elements and tapping on one I want it to disappear and the ones below him to 'get up' filling the missing space
How can I make the transition smooth?
React-natives own animated API works really well.
Basically you have a value in state, which you connect with a style props, and change that value over time. (for examples follow link)
For smooth animations use usenativedriver (not always possible) and also, make sure you don't have debugger runnning in emulated/real device
EDIT: 2018-05-31
This is an example of how I've used it. Probably exist other ways of doing it
import { Animated, Text} from 'react-native';
class ShowCaseAnimation extends Component {
state = {
animations: {
height: new Animated.Value(0),
fade: new Animated.Value(0),
}
}
componentDidMount() {
const { height, fade } = this.state.animations;
if (this.props.animate) {
doneAnimation({ height, fade }).start(() => {
// Do stuff after animations
});
}
}
render() {
const { animations } = this.state;
return (
<Animated.View
style={{
height: animate? animations.height : 300,
opacity: animate? animations.fade: 1,
// other styling
}}
>
<Text> All your base are belong to us </Text>
</Animated.View>
);
}
}
*doneAnimation: *
import { Animated, Easing } from 'react-native';
export const doneAnimation = ({ height, fade }) => Animated.parallel([
Animated.timing(height, {
toValue: 300,
easing: Easing.elastic(),
duration: 500,
delay: 1500,
}),
Animated.timing(fade, {
toValue: 1,
easing: Easing.ease,
duration: 1000,
delay: 1500,
}),
]);
export default doneAnimation;
doneAnimation will change the state and perform the described animations.
This is how you can trigger an animation on state change in a functional component.
Say you have a Button that changes state with onPress:
<Button title="toggle" onPress={() => setValue(!Value)} />
then you can trigger the animation inside a useEffect with the Value
that changes in the dependency array:
const [Value, setValue] = useState(true);
useEffect(() => {
// Input your animation here
// ...
}, [Value]);

Imperatively request interpolated value from a React Native animation

I'm trying to retrieve the current color from a react-native animation. It's mapped through interpolate to a set of color strings.
class IconTransition extends React.Component<Props, State> {
protected _param: number = 0;
constructor(props: Props) {
super(props);
this.state = {
param: new Animated.Value(0)
};
this.state.param.addListener(param => {
this._param = param.value;
});
}
componentDidMount() {
Animated.spring(this.state.param, {
mass: 1,
stiffness: 10,
damping: 10,
toValue: 1
});
}
componentWillReceiveProps() {
// I want to do something like this. Would be awesome
// if I could avoid the listener in the constructor.
//
// const currentColor = Animated.interpolate.get({
// currentInput: this._param,
// outputRange: ["#FFFFFF", "#000000"]
// });
}
render() {
return (
<AnimatedIcon
{...this.props}
color={this.state.param.interpolate({
inputRange: [0, 1],
outputRange: ["#FFFFFF", "#000000"]
})}
/>
);
}
}
I want to retrieve the color, as interpolated, should the animation not finish. I'm aware I could probably use an external library such a chroma-js (in particular, the chroma.mix function) to achieve this - but there are different ways to interpolate through two different colors and I'd rather not depend on an external library if I can avoid it.
So... the greater question remains, how can I imperatively request an output value from the interpolation API? Can we not listen on interpolated values, just as we do with Animated.Value()?
I was trying to do the same for a while now and there's a few things you need to keep in mind:
Since you're trying to update a prop that is not a style prop your
best bet is to use the addListener and setNativeProps methods
https://facebook.github.io/react-native/docs/animations#setnativeprops
The interpolation has a __getValue method to check the current value.
This is the function you want to call in the listener to check the
current interpolated value
https://github.com/facebook/react-native/blob/master/Libraries/Animated/src/nodes/AnimatedInterpolation.js#L327
Colors cannot be passed to props like that when set from setNative
props, it has to be passed through processColor
https://github.com/facebook/react-native/blob/master/Libraries/StyleSheet/processColor.js
If you put that all together you can get somthing like the following, which worked in my case:
import React from 'react';
import {View, processColor} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
class BackgroundColorLinearGradientText extends React.Component {
/**
* Class constructor.
*/
constructor(props, context) {
super(props, context);
this.backgroundColor = new Animated.Value(0);
this.backgroundColor.addListener(i => {
let interpolated = this.backgroundColor.interpolate({
inputRange: [0, 1],
outputRange: ['#FF0000', '#00FF00'],
}).__getValue();
if (this.background) {
this.background.setNativeProps({colors: [processColor(interpolated), processColor(this.background.props.colors[1])]})
}
});
}
componentDidMount() {
Animated.timing(this.backgroundColor, {
toValue: 1,
duration: 3000,
}).start();
}
render() {
return (
<LinearGradient ref={i => this.background = i} colors={['red', 'blue']} style={{flex: 1}}>
<View style={{
flex: 1,
justifyContent: 'center',
alignItems: 'center',
}}>
Content
</View>
</LinearGradient>
);
}
}
This will create a screen which has a red to blue gradient background, transitioning to green to blue in three seconds.

Resources