Optimize more Swiping behavior in View Pager

ViewPager merupakan salah satu LayoutManager yang memungkinkan pengguna untuk membalik ke kiri dan kanan suatu halaman yang berisi data-data. Anda membutuhkan implementasi PagerAdapter untuk dapat menghasilkan halaman yang akan ditampilkan.

Untuk penjelasanya mungkin tidak terlalu dibutuhkan disini karena Anda pasti sudah sering mengimplementasikanya (jika ingin membaca lebih), namun disini saya akan lebih membahas ke arah perilaku Swipe yang merupakan keunggulan utama dari ViewPager ini.

NB : Jika anda menggunakan TabLayout atau sejenisnya yang dapat berfungsi sama halnya dengan swipe pada ViewPager silahkan disable pada event onClicknya.

Contoh code for disabling TabLayout :

tabs_main.apply {

    setupWithViewPager(vp_checksheet)
    post {
        getTabAt(0)?.select()

        clearOnTabSelectedListeners()
        for (v in tabs_main.touchables) {
            v.isEnabled = false
        }
    }
}

  1. Disable Swipe ke kiri maupun ke kanan ( SwipeViewPager )

Yang pertama cukup singkat, class ini dibuat untuk menonaktifkan seluruh fungsi swipe (baik ke kiri maupun ke kanan) pada ViewPager.

Kunci dari class ini hanya ada di penambahan fungsi singkat untuk menambah flag boolean yang akan digunakan sebagai return nantinya :

private var swipe: Boolean = true  

fun setSwipe(enable: Boolean) {
    swipe = enable
}  

Variabel swipe kita inisialisasi dengan nilai true agar jika Anda menggunakan class ini tanpa kebutuhan disabling swipe juga tetap bisa digunakan tanpa mengimplementasikan fungsi dari setSwipe.

override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
    return swipe && super.onInterceptTouchEvent(ev)
}

Method onInterceptTouchEvent digunakan untuk mencegat semua eventMotion dan Anda dapat memiliki akses untuk mengontrol gerakan di saat ini. Fungsi ini perlu digunakan juga secara hati-hati karena memiliki interaksi yang cukup rumit dengan View.onTouchEvent (MotionEvent) :

override fun performClick(): Boolean {
    return swipe && super.performClick()
}

override fun onTouchEvent(ev: MotionEvent?): Boolean {
    performClick()
    return swipe && super.onTouchEvent(ev)
}

Semua event sentuhan yang terjadi pada layar ViewPager akan terbaca di onTouchEvent ini dan dikarenakan event sentuhan juga merupakan event click maka perlu meng-override juga fungsi performClick.

Untuk implementasi disabling swipe nya sangat simple cukup panggil fungsi untuk merubah flag “swipe” yang terdapat pada class SwipeViewPager tadi :

swipeViewPager.setSwipe(false)

Full Code of SwipeViewPager class :
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import androidx.viewpager.widget.ViewPager

class SwipeViewPager(
    context: Context,
    attrs: AttributeSet? = null
) : ViewPager(context, attrs) {

    private var swipe: Boolean = true

    fun setSwipe(enable: Boolean) {
        swipe = enable
    }

    override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
        return swipe && super.onInterceptTouchEvent(ev)
    }

    override fun performClick(): Boolean {
        return swipe && super.performClick()
    }

    override fun onTouchEvent(ev: MotionEvent?): Boolean {
        performClick()
        return swipe && super.onTouchEvent(ev)
    }
}

2. Disabling specific swipe direction ( DirectionalViewPager )

Pertama untuk memudahkan implementasi saya menggunakan Enum Class untuk membedakan event swipe yang diinginkan karena akan ada lebih dari 2 kondisi dan sudah pasti tidak memungkinkan untuk menggunakan flag boolean seperti pada SwipeViewPager.

SwipeDirection enum :
enum class SwipeDirection {
    ALL, LEFT, RIGHT, NONE
}

Lalu satu fungsi tambahan yang sebetulnya opsional dan hanya untuk membuktikan bahwa ViewPager ini dapat berfungsi ketika dilakukan capture screen nantinya yaitu memunculkan Toast , saya membuat fungsi custom Toast karena nantinya akan diimplementasikan untuk MotionEvent.ACTION_MOVE yang meliputi MotionEvent.ACTION_DOWN & MotionEvent.ACTION_UP :

private lateinit var mToast: Toast
private fun showAToast(message: String?) {
    try {
        mToast.view.isShown // true if visible
        mToast.setText(message)
    } catch (e: java.lang.Exception) {  // invisible if exception
        mToast = Toast.makeText(context, message, Toast.LENGTH_SHORT)
    }
    mToast.show()
}

Toast ini hanya akan ditampilkan apabila sudah tidak terdapat Toast dilayar sehingga tidak akan mengganggu pengguna sekalipun akan diimplementasikan nantinya.

Kemudian buat fungsi untuk mengontrol masing-masing event swipe :

private var initialXValue = 0f
private var direction: SwipeDirection 

private fun isSwipeAllowed(event: MotionEvent): Boolean {
        if (direction === SwipeDirection.ALL) return true
        if (direction === SwipeDirection.NONE)
            return false
        if (event.action == MotionEvent.ACTION_DOWN) {
            initialXValue = event.x
            return true
        }
        if (event.action == MotionEvent.ACTION_MOVE) {
            try {
                val diffX = event.x - initialXValue
                if (diffX > 0 && direction === SwipeDirection.RIGHT) {
                    return false
                } else if (diffX < 0 && direction === SwipeDirection.LEFT) {

showAToast("You need to ensure you fill all the question fields before accessing the next form")
                    return false
                }
            } catch (exception: Exception) {
                exception.printStackTrace()
            }
        }
        return true
    }

