I'm using a gesture detector to capture scroll events from a scroll view:
val gestureDetector = GestureDetector(this.fragment.activity, ScrollGestureListener(scrollView))
scrollView.setOnTouchListener(OnTouchListener { view, event ->
gestureDetector.onTouchEvent(event)
return@OnTouchListener false
})
internal inner class ScrollGestureListener(view: View) : GestureDetector.SimpleOnGestureListener() {
override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
[email protected]()
return true
}
}
When using compileSdkVersion 30, my app would crash on the onScroll
function because that always received a null value for the first argument. I worked around that by making the first argument optional:
override fun onScroll(e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
Now I'm trying to update my compileSdkVersion to 33, and the above line will no longer compile. But if I revert back to the standard function signature, then the app crashes again when I scroll the scroll view.
A comment on this SO post says, "You'll get a crash like this if something other than the gesture detector consumes ACTION_DOWN events." But I can't figure out what else would be doing that. I have gesture detectors on some other views in this fragment, but if I comment out all that code, the crashes remain.
I temporarily added an implementation of onDown
to my ScrollGestureListener
and it was never called, so that seems related to the ACTION_DOWN comment.
Looking at the source code for GestureDetector.java
, I see that the first argument it sends to onScroll
is mCurrentDownEvent
, so that also seems related to the ACTION_DOWN comment. But if I add breakpoints to GestureDetector.java
in Android Studio, mCurrentDownEvent
never shows as null. Also, it's always the same as the event passed as the second argument (ev
) -- GestureDetector
simply sets mCurrentDownEvent
to a copy of ev
.
It looks like the crash occurs when GestureDetector
calls onScroll
, because a breakpoint in my onScroll
method isn't reached, and the same crash happens if I remove my onScroll
override -- apparently just calling the superclass implementation of onScroll
causes a crash. Here's what appears in the console:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.arlomedia.bandhelper, PID: 18797
java.lang.NullPointerException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkNotNullParameter, parameter e1
at com.arlomedia.bandhelper.helpers.DocumentViewer$ScrollGestureListener.onScroll(Unknown Source:2)
at android.view.GestureDetector.onTouchEvent(GestureDetector.java:788)
at com.arlomedia.bandhelper.helpers.DocumentViewer.viewDocument$lambda-9(DocumentViewer.kt:1398)
at com.arlomedia.bandhelper.helpers.DocumentViewer.$r8$lambda$kyj-4h2jNAIkIYOWSE7_HVetJAg(Unknown Source:0)
at com.arlomedia.bandhelper.helpers.DocumentViewer$$ExternalSyntheticLambda7.onTouch(Unknown Source:6)
at android.view.View.dispatchTouchEvent(View.java:15147)
I've tried adding some kind of check to my OnTouchListener
, before calling onTouchEvent
-- something like this:
if (event != null) {
gestureDetector.onTouchEvent(event)
}
But event
is never null here, and when looking at its properties, I don't see anything I could check to determine whether it will cause a crash.
Another potential clue is that onScroll
is only called if my finger is still on the screen when calling onTouchEvent
. That's normally the case, but I tried delaying its call like this:
val runnable = Runnable {
gestureDetector.onTouchEvent(event)
}
App.instance.timerHandler.postDelayed(runnable, 1000)
Then if I perform a scroll and lift my finger off the screen within one second, onScroll
is not called and there is no crash. If I perform a scroll and leave my finger on the screen for more than one second, onScroll
is called and there is a crash.
I can imagine two approaches to fixing this: validating the event in my OnTouchListener
before calling onTouchEvent
, or figuring out what is causing GestureDetector
to send invalid events to onScroll
. But I've run out of ideas on both. Does anyone else have an idea?
I rebuilt this functionality one step at a time in a new fragment to isolate the problem. The scroll view was wrapped around a TextView
in order to make the text view flingable (inertial scrolling), but I also had an OnTouchListener
on that text view so I could detect pinch gestures, to resize the text. Apparently that was absorbing the onDown
event so the surrounding scroll view didn't receive it.
I assume there's a way to share the onDown
event between both views, but in my case it was easier to move the pinch gesture detector from the inner text view to the outer scroll view, so the scroll gesture detector and the pinch gesture detector are on the same view -- something like this:
val scrollGestureDetector = GestureDetector(this.fragment.activity, ScrollGestureListener(scrollView))
val pinchGestureDetector = GestureDetector(this.fragment.activity, PinchGestureListener(scrollView))
scrollView.setOnTouchListener(OnTouchListener { view, event ->
scrollGestureDetector.onTouchEvent(event)
pinchGestureDetector.onTouchEvent(event)
return@OnTouchListener false
})
internal inner class ScrollGestureListener(view: View) : GestureDetector.SimpleOnGestureListener() {
override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
[email protected]()
return true
}
}
internal inner class PinchGestureListener(view: View) : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScale(detector: ScaleGestureDetector): Boolean {
[email protected]()
return true
}
}