Background
Recently I worked on a mobile application which required cool functionality that includes the scenario where the user should be able to rearrange items using gestures. The whole process is to create a puzzle that the user needs to be set in the right order, Sounds crazy.
Application is built using NativeScript-Vue. To fulfil requirements, I used Nativescript Gesture API for the implementation, after applying lots of gestures I ended up with a panning gesture to create drag and drop functionality.
We are going to make drag and drop functionality in ios and Android using NativeScript built with Vue. Here is an animated image of what we are going to build in the next steps.
Here, its GitHub repository https://github.com/rjbhatt110/nativescript-drag-drop-fn of this implementation.
A dropArea (Grey-boxes) and random ordered draggable Object (Color-boxes) appear on the screen. Using gestures, the user is able to drag an object to the drop area to set the right order.
Getting Started
Create a New Project
Before creating a new project I assume that you already set up a NativeScript environment on your system.
Let’s create a new project. From the terminal or command line, execute the following command.
vue init nativescript-vue/vue-cli-template <project-name>
cd <project-name>
tns platform add ios
tns platform add android
tns run ios
tns run android
Developing Basic Drag and drop Features
Starting with the basic code of drag and drop features. open app.vue
file and After that, we begin with UI structure by adding an Image of dropArea with @pan
event and its reference dropArea0
on it.
After adding a UI, it’s time to include logic by adding the following javascript code.
In the above code, we take reference of UI element in the mounted()
a method. Later on, we use that reference as nativeview to access properties of an image using onPan()
events which handle three states of panning gesture.
onPan()
event args.states === 2
we assign our deltaX
and deltaY
to translateX
and translateY
which create dragging.
Adding a More UI Element and make it draggable
Next thing is to add more elements in UI code that contain four dropArea (Grey-boxes) and four draggable Object (Color-boxes) with arrays named boxArray
which bind into an image
tag with v-for
attributes.
We are also updating our logic as well by passing an event, index and dragObject to onPan()
a method as an argument. after that, we create boxArray
for a binding UI element that we used in the v-for
loop and add all references in mounted()
a method.
Handling dropArea and dragObject
It’s time to play with animation, The following code implements scale up animation when an object comes over the drop area. So, we have to write this code for other drop areas as well.
if (this.$refs[ref][0].nativeView.getLocationOnScreen().x >=
this.dropArea0.getLocationOnScreen().x &&
this.$refs[ref][0].nativeView.getLocationOnScreen().x <=
this.dropArea0.getLocationOnScreen().x + this.dropArea0.width &&
this.$refs[ref][0].nativeView.getLocationOnScreen().y >=
this.dropArea0.getLocationOnScreen().y &&
this.$refs[ref][0].nativeView.getLocationOnScreen().y <=
this.dropArea0.getLocationOnScreen().y + this.dropArea0.height
) {
this.dropArea0.scaleX = 1.1;
this.dropArea0.scaleY = 1.1;
} else {
this.dropArea0.scaleX = 1;
this.dropArea0.scaleY = 1;
}
Now we are going to manage animation of the draggable object and start implementation that creates a swappable object from one dropArea to another.
For handling swap between two objects, we use switch case to manage different positions that get by array index. At the end of this condition, we splice boxArray
that helps to get our final position of an object.
if (
// <========== Drop Area 0 ===========>
this.$refs[ref][0].nativeView.getLocationOnScreen().x >=
this.dropArea0.getLocationOnScreen().x &&
this.$refs[ref][0].nativeView.getLocationOnScreen().x <=
this.dropArea0.getLocationOnScreen().x + this.dropArea0.width &&
this.$refs[ref][0].nativeView.getLocationOnScreen().y >=
this.dropArea0.getLocationOnScreen().y &&
this.$refs[ref][0].nativeView.getLocationOnScreen().y <=
this.dropArea0.getLocationOnScreen().y + this.dropArea0.height
) {this.translateX = this.$refs[ref][0].nativeView.translateX;
this.translateY = this.$refs[ref][0].nativeView.translateY;this.dropArea0.scaleX = 1;
this.dropArea0.scaleY = 1;if (this.$refs[ref][0].nativeView.translateX >= -30) {
this.$refs[ref][0].nativeView.translateX = 0;
} else {
this.$refs[ref][0].nativeView.translateX = -160;
}if (this.$refs[ref][0].nativeView.translateY >= -30) {
this.$refs[ref][0].nativeView.translateY = 0;
} else {
this.$refs[ref][0].nativeView.translateY = -140;
}// Translate Animation Start 0
// Handle Animation for swipe object from different positionswitch (ref) {case 1:
if (this.$refs[this.boxArray.indexOf(this.boxArray[0])][0].nativeView.translateX >= 0) {
this.$refs[this.boxArray.indexOf(this.boxArray[0])]
[0].nativeView.translateX = 160 - this.$refs[this.boxArray.indexOf(this.boxArray[0])][0].nativeView.translateX;
} else {
this.$refs[this.boxArray.indexOf(this.boxArray[0])]
[0].nativeView.translateX = 160 + this.$refs[this.boxArray.indexOf(this.boxArray[0])][0].nativeView.translateX;
}if (this.$refs[this.boxArray.indexOf(this.boxArray[0])][0].nativeView.translateY >= 0) {
this.$refs[this.boxArray.indexOf(this.boxArray[0])]
[0].nativeView.translateY = 0 - this.$refs[this.boxArray.indexOf(this.boxArray[0])][0].nativeView.translateY;
} else {
this.$refs[this.boxArray.indexOf(this.boxArray[0])]
[0].nativeView.translateY = 0 + this.$refs[this.boxArray.indexOf(this.boxArray[0])][0].nativeView.translateY;
}
break;case 2:
if (this.$refs[this.boxArray.indexOf(this.boxArray[0])][0].nativeView.translateX >= 0) {
this.$refs[this.boxArray.indexOf(this.boxArray[0])]
[0].nativeView.translateX = 0 - this.$refs[this.boxArray.indexOf(this.boxArray[0])][0].nativeView.translateX;
} else {
this.$refs[this.boxArray.indexOf(this.boxArray[0])]
[0].nativeView.translateX = 0 + this.$refs[this.boxArray.indexOf(this.boxArray[0])][0].nativeView.translateX;
}if (this.$refs[this.boxArray.indexOf(this.boxArray[0])][0].nativeView.translateY >= 0) {
this.$refs[this.boxArray.indexOf(this.boxArray[0])]
[0].nativeView.translateY =140 - this.$refs[this.boxArray.indexOf(this.boxArray[0])][0].nativeView.translateY;
} else {
this.$refs[this.boxArray.indexOf(this.boxArray[0])]
[0].nativeView.translateY = 140 + this.$refs[this.boxArray.indexOf(this.boxArray[0])][0].nativeView.translateY;
}
break;case 3:
if (this.$refs[this.boxArray.indexOf(this.boxArray[0])][0].nativeView.translateX >= 0) {
this.$refs[this.boxArray.indexOf(this.boxArray[0])]
[0].nativeView.translateX = 160 -
this.$refs[this.boxArray.indexOf(this.boxArray[0])][0].nativeView.translateX;
} else {
this.$refs[this.boxArray.indexOf(this.boxArray[0])]
[0].nativeView.translateX = 160 + this.$refs[this.boxArray.indexOf(this.boxArray[0])][0].nativeView.translateX;
}if (this.$refs[this.boxArray.indexOf(this.boxArray[0])][0].nativeView.translateY >= 0) {
this.$refs[this.boxArray.indexOf(this.boxArray[0])]
[0].nativeView.translateY = 140 -
this.$refs[this.boxArray.indexOf(this.boxArray[0])][0].nativeView.translateY;
} else {
this.$refs[this.boxArray.indexOf(this.boxArray[0])]
[0].nativeView.translateY = 140 + this.$refs[this.boxArray.indexOf(this.boxArray[0])][0]
.nativeView.translateY;
}
break;
}// Translate Animation End 0
// Array that control Position drag item
this.boxArray.splice(ref, 1, this.boxArray[0]);
this.boxArray.splice(0, 1, box);
}
Final Code
Finally, It’s time to put everything together adding all elements, code and animation to achieve an expected result.