How to make Drag and drop in NativeScript

Raj Bhatt
ashutec
Published in
4 min readApr 14, 2020
An IDE tools image while programming

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.vuefile 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 position
switch (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.

--

--