Perlu diperhatikan karena ini ViewPager seperti layaknya ViewPager biasa yang memiliki swipe secara horizontal maka yang kita butuhkan hanya nilai X nya saja.

  • if (direction === SwipeDirection.ALL)

Memungkinkan ViewPager dapat melakukan swipe seperti biasanya ( tanpa adanya disabling event swipe )

  • if (direction === SwipeDirection.NONE)

Fungsi ini akan berjalan seperti halnya menggunakan SwipeViewPager dengan memanggil fungsi SwipeViewPager.setSwipe(false) yang akan membuat ViewPager tidak dapat di swipe ke kiri dan ke kanan.

 if (event.action == MotionEvent.ACTION_DOWN) {
            initialXValue = event.x }

Di MotionEvent.ACTION_DOWN kita harus meng-handle dan menginisialisasi nilai koordinat X karena di event ini adalah dimana terjadi sentuhan pada layar. Dan MotionEvent.ACTION_UP sendiri adalah event dimana sentuhan dilepaskan dari layar dan sampai sejauh ini event ini tidak dibutuhkan untuk class ini (saya rasa).

Lalu kita hitung perbandingan dari koordinat X sebelum dan sesudah terjadinya MotionEvent.ACTION_MOVE dengan mengurangkanya :

val diffX = event.x - initialXValue

  • if (diffX > 0 && direction === SwipeDirection.RIGHT)

Jika perbandingan value X bernilai positif maka dapat kita simpulkan bahwa pengguna sedang menempelkan sesuatu di layarnya dan menggeser sesuatu tersebut ke arah kanan.

  • if (diffX < 0 && direction === SwipeDirection.LEFT)

Jika perbandingan value X bernilai negatif maka dapat kita simpulkan bahwa pengguna sedang menempelkan sesuatu di layarnya dan menggeser sesuatu tersebut ke arah kiri. Dan disini saya mengimplementasikan fungsi Toast tadi karena normalnya ViewPager akan ditampilkan pada posisi 0 dan akan kita coba untuk mengimplementasikan dimana tidak bisa di swipe ke kanan (swipe ke kanan = sesuatu tersebut harus diseret ke kiri sehingga menghasilkan nilai X negatif).

Untuk implementasinya cuku memanggil method :

if(unfilledChecksheet>0){

DirectionalViewPager.setAllowedSwipeDirection(SwipeDirection.LEFT)

}

Flow dari test yang akan dilakukan adalah pengguna tidak dapat berpindah ke halaman berikutnya (halaman sebelah kanan) apabila belum mengisi keseluruhan radioButton yang terdapat pada halaman saat ini namun dapt kembali ke halaman sebelumnya (halaman sebelah kiri) meski halaman saat ini belum terisi semua.

Pengguna tidak dapat melakukan swipe ke kanan apabila belum mengisi seluruh radioButton pada halaman saat ini.

Pengguna dapat melakukan swipe ke kanan (halaman berikutnya) apabila semua radioButton pada halaman saat ini sudah terisi dan juga dapat kembali ke halaman sebelumnya, namun kembali tak bisa swipe ke kanan untuk menuju halaman ketiga dikarenakan radioButton pada halam ke dua belum terisi seluruhnya.

Full Code for DirectionalViewPager :

/**
 * Created by rizkyagungramadhan@gmail.com
 * on 4/17/2020.
 */
class DirectionalViewPager(context: Context, attrs: AttributeSet?) :
    ViewPager(context, attrs) {

    private var initialXValue = 0f
    private var direction: SwipeDirection
    private lateinit var mToast: Toast

    override fun onTouchEvent(event: MotionEvent): Boolean {
        return if (isSwipeAllowed(event)) {
            super.onTouchEvent(event)
        } else false
    }

    override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
        return if (isSwipeAllowed(event)) {
            super.onInterceptTouchEvent(event)
        } else false
    }

    private fun isSwipeAllowed(event: MotionEvent): Boolean {
        if (direction === SwipeDirection.ALL) return true
        if (direction === SwipeDirection.NONE)
            return false
        if (event.action == MotionEvent.ACTION_DOWN) {
            initialXValue = event.x
            return true
        }
        if (event.action == MotionEvent.ACTION_MOVE) {
            try {
                val diffX = event.x - initialXValue
                if (diffX > 0 && direction === SwipeDirection.RIGHT) {
                    return false
                } else if (diffX < 0 && direction === SwipeDirection.LEFT) {
                    showAToast("You need to ensure you fill all the question fields before accessing the next form")
                    return false
                }
            } catch (exception: Exception) {
                exception.printStackTrace()
            }
        }
        return true
    }

    fun setAllowedSwipeDirection(direction: SwipeDirection) {
        this.direction = direction
    }

    private fun showAToast(message: String?) {
        try {
            mToast.view.isShown
            mToast.setText(message)
        } catch (e: java.lang.Exception) {
            mToast = Toast.makeText(context, message, Toast.LENGTH_SHORT)
        }
        mToast.show()
    }

    init {
        direction = SwipeDirection.ALL
    }
}

Akhir kata jika ada salah kata atau pemahaman dari saya mohon koreksinya 🙂

Rizky Agung Ramadhan has written 10 articles

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>