react-native-gesture-handler
react-native-gesture-handler copied to clipboard
Drag Gesture Handler
EDITED 22/05/21
Motivation #928
- Drag and drop is a commonly used gesture in native platforms and is a top choice for app UI/UX.
- There are many unique attributes and behaviors of the drag and drop gesture that are different than
PanGestureHandler
-> usingPanGestureHandler
requires a lot of boilerplate to get it to function as a synthetic drag and drop handler (I've tried it rn-drag-drop). - Without reanimated it is not responsive. With reanimated it causes a lot of overhead (lots of nodes created for each synthetic handler), which makes it not good enough of a solution (e.g. Using this as items of a
FlatList
is a KILLER). - Drag events between apps are now supported by both platforms. This can be handled only with native drag events. There is no way of achieving this currently in react-native. In this PR I've achieved it on android already.
- The way I see it,
react-native-gesture-handler
aims to be a plug and play library, this is why I think drag and drop gesture handling is essential.
Changes
I've added 2 new handlers: DragGestureHandler
and DropGestureHandler
both of them extend PanGestureHandler
.
I have made adaptions mainly to GestureHandlerOrchestrator
in the android native code and added examples under Drag & Drop
and Drag & Drop in FlatList
.
In addition I've revived the native android example, this was done by adjusting the gesture handler registry and mocking a root view.
The only thing out of scope is this fix, which fixes passing down the wrong handlerTag
to native making relations config wrong for NativeViewGestureHandler
.
https://github.com/software-mansion/react-native-gesture-handler/pull/963/files#diff-6eb54a5e8a566ff2be948cdfe0abb5f4
Logic
Gesture handling is in progress.
PanGestureHandler
super class of DragGestureHandler
receives events. It is in charge of moving from State.UNDETERMINED
to State.BEGAN
and State.ACTIVE
based on it's own logic.
Once active, DragGestureHandler
begins dragging.
From this point on the GestureHandlerOrchestrator
receives a DragEvent
which is first adapted to a MotionEvent
and delivered to all handlers. This is done to activate DropGestureHandlers
(DropGestureHandler
is the same as DragGestureHandler
in terms of PanGestureHandler
responsibilities), run simultaneous handlers and cancel the rest.
Then the DragEvent
is delivered to all handlers, DropGestureHandlers
first, then the rest.
Extracting DropGestureHandlers
is done for each event to obtain the top most handler.
If DropGestureHandlers
are extracted the top most will be activated notifying the other drag/drop handlers with an appropriate action
.
Multi Window
Android supports multi window.
The AndroidManifest.xml
file needs to be edited to get this working.
To persist the same behavior I had to add some logic to bridge the framework not dispatching drag events back to the app that started the drag gesture, leaving DragGestureHandler
unaware of interactions with DropGestureHandlers
. So, I've added a small broadcasting mechanism that broadcasts changes of the drag action
to the source app.
To test this I suggest running both AndroidNativeExample
and Example
side by side.
Simultaneous Handlers
There is a caveat regarding simultaneous handlers. A drag event (on android) has only one pointer so simultaneous handling with RotationGestureHandler
or PinchGestureHandler
won't work unfortunately.
Passing DragGestureHandlers
before the event begins will join them to the drag event and add them to the drag shadow. See example.
Props
- types =
number | number[] | null
, if aDragGestureHandler
and aDropGestureHandler
share one type they can interact.
DragGestureHandler props
- data - pass object to
DropGestureHandler
on drop via gesture event. - Drag Shadow
There are several options here, all listed in
react-native-gesture-handler.d.ts
-
dragMode
- handles the visibilty ofDragGestureHandler
while dragging-
move
-
move-restore
- after drop occurs, restores visibility to the DragGestureHandler's view -
copy
-
none
-
-
shadow
=component | element | tag | null
to render view while dragging - ~~
shadowEnabled
~~ merged asdragMode='none'
-
shadowConfig
- control position, opacity, margin, etc. Drag shadow can update during a drag only fornougat
and higher.
-
Events
Extending PanGestureHandler
event.
type Map = { [index: string]: any };
type DragData<T extends Map> = T & { nativeProps?: Map };
type DropData<T extends Map> = (DragData<T> | {
/**This property will be available if an error occured while trying to parse the data */
rawData: string
}) & { readonly target: number };
export enum DragState {
BEGAN,
ACTIVE,
DROP,
END,
ENTERED,
EXITED
}
export enum DragMode {
MOVE,
/** After drop occurs, restores visibility to the DragGestureHandler's view */
MOVE_RESTORE,
COPY
}
interface DragGestureHandlerEventExtra extends PanGestureHandlerEventExtra {
dragTarget: number,
dragTargets: number[],
dropTarget: number,
dragState: DragState
}
interface DropGestureHandlerEventExtra<T extends Map> extends DragGestureHandlerEventExtra {
/**
* The data received from the DragGestureHandler
*/
data: DropData<T>[] | null,
/**
* The id of the app the event originated from.
* This property will be available once a drop occurs for an event that originated from a different app.
*/
sourceAppID?: string
}
https://github.com/software-mansion/react-native-gesture-handler/pull/963#issuecomment-588223553
I believe iOS will be much easier to implement.
Please consider adding this to the road map. Drag gesture handling is the missing piece of RNGH!
Hi guys,
I had this PR in mind for a few months and now found the time, need and stamina to dive into it.
It was hard at first to understand the inner workings of GestureHandlerOrchestrator
but I pulled through.
The more deeper I went, the more I understood what a fine job you guys do.
Both libraries, this and reanimated, make developing in react-native possible, easy and even effortless, yielding great results.
I wanted it to be even more possible, easy and even effortless, hence this PR.
As far as I'm concerned this is ready for android.
It is stable and seems reliable.
The android native example is alive and kicking, you should take a look. It is very convenient to debug with.
In Depth
-
I've introduced 2 new gesture handlers:
DragGestureHandler
&DropGestureHandler
, both extendPanGestureHandler
. -
Activation logic:
- A touch starts.
-
DragGestureHandler
'sPanGestureHandler
decides if to activate or not. If it does a drag session begins. -
DropGestureHandler
s are continuously activated as the event proceeds and cancelled once the pointer exits their bounds. - The event ends with a drop/end action
-
I've decided on a simple mechanism to determine if a drag event can relate with a drop handler. It is passed as
types: number | number[]
prop. If both share a type the event is delivered. -
Android doesn't fire
MotionEvent
duringDragEvent
so I've synthesized one and usedGestureHandlerOrchestrator
to deliver it normally in order to support simultaneous handlers etc... I think this is a sound approach but might be vulnerable. If issues will rise due to this (e.g. bad MotionEvents) then I suggest adjusting the syntheticMotionEvent
to better impersonate a real one. -
~~Speaking of, I've managed to run a drag handler and a scroll handler simultaneously only on the native example and couldn't get it working in react-native. I hope you guys can suggest the source of this issue, the only thing that I can think of is a root view that needs to listen to
onDrag
as well as intercepting the event (which sounds wrong) or some internal react-native logic.~~ FIXED -
I've written the handlers to be compatible with external drag events coming from different apps (both platforms seem to support this) but there's some minor work/testing to be done to finalize it.
-
Best practice regarding nesting Drag/Drop handlers fora view hierarchy: Drag at top, Drop nested deep.
-
In the example I've tested a few situations. One of them being using the event data only and handling UI with pan event data. This is possible when passing
shadowEnabled=false
. The thing with this is elevation, no problem on the native example (guessing it is caused byShadowNode
overriding it). This is a caveat for future work, drag shadow works well and is the native approach.
Conclusion
Already I can say this saves tons of boilerplate and logic that mimics drag&drop interactions. I am sure it will resolve a lot of performance issues as well, especially when integrated with reanimated. I have an idea for this too, using a PropsNode
to update a drop zone with the data received from the drag event, all without crossing the bridge once. Sounds exciting, and you can see it opens up new possibilities.
Next Up iOS
I found a MAJOR bug that was preventing interaction management with native view handlers. cf8e911 fixes the ability to configure interactions with NativeViewGestureHandler based on createHandler#transformIntoHandlerTags.
Check it out - Multi window support!
@osdnk @kmagiera android is ready for reviewing
Thank you for working on this however I need to point out that this PR is very difficult to review at the current state. What could help is to split it into smaller changes (not sure if I understand correcly but seem like there are some unrelated changes here), and or provide some pointers in the PR description as to what changes are being made, in which files and why.
Apart from that I am not sure if I understand the motivation and why PanGestureHandler cannot be used for the use case you are describing?
@kmagiera please refer to the top. I've edited the first comment, hope it gives a better understanding of this PR in terms of motivation, necessity and logic. I've updated the fork so it is even with master.
android is done
Thanks for your work. I regret to say that this code is unreviewable. I hope it's a valuable code but I'm afraid merging over 4k lines in one batch. Can you maybe split your work in a few independent PRs to make it possible to review?
@osdnk What do you suggest? How should I split the code? sounds awkward. I can't think of an effective way of doing it. Are there any other options?
The only thing I can think of is extracting the android native app upgrade (1K of changes)
So there's no way for making it a bit smaller? I'll be even happy with merging smaller chanks of compilable non-breaking code if we'll be able to review it
I'll try something. What about DMNodes? I've tried pinging you there
@ShaMan123 I tried the examples but for some reason can't get it to work. Views are showing but nothing happens when I long press the items on Android. Tried adding the resizeableActivity flag - any other ideas on how to proceed?
Really excited about this possibility since I'm interested in both cross-app functionality and smooth drag-and-drop in lists. I'd be invested in seeing it working on iOS and the web as well. Running a moto g7 play on Android 9 (React Native 0.62.2).
Which example did you try Drag & Drop
or Drag & Drop in FlatList
? Did you try just dragging without long pressing?
In Example/draggable
index.js
is a simple PanGestureHandler
, not a DragGestureHandler
.
list.js
should work as should drag.js
.
Are you running it in the example app or in your own app? I recommend you first check it works in the example to eliminate issues/wrong config.
And you should pull again and rebuild + change your manifest to match the example's
Awesome work! Looking forward to seeing this merged soon.
I have tried something similar on iOS long time ago but I finally gave up https://github.com/neiker/react-native-drag-n-drop
Hey guys ! I'm really looking for this adding to RNGH ! Thanks a lot for your work @ShaMan123, hope it will be merge soon !
Thank you and looking forward to the new feature soon!
Updated from origin, build passes
fixed a bunch of edge case bugs
I think android is ready for roll out
Would this support web too?
That would be awesome
@ShaMan123 would it be possible to move this PR into a standalone npm package, which uses gesture handler as a peer dependency? That way, you could get community support and testing to help get this merged.
It doesn't seem likely that this will get merged as-is right now, but I'd love to get to use it in my app. I think making it a separate package (like native-dnd
) would be awesome.
What do you think?
I think it isn't feasible. The code can't be separated from the core. And can't be patched together. There is logic that changed. You could PR my fork and I'll merge it. I think this is worth working on to support ios.
בתאריך יום ד׳, 19 במאי 2021, 15:31, מאת Fernando Rojo < @.***>:
@ShaMan123 https://github.com/ShaMan123 would it be possible to move this PR into a standalone npm package, which uses gesture handler as a peer dependency? That way, you could get community support and testing to help get this merged.
It doesn't seem likely that this will get merged as-is right now, but I'd love to get to use this. I think making it a separate package (like native-dnd) would be awesome.
What do you think?
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/software-mansion/react-native-gesture-handler/pull/963#issuecomment-844058932, or unsubscribe https://github.com/notifications/unsubscribe-auth/AIGAW4OW25HSYEEDKHAFZFDTOOVTDANCNFSM4KTI6WOQ .
I see. How far along is it? Is it all working for you?
Android works completely. iOS isn't supported. Clone the fork and run the example app
In
Example/draggable
index.js
is a simplePanGestureHandler
, not aDragGestureHandler
.list.js
should work as shoulddrag.js
. Are you running it in the example app or in your own app? I recommend you first check it works in the example to eliminate issues/wrong config.