Alkisah ketika sedang mendevelop prototipe UI mesin vending powerbank ReCharge yang dapat menampilkan iklan layar penuh, ada kebutuhan untuk suatu elemen / widget yang bisa dipindah-pindah di layar. Waktu itu yang terbayang adalah chat head nya Facebook dan lucky egg nya Tokopedia.
Bare Minimum
private var widgetDX: Float = 0F
private var widgetDY: Float = 0F
fun setupStickyDraggable(){
iv_sticky_draggable.setOnTouchListener { v, event ->
when(event.actionMasked){
MotionEvent.ACTION_DOWN -> {
widgetDX = v.x - event.rawX
widgetDY = v.y - event.rawY
}
MotionEvent.ACTION_MOVE -> {
v.x = event.rawX + widgetDX
v.y = event.rawY + widgetDY
}
else -> {
return@setOnTouchListener false
}
}
true
}
}
Yang pertama adalah bare minimum, tujuannya disini agar view bisa digeser-geser dulu.
Disini kita set setOnTouchListener pada sebuah view iv_sticky_draggable
(dalam kasus ini ImageView). Di setOnTouchListener ada dua parameter yaitu v
(view itu sendiri) dan event.
Event yang kita butuhkan adalah ACTION_DOWN
& ACTION_MOVE
. Di line 8–9 kita simpan dulu koordinat dari view dikurangi koordinat absolut titik sentuh ke widgetDX & widgetDY (ketika ACTION_DOWN
).
v.x & v.y adalah posisi relative view terhadap koordinat awal view.
event.rawX & event.rawY adalah koordinat absolut dari titik sentuh pada layar
Ketika ACTION_MOVE
(line 12–13), kita update koordinat view dengan nilai yang sudah disimpan tadi, ditambah dengan nilai pergerakkan titik sentuh di layar, dengan begitu si view tersebut akan tergeser mengikuti sentuhan jari kita dilayar. That’s it.
Screen Border Collision
private var widgetDX: Float = 0F
private var widgetDY: Float = 0F
fun setupStickyDraggable(){
iv_sticky_draggable.setOnTouchListener { v, event ->
val viewParent:View = (v.parent as View)
val PARENT_HEIGHT = viewParent.height
val PARENT_WIDTH = viewParent.width
when(event.actionMasked){
MotionEvent.ACTION_DOWN -> {
widgetDX = v.x - event.rawX
widgetDY = v.y - event.rawY
}
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)
v.x = newX
var newY = event.rawY + this.widgetDY
newY = Math.max(0F, newY)
newY = Math.min((PARENT_HEIGHT - v.height).toFloat(), newY)
v.y = newY
}
else -> {
return@setOnTouchListener false
}
}
true
}
}
Oke viewnya sudah bisa bergeser, tapi masalahnya, view itu bisa hilang dari layar kalau kita geser ke tepian layar. Solusinya adalah dengan membatasi nilai X dan Y dalam rentang 0 hingga nilai maksimal Width dan Height parent dari view tersebut.
Jadi pertama kita ambil nilai Width dan Height (line 6–8). Kita harus cast parent dari view menjadi View
agar dapat mengakses properties Width & Height-nya.
Disini saya batasi dengan bantuan Math.max()
& Math.min()
. Line 18 & 23 artinya bila nilai negatif maka akan dipaksa menjadi 0, sedangkan line 19 & 24 artinya nilai akan dipaksa menjadi PARENT_HEIGHT
dikurangi tinggi si view bila nilainya lebih besar dari itu. Setelah melalui 2 pengecekan (pada setiap axis) tersebut barulah di set ke Width & Height pada view.
Sticky to Screen Border
private var widgetDX: Float = 0F
private var widgetDY: Float = 0F
fun setupStickyDraggable(){
iv_sticky_draggable.setOnTouchListener { v, event ->
val viewParent:View = (v.parent as View)
val PARENT_HEIGHT = viewParent.height
val PARENT_WIDTH = viewParent.width
when(event.actionMasked){
MotionEvent.ACTION_DOWN -> {
widgetDX = v.x - event.rawX
widgetDY = v.y - event.rawY
}
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)
v.x = newX
var newY = event.rawY + this.widgetDY
newY = Math.max(0F, newY)
newY = Math.min((PARENT_HEIGHT - v.height).toFloat(), newY)
v.y = newY
}
MotionEvent.ACTION_UP -> {
// Stick to Left or Right screen
if(event.rawX >= PARENT_WIDTH / 2)
v.x = (PARENT_WIDTH) - (v.width).toFloat()
else
v.x = 0F
// Stick to Top or Bottom screen
if(event.rawY >= PARENT_HEIGHT / 2)
v.y = (PARENT_HEIGHT) - (v.height).toFloat()
else
v.y = 0F
// IF BOTH X & Y set to stick, the view will only stick to corner
}
else -> {
return@setOnTouchListener false
}
}
true
}
}
Agar DraggableView nya tidak menghalangi user, kita bisa tambah behavior sticky, jadi setelah di-drag, view akan menempel pada tepian layar (atau parent view) baik dalam sumbu x, sumbu y atau keduanya.
Untuk implementasinya digunakan MotionEvent.ACTION_UP
, sebagai contoh agar sticky pada sumbu X (line 29–32), bila koordinat titik sentuh berada di tengah ke kanan, maka view akan diset ke screen border kanan (PARENT_HEIGHT - v.height), bila berada di tengah ke kiri, maka view akan diset ke screen border kiri (0F). Begitu pula untuk sumbu Y (line 35–38).
Sticky to First Position
private var widgetDX: Float = 0F
private var widgetDY: Float = 0F
// Add these
private var widgetXOrigin : Float = 0F
private var widgetYOrigin : Float = 0F
fun setupStickyDraggable(){
iv_sticky_draggable.setOnTouchListener { v, event ->
val viewParent:View = (v.parent as View)
val PARENT_HEIGHT = viewParent.height
val PARENT_WIDTH = viewParent.width
when(event.actionMasked){
MotionEvent.ACTION_DOWN -> {
widgetDX = v.x - event.rawX
widgetDY = v.y - event.rawY
// save widget origin coordinate
widgetXOrigin = v.x
widgetYOrigin = 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)
v.x = newX
var newY = event.rawY + this.widgetDY
newY = Math.max(0F, newY)
newY = Math.min((PARENT_HEIGHT - v.height).toFloat(), newY)
v.y = newY
}
MotionEvent.ACTION_UP -> {
// Back to original position
v.x = widgetXOrigin
v.y = widgetYOrigin
}
else -> {
return@setOnTouchListener false
}
}
true
}
}
Behavior lain yang dapat kita implementasikan adalah Sticky to first position. Disini kita perlu menyimpan nilai koordinat awal view (line 18–19) pada variabel mis. widgetXOrigin & widgetYOrigin (line 4–5) saat ACTION_DOWN
, kemudian pada ACTION_UP
kita tinggal set koordinat view pada nilai koordinat awal tersebut (line 35–36)
Animation
Lebih lanjut lagi, agar gerakkan pada behavior sticky-nya smooth, kita bisa menggunakan .animate()
pada view tersebut. Jadi alih-alih meng-assign langsung dengan =
, kita gunakan seperti ini:
// TO ANIMATE USE animate()
v.animate().x(0F).setDuration(250).start()
// INSTEAD OF
v.x = 0F
Berikut hasilnya:
Ready to use Library
Untuk memudahkan kalian mengimplementasi hal-hal diatas, saya buat sebuah library open-source yang mencakup hal-hal diatas, silahkan cek disini:
hyuwah / DraggableView
DraggableView is an Android library to make floating draggable view easily using extensions on Kotlin & provided utils class on Java
Sekian cerita kali ini mengenai implementasi Draggable View di Android.
Semoga membantu & selamat mencoba :)
Originally posted on Medium
Cover Photo by Marcel Walter on Unsplash
Top comments (0)