[Android] WebView 에서 스크롤 중 터치로 멈추면 클릭되는 현상

곰튀김·2022년 4월 28일
0

Android Webview 에서 fling 으로 스크롤 하는 중에 터치로 스크롤을 멈추면 멈추기만 하는게 아니라 클릭이 되버려서 의도치 않는 클릭이 발생하게 된다. (아이폰은 이런거 잘 되던대)

검색을 해봐도 별로 뾰족한 수가 보이지 않는다.
앱이 아니라 크롬 브라우져에서 실행해봐도 동일한 현상이 있긴하더라.

그래서 직접 터치이벤트를 받아서 고쳐본다.

설계하기

  1. 스크롤 중에 touch 이벤트가 오면 무시해버리자.
  2. 스크롤이 멈춘상태면 무시하면 안된다.
  3. 손가락으로 천천히 스크롤해서 멈춰놓으면 무시되면 안된다.
  4. fling 하고 스크롤이 끝난 후에는 무시하면 안된다.

일단 이렇게 요구사항을 정해놓고 하나씩 구현해보면,

클릭 방지하기

터치이벤트가 발생할 때 무시하는 것을 구현하자. 스크롤중에 터치가 일어나서 스크롤이 멈추는 경우에 한해서 한 번만 무시하면 되니까 무시하는 플래스 하나 만들어서 처리해 보자.

override fun onTouch(view: View, event: MotionEvent): Boolean {
    if (event.actionMasked == MotionEvent.ACTION_UP || event.actionMasked == MotionEvent.ACTION_CANCEL) {
        if (needBlockTap) {
            needBlockTap = false
            return true
        }
        return false
    }
    return false
}

touch up 을 시점으로 무시하게 했다. 일단 fling도중에 스크롤이 멈출 때 클리이 발생하지는 않는다.

그럼 스크롤이 시작될 때 저 플래그를 켜줘야 하는데,

override fun onScrollChange(v: View?, cx: Int, cy: Int, ox: Int, oy: Int) {
    needBlockTap = true
}

이렇게 해버리면 fling이 아닌 경우 - 천천히 스크롤시켜서 멈추고 손가락을 떼는 경우 - 도 터치가 무시되버리니까 안된다.

GestureDetector 로 스크롤이 시작되는지, fling이 시작되는지 확인하자.
손가락으로 천천히 스크롤 시키는경우는 무시하면 안되니까 onScroll에서는 플래그를 꺼야 한다. 나는 세로 스크롤만 처리할 꺼니까 onFling에서 가로스크롤인 경우는 플래그를 세우지 않는다.
onSingleTap 같이 정상적인 경우도 플래스를 세우면 안된다.
그래서,

GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {

    override fun onSingleTapUp(e: MotionEvent?): Boolean {
        needBlockTap = false
        return super.onSingleTapUp(e)
    }

    override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
        needBlockTap = false
        return super.onSingleTapConfirmed(e)
    }

    override fun onScroll(e1: MotionEvent?, e2: MotionEvent?, distanceX: Float, distanceY: Float): Boolean {
        needBlockTap = false
        return super.onScroll(e1, e2, distanceX, distanceY)
    }

    override fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, velocityY: Float): Boolean {
        if (abs(velocityX) < abs(velocityY)) {
            needBlockTap = true
        }
        return super.onFling(e1, e2, velocityX, velocityY)
    }
})

fling 중인지 확인하기

GestureDetector 로 onFling 의 시작시점은 아는데, scroll state 가 IDLE 이 되는 시점을 알수가 없다. WebView 는 scroll state를 알려주지도 않는다.
그래서 onScrollChange 될 때마다 기억하고 있다가 가장 마지막에 발생한 이벤트로부터 일정시간 이후를 IDLE로 정한다.

private val subject = PublishSubject.create<Int>()

init {
    subject
            .debounce(SCROLL_IDLE_DETECT_DELAY, TimeUnit.MILLISECONDS)
            .subscribeOn(Schedulers.computation())
            .subscribe { needBlockTap = false }
}

override fun onScrollChange(v: View?, cx: Int, cy: Int, ox: Int, oy: Int) {
    subject.onNext(0)
}

일단, 이정도 원하는 대로 동작한다.
이 기능들을 별도의 클래스로 두고 ViewGroup으로 적용할 수 있도록 했다.

class MyWebView : WebView {
	val scrollHandler = ScrollTouchHandler(context)
    
	init {
	    scrollHandler.apply(this)
    }
    
    override fun onTouchEvent(event: MotionEvent): Boolean {
        scrollHandler.onTouchEvent(event)
        return super.onTouchEvent(event)
    }
}

💾 ScrollTouchHandler.kt

profile
사실주의 프로그래머

0개의 댓글