Skip to content

Commit

Permalink
Add DraggableListener for listening to the view while dragging / movi…
Browse files Browse the repository at this point in the history
…ng the view

- refactor code
- update example code & readme
  • Loading branch information
hyuwah authored and Muhamad Wahyudin committed Jan 29, 2020
1 parent 990f277 commit b363a7e
Show file tree
Hide file tree
Showing 9 changed files with 291 additions and 105 deletions.
104 changes: 82 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# DraggableView
[![](https://jitpack.io/v/hyuwah/DraggableView.svg)](https://jitpack.io/#hyuwah/DraggableView)

DraggableView is an Android library to make floating draggable view easy, currently it only extends from ImageView.
Now you can make any view (or viewgroup) draggable using extensions on Kotlin & provided utils class on Java
DraggableView is an Android library to make floating draggable view easy.

![Preview](https://miro.medium.com/max/314/1*dMzIJlT12hmSTkVkzNnxEQ.gif)

Expand Down Expand Up @@ -35,7 +34,11 @@ dependencies {

## Usage

### Customizable Attributes
### CustomView (XML)

Currently i've only provide CustomView that extends ImageView. For other view, see **Programmatically** usage below

#### Customizable Attributes

**DraggableImageView**

Expand All @@ -44,12 +47,7 @@ Attribute | Value (Default) | XML | Code
Animate | true, false (false) | animate | setAnimate(boolean isAnimate)
Sticky Axis | NON_STICKY, STICKY_AXIS_X, STICKY_AXIS_Y, STICKY_AXIS_XY (NON_STICKY) | sticky | setStickyAxis(int axis)


### Basic sample

#### Using xml view (DraggableImageView)

On Layout XML file
#### On Layout XML file
```xml
<io.github.hyuwah.draggableviewlib.DraggableImageView
android:src="@mipmap/ic_launcher_round"
Expand All @@ -58,18 +56,33 @@ On Layout XML file
android:layout_height="wrap_content"/>
```

On Activity / Fragment file
#### On Activity / Fragment file
```kotlin
var dv = findViewById<DraggableImageView>(R.id.draggableView)
dv.setOnClickListener {
// TODO on click
}
```

#### Using extension (Kotlin only)
You can add a DraggableListener programmatically via `setListener()` method directly on the view, see below explanation about the listener

### Programmatically

#### Using extension (Kotlin)

You can extent any view or viewgroup to be draggable (i.e. Button, FrameLayout, Linearlayout, LottieView, etc)

```kotlin
// Method signature
fun View.makeDraggable(
stickyAxis: Draggable.STICKY = Draggable.STICKY.NONE,
animated: Boolean = true,
draggableListener: DraggableListener? = null
){
...
}
```

Here's some example using TextView:

```xml
Expand All @@ -83,22 +96,44 @@ Here's some example using TextView:
```kotlin
var tv = findViewById<TextView>(R.id.tv_test_draggable)

tv.makeDraggable(Draggable.STICKY.AXIS_X, true) // default is STICKY.NONE & animated true
tv.makeDraggable(Draggable.STICKY.AXIS_X, false) // set sticky axis to x & animation to false
tv.makeDraggable(Draggable.STICKY.AXIS_XY) // set sticky axis to xy
tv.makeDraggable() // all default

// First param is the axis:
// First param is the axis (optional)
// - Draggable.STICKY.AXIS_X
// - Draggable.STICKY.AXIS_Y
// - Draggable.STICKY.AXIS_XY
// - Draggable.STICKY.NONE
// - Draggable.STICKY.NONE (default)

// Second param is animation flag (optional)
// - true or false (default is true)
// *Sticky.NONE doesn't get affected by this flag

// Second param is animation toggle
// - true or false
// Third param is listener (optional)
// - DraggableListener implementation (default is null)
```

#### Using DraggableUtils (on Java)
#### Using DraggableUtils (Java)

If you're on java class, you could do it with the help of DraggableUtils

```java
class DraggableUtils {

// Method signature
public static void makeDraggable(
View $self,
Draggable.STICKY stickyAxis,
boolean animated,
DraggableListener draggableListener
) {
...
}
}

```

Here's some example using Button:

```xml
Expand All @@ -112,20 +147,45 @@ Here's some example using Button:
```java
Button button = findViewById(R.id.tv_test_draggable);

DraggableUtils.makeDraggable(button, Draggable.STICKY.AXIS_X, true); // default is STICKY.NONE & animated true
DraggableUtils.makeDraggable(button, Draggable.STICKY.AXIS_X, false) // set sticky axis to x & animation to false
DraggableUtils.makeDraggable(button, Draggable.STICKY.AXIS_XY) // set sticky axis to xy
DraggableUtils.makeDraggable(button) // all default

// First param is the view

// Second param is the axis:
// Second param is the axis (optional)
// - Draggable.STICKY.AXIS_X
// - Draggable.STICKY.AXIS_Y
// - Draggable.STICKY.AXIS_XY
// - Draggable.STICKY.NONE
// - Draggable.STICKY.NONE (default)

// Third param is animation flag (optional)
// - true or false (default is true)
// *Sticky.NONE doesn't get affected by this flag

// Third param is animation toggle
// - true or false
// Fourth param is listener (optional)
// - DraggableListener implementation (default is null)
```

#### DraggableListener
There's an interface `DraggableListener` to listen to the `View` while being dragged / moved

```kotlin
interface DraggableListener {
fun onViewMove(view: View)
}
```
Just pass the implementation of the interface to `makeDraggable` method

```kotlin
someView.makeDraggable(object: DraggableListener{
override fun onViewMove(view: View){
// Do something, get coordinates of view, etc
}
})

// *Java counterpart must supply all 3 other params to use the listener
```

Check example module [kotlin](https://github.com/hyuwah/DraggableView/blob/master/example/src/main/java/io/github/hyuwah/draggableview/MainActivity.kt), [java](https://github.com/hyuwah/DraggableView/blob/master/example/src/main/java/io/github/hyuwah/draggableview/JavaMainActivity.java) for actual implementation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ object Draggable {
}

const val DRAG_TOLERANCE = 16
const val DURATION_MILLIS = 250L
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import android.view.View
import android.widget.ImageView
import io.github.hyuwah.draggableviewlib.Draggable.DRAG_TOLERANCE
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min

/**
* 29/01/2019
Expand All @@ -25,10 +27,12 @@ class DraggableImageView(context: Context, attrs: AttributeSet) : ImageView(cont
private var stickyAxis: Int
private var mAnimate: Boolean

private var draggableListener: DraggableListener? = null

// Coordinates
private var widgetXFirst: Float = 0F
private var widgetInitialX: Float = 0F
private var widgetDX: Float = 0F
private var widgetYFirst: Float = 0F
private var widgetInitialY: Float = 0F
private var widgetDY: Float = 0F

init {
Expand All @@ -49,90 +53,116 @@ class DraggableImageView(context: Context, attrs: AttributeSet) : ImageView(cont
*/
private fun draggableSetup() {
this.setOnTouchListener { v, event ->
val viewParent: View = (v.parent as View)
val PARENT_HEIGHT = viewParent.height
val PARENT_WIDTH = viewParent.width
val viewParent = v.parent as View
val parentHeight = viewParent.height
val parentWidth = viewParent.width
val xMax = parentWidth - v.width
val xMiddle = parentWidth / 2
val yMax = parentHeight - v.height
val yMiddle = parentHeight / 2

when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
this.widgetDX = v.x - event.rawX
this.widgetDY = v.y - event.rawY
this.widgetXFirst = v.x
this.widgetYFirst = v.y
widgetDX = v.x - event.rawX
widgetDY = v.y - event.rawY
widgetInitialX = v.x
widgetInitialY = v.y
}
MotionEvent.ACTION_MOVE -> {
// Screen border Collision
var newX = event.rawX + this.widgetDX
newX = Math.max(0F, newX)
newX = Math.min((PARENT_WIDTH - v.width).toFloat(), newX)
var newX = event.rawX + widgetDX
newX = max(0F, newX)
newX = min(xMax.toFloat(), newX)
v.x = newX

var newY = event.rawY + this.widgetDY
newY = Math.max(0F, newY)
newY = Math.min((PARENT_HEIGHT - v.height).toFloat(), newY)
var newY = event.rawY + widgetDY
newY = max(0F, newY)
newY = min(yMax.toFloat(), newY)
v.y = newY

draggableListener?.onViewMove(v)
}
MotionEvent.ACTION_UP -> {
// If Sticky
when (this.stickyAxis) {
when (stickyAxis) {
STICKY_AXIS_X -> {
if (event.rawX >= PARENT_WIDTH / 2) {
if (this.mAnimate)
v.animate().x((PARENT_WIDTH) - (v.width).toFloat()).setDuration(250).start()
if (event.rawX >= xMiddle) {
if (mAnimate)
v.animate().x(xMax.toFloat())
.setDuration(Draggable.DURATION_MILLIS)
.setUpdateListener { draggableListener?.onViewMove(v) }
.start()
else
v.x = (PARENT_WIDTH) - (v.width).toFloat()
v.x = xMax.toFloat()
} else {
if (this.mAnimate)
v.animate().x(0F).setDuration(250).start()
if (mAnimate)
v.animate().x(0F).setDuration(Draggable.DURATION_MILLIS)
.setUpdateListener { draggableListener?.onViewMove(v) }
.start()
else
v.x = 0F
}
}
STICKY_AXIS_Y -> {
if (event.rawY >= PARENT_HEIGHT / 2) {
if (this.mAnimate)
v.animate().y((PARENT_HEIGHT) - (v.height).toFloat()).setDuration(250).start()
if (event.rawY >= yMiddle) {
if (mAnimate)
v.animate().y(yMax.toFloat())
.setDuration(Draggable.DURATION_MILLIS)
.setUpdateListener { draggableListener?.onViewMove(v) }
.start()
else
v.y = (PARENT_HEIGHT) - (v.height).toFloat()
v.y = yMax.toFloat()
} else {
if (this.mAnimate)
v.animate().y(0F).setDuration(250).start()
if (mAnimate)
v.animate().y(0F)
.setDuration(Draggable.DURATION_MILLIS)
.setUpdateListener { draggableListener?.onViewMove(v) }
.start()
else {
if (this.mAnimate)
v.animate().y(0F).setDuration(250).start()
if (mAnimate)
v.animate().y(0F).setDuration(Draggable.DURATION_MILLIS)
.setUpdateListener { draggableListener?.onViewMove(v) }
.start()
else
v.y = 0F
}
}
}
STICKY_AXIS_XY -> {
if (event.rawX >= PARENT_WIDTH / 2) {
if (this.mAnimate)
v.animate().x((PARENT_WIDTH) - (v.width).toFloat()).setDuration(250).start()
if (event.rawX >= xMiddle) {
if (mAnimate)
v.animate().x(xMax.toFloat())
.setDuration(Draggable.DURATION_MILLIS)
.setUpdateListener { draggableListener?.onViewMove(v) }
.start()
else
v.x = (PARENT_WIDTH) - (v.width).toFloat()
v.x = xMax.toFloat()
} else {
if (this.mAnimate)
v.animate().x(0F).setDuration(250).start()
if (mAnimate)
v.animate().x(0F).setDuration(Draggable.DURATION_MILLIS)
.setUpdateListener { draggableListener?.onViewMove(v) }
.start()
v.x = 0F
}

if (event.rawY >= PARENT_HEIGHT / 2) {
if (this.mAnimate)
v.animate().y((PARENT_HEIGHT) - (v.height).toFloat()).setDuration(250).start()
if (event.rawY >= yMiddle) {
if (mAnimate)
v.animate().y(yMax.toFloat())
.setDuration(Draggable.DURATION_MILLIS)
.setUpdateListener { draggableListener?.onViewMove(v) }
.start()
else
v.y = (PARENT_HEIGHT) - (v.height).toFloat()
v.y = yMax.toFloat()
} else {
if (this.mAnimate)
v.animate().y(0F).setDuration(250).start()
if (mAnimate)
v.animate().y(0F).setDuration(Draggable.DURATION_MILLIS)
.setUpdateListener { draggableListener?.onViewMove(v) }
.start()
else
v.y = 0F
}
}
}

// Will register as clicked if not moved for 16px in both X & Y
if (abs(v.x - widgetXFirst) <= DRAG_TOLERANCE && abs(v.y - widgetYFirst) <= DRAG_TOLERANCE) {
if (abs(v.x - widgetInitialX) <= DRAG_TOLERANCE && abs(v.y - widgetInitialY) <= DRAG_TOLERANCE) {
performClick()
}
}
Expand Down Expand Up @@ -169,4 +199,8 @@ class DraggableImageView(context: Context, attrs: AttributeSet) : ImageView(cont
invalidate()
requestLayout()
}

fun setListener(draggableListener: DraggableListener?) {
this.draggableListener = draggableListener
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.github.hyuwah.draggableviewlib

import android.view.View

interface DraggableListener {

fun onViewMove(view: View)

}
Loading

0 comments on commit b363a7e

Please sign in to comment.