I want to implement react-native drag-and-drop, to swap component on the drop. supposed there is five component on a screen once I drag it should highlight dropable element. and once it is dropped the component should be swap.
In order to implement drag an drop you need to use the pan responders. First you need to define where are you going to store the values when the object is moving, you can set a property on the state.
state = {
pan: new Animated.ValueXY(),
};
Then you will have to create the pan responders in componentWillMount for example:
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder : () => true,
onPanResponderMove : Animated.event([null,{ // <--- When moving
dx : this.state.pan.x,
dy : this.state.pan.y
}]),
onPanResponderRelease : (e, gesture) => {} // <--- callback when dropped
});
Finally you need to set the left and top to the animated element in the render method.
<Animated.View
{...this.panResponder.panHandlers}
style={[this.state.pan.getLayout(), styles.circle]}>
<Text style={styles.text}>Drag me!</Text>
</Animated.View>
The this.state.pan.getLayout() returns the top and left, you need this in order to move the element around.
In order to swap the elements you need to implement the onPanResponderRelease.
For more detailed steps you can take a look at this tutorial: https://moduscreate.com/blog/animated_drag_and_drop_with_react_native
Related
I’m currently using LayoutAnimation to animate a view when children are added. However, since LayoutAnimation causes everything to be animated, globally, and I can’t easily use built-in Animated library to fit my use-case, I’m wondering if react-native-reanimated is able to help.
Here's a snack of my current solution:
https://snack.expo.io/#insats/height-adapation
This is what the result of that looks like:
Is there a way to achieve the same thing without using LayoutAnimation? I've looked through all exampled in react-native-reanimated, and I've read through the docs but I'm still not sure if this is possible to do or how I should get started. I've thought about using Animated to move the item-wrapper out of the viewable area and "scroll" it upwards (using transform translateY) when items are added, but that would require fixed height, which I don't have.
I have 2 approaches that I can suggest out of my mind:
You can configure your LayoutAnimation only when your desired state changed. If you use hooks it would be too easy:
const [state,setState] = useState([]);
useEffect(()=>{
/*rest code*/
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
},[state])
Or if you use class component you can catch your desired state change in componentDidUpdate:
componentDidUpdate(prevProps,prevState){
if(prevState.items!==state.items){
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
}
}
You can use onLayout function of view:
addItem = () => {
this.setState({
items: [...this.state.items, {title:'An item',isNew:true}]
})
};
renderItems = () => {
return this.state.items.map((item, index) => {
let opacity = new Animated.Value(0);
return (
<Animated.View onLayout={({nativeEvent})=>{
if(this.state.item.isNew){
// here you got the height from nativeEvent.layout.height
// Then you have to store the height animate height and opacity to its precise value
// PS I used opacity:0 to calculate the height
}
}} key={index} style={[styles.item,{opacity}>
<Text>{item.title}</Text>
</View>
)
});
};
When it comes to react-native-reanimated I regard it as more faster version of react-native's Animated library. So either way you will have to calculate the height!
We have already developed a swiper using React Native PanResponder, (before somebody comment) - we cannot use any other swiper library here. In the swiper we developed, we are facing an issue when a user ends the swipe(meaning releases the Pan) there is a lag when starting a spring animation.
So to fix that, we are trying to move from React Native Animated API to Reanimated Libary which could solve the problem of lag the user is facing.
But while developing with React Native Gesture Handler (PanGestureHandler) and
Reanimated Library, we're unable to detect swipe direction in the
gesture handler.
I'm adding the part of code which we used to detect swipe direction in PanResponder
onPanMoving = (evt, {dx}) => {
this.draggedEnd = dx < -this.swipeThreshold; //You are moving towards right screen
this.draggedStart = dx > this.swipeThreshold; //You are moving towards left screen
}
As you see, detecting the direction while the pan is moving was so easy in PanResponder.
Here comes the problem with Gesture Handler,
I'm unable to detect swipe while the gesture state is active
I have already searched for issues in Gesture Handler and I found this.
There were two workaround suggested in the issue
First one is by Sonaye which has two handler and detects the
swipe direction in onHandlerStateChange for both the handlers
which was not helpful while using reanimated because it only sets swipe direction
when handlers state changes.
Second one is by Satya which actually detects the swipe when
the State is ACTIVE but he uses translationX property, the
translationX property can also negative for us and can move in
either direction similar to swiper.
Both the workarounds doesn't solve our problem.
So is there any other way to find direction using PanGestureHandler and Reanimated. I tried using PanResponder with Reanimated with dx value but ended up with error message i.e nativeEvent properties are only support as dx is from gestureState param from PanResponder.
Note: We cannot use FlingGestureHandler and Swipeables because we need to find direction in Fling still onHandlerStateChange and Swipeables doesn't use Reanimated.
You can detect direction when swipe horizontal via property velocityX
Example :
const handleGesture = (evt) => {
const { nativeEvent } = evt;
if (nativeEvent.velocityX > 0) {
console.log('Swipe right');
} else {
console.log('Swipe left');
}
};
return (
<PanGestureHandler onGestureEvent={handleGesture}>
// child component
</PanGestureHandler>
);
For those who want to use double FlingGestureHandler to have vertical or horizontal fling gesture here is the solution:
First you have to initialize the stateHandlers for the direction you want. In my case, i was trying to implement a vertical flingGestureHandler.
const onDownFlingHandlerStateChange = ({
nativeEvent,
}: FlingGestureHandlerStateChangeEvent) => {
if (nativeEvent.oldState === State.ACTIVE) {
progressHeight.value = withTiming(COLLAPSED_HEIGHT, {
duration: ANIMATION_DURATION,
});
}
};
const onUpFlingHandlerStateChange = ({
nativeEvent,
}: FlingGestureHandlerStateChangeEvent) => {
if (nativeEvent.oldState === State.ACTIVE) {
progressHeight.value = withTiming(
DEVICE_HEIGHT - TOP_PADDING_FOR_EXPANDED,
{
duration: ANIMATION_DURATION,
},
);
}
};
and then you have to bind those handlers to 2 FlingGestureHandler component of RNGH
<FlingGestureHandler
onHandlerStateChange={onUpFlingHandlerStateChange}
onEnded={() => {
onPressChecklistIcon();
}}
direction={Directions.UP}>
<FlingGestureHandler
direction={Directions.DOWN}
onEnded={() => {
onPressChecklistIcon();
}}
onHandlerStateChange={onDownFlingHandlerStateChange}>
<Animated.View style={[styles.container, reanimatedStyle]}>
<View style={styles.closerContainer}>
<View style={styles.closer} />
</View>
{renderChecklistContent()}
</Animated.View>
</FlingGestureHandler>
</FlingGestureHandler>
Use translationX property rather than velocityX property by nativeEvent:
const handleGesture = (evt) => {
const { nativeEvent } = evt;
if (nativeEvent.translationX > 0) {
Alert.alert('Swipe right');
} else {
Alert.alert('Swipe left');
}
};
In my case I have used double FlingGestureHandler to detect the direction. It's worked for me.
So i am on RN 49.3,
I am looking for a way to get the scroll position or the index of the item visible on the virtualizedList!
PS: when i opened the source code of VirtualizedList.js which seems to have props.onScrollEndDrag & props.onScroll
We were using a different approach. It has changed a lot since, but I can help you with the initial approach. We added onScroll param to the list. We captured event.nativeEvent.contentOffset.y into the state or some variable inside spec. We used redux. When the user left the screen we saved this value inside a database. Second part was to load this value from the db in componentDidMount . You just put a ref into the list component and then call this.refs.myRef.scrollTo({ x: 0, y: loadedValue, animated: false });
Capture the scroll
render() {
return (
<VirtualizedList
ref='myRef'
onScroll={event => {
this.scroll = event.nativeEvent.contentOffset.y;
}}
...
/>
);}
Save on exit
componentWillUnmount() {
AsyncStorage.setItem(key, this.scroll);
}
Load after mounting
componentDidMount() {
AsyncStorage.getItem(key)
.then(y => {
this.refs.myRef.scrollTo({ x: 0, y, animated: false });
});
}
Best approach I think is to handle it inside redux and you connect this component with the list to the store. I mentioned saving into db because we do save the position for later use, this is only optional and depends on your requirements.
You may use the state as well, but then you need to handle the unnecessary updates
Hello I'm working on react native and I have this code to manage a drag & drop on a element
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onPanResponderMove :
Animated.event([null, {
dx: this.state.pan.x,
dy: this.state.pan.y
}]),
.
.
.
The thing is that I want to be able to manage the Animated.event pass through the onPanResponderMove when I press a button
Something like
If I press a button then OnPanResponderMove so I can use drag & drop
I don't really know how I can do it. Has anybody any key on this?
I am not 100% sure but probably you can just edit the onStartShouldSetPanResponder: () => true. Right now it always returns true but as the function name says maybe you must return wether you like to enable it or not.
So the solution should be something like this:
onStartShouldSetPanResponder: function () {
return this.state.panResponderEnabled
}
And else where you have a button with an onPress property (or anything alike) where you can actually change the state.
How do I find out the x,y position of where an element is positioned on the screen, I'm using React Native v0.14. I have tried the following but it says measure is not a function.
componentDidMount() {
setTimeout(this.measurePosition);
},
measurePosition() {
this.refs.q.measure((a, b, width, height, px,py ) => console.log(width))
},
Option1:
You can use the NativeMethodsMixin. Make sure you've added the mixin to your class
var NativeMethodsMixin = require('NativeMethodsMixin')
...
var MyComponent = React.createClass({
mixins: [NativeMethodsMixin]
...
componentDidMount: function(){
this.refs.element.measure((x,y,w,h,pX,pY) => console.log("dets"))
}
Option 2
You can use the onLayout property on Views. It is available on most components.
render:function(){
...
<View onLayout = {this.onLayout} />
...
},
onLayout:function(event){
console.log(event.nativeEvent.layout)
}
You will get the x,y, width and height
Can you use something like
1. get the browser DOM element using (ReactDOM.findDOMNode())
2. Use the elements getBoundingClientRect()
3. You have top, left, bottom and right coords
this may also be useful Retrieve the position (X,Y) of an HTML element