1

I have found the solution to collapse BottomSheetBehavior when clicking outside here. The code from said link is below (converted to Kotlin):

override fun dispatchTouchEvent(event: MotionEvent): Boolean {
    var returnValue: Boolean = super.dispatchTouchEvent(event)
    if (event.action == MotionEvent.ACTION_DOWN) {
        if (mBottomSheetBehavior?.state == BottomSheetBehavior.STATE_EXPANDED) {
            val outRect = Rect()
            val fragment = supportFragmentManager.findFragmentById(R.id.queueChoicePanel)
            fragment?.view?.getGlobalVisibleRect(outRect)

            if (!outRect.contains(event.rawX.toInt(), event.rawY.toInt())) {
                mBottomSheetBehavior?.state = BottomSheetBehavior.STATE_COLLAPSED
            }
        }
    }

    return returnValue
}

However, what it lacks is that when I click outside and I click an area that is clickable, it also fires that clickable area's click handler. I want it so when the BottomSheetBehavior is in its expanded state (i.e. BottomSheetBehavior.state == BottomSheetBehavior.STATE_EXPANDED) and I click outside, I collapse the BottomSheetBehavior and intercepting the click so that it does not further trigger click handlers from outside the BottomSheetBehavior (for example, clicking a button that is outside BottomSheetBehavior and disabling the button's click handler from firing). How do I do that?

Richard
  • 7,037
  • 2
  • 23
  • 76

1 Answers1

2

Try doing your check before calling super and add return true to the case where you collapse the sheet, it should consume the touch event so the view below doesn't receive it.

override fun dispatchTouchEvent(event: MotionEvent): Boolean {
    if (event.action == MotionEvent.ACTION_DOWN) {
        if (mBottomSheetBehavior?.state == BottomSheetBehavior.STATE_EXPANDED) {
            val outRect = Rect()
            val fragment = supportFragmentManager.findFragmentById(R.id.queueChoicePanel)
            fragment?.view?.getGlobalVisibleRect(outRect)

            if (!outRect.contains(event.rawX.toInt(), event.rawY.toInt())) {
                mBottomSheetBehavior?.state = BottomSheetBehavior.STATE_COLLAPSED
                return true
            }
        }
    }    
    return super.dispatchTouchEvent(event)
}
patrick.elmquist
  • 2,113
  • 2
  • 21
  • 35
  • It didn't work. I think that `return true` only intercepts the click when we use it with `onInterceptTouchEvent()`. – Richard Nov 25 '19 at 11:13
  • Ahh now I see, `var returnValue: Boolean = super.dispatchTouchEvent(event)` is most likely what's causing that since you are actually passing the event to the whole view hierarchy below, letting all the views handle the event as they please, and after that you want to block some views from reacting to the input. You need to do your check first and if it's not collapsing, pass the event on to the hierarchy. Updated the answer. Also this really feels like the type of logic that belongs in `onInterceptTouchEvent`. – patrick.elmquist Nov 25 '19 at 11:18
  • That worked. But how? Previously, we returned `true` if we click outside `outRect`. It shouldn't have returned `super.dispatchTouchEvent(event)`. Or could it be that it handled all the `super.dispatchTouchEvent(event)` when I called it, which caused it to also trigger the other Views (in my example, the button) to react? If that's the case, what does `return super.dispatchTouchEvent(event)` even do? What is it returning exactly? – Richard Nov 25 '19 at 11:23
  • Yeah when calling `super.dispatchTouchEvent(event)` you pass the event to all views below, e.g. the button, and let it react to it, and once it's done, your collapse check is performed but by then it's already too late. By doing the check first and in the collapse case return true, you are consuming the event, letting no other views handle it. – patrick.elmquist Nov 25 '19 at 11:26
  • One final question, why do we need to `return super.dispatchTouchEvent(event)`. Why not just return a `false` and call the super function `super.dispatchTouchEvent(event)`? – Richard Nov 25 '19 at 11:28
  • You still need to call `super.dispatchTouchEvent(event)` in the case that you are not handling the event to let otherwise handle it, otherwise the Bottom sheet would basically block all touch events going to the rest of the view hierarchy. By calling `return super.dispatchTouchEvent(event)` you basically say "if you're not executing my special case, do what you usually do" – patrick.elmquist Nov 25 '19 at 11:31