Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save and291/ce5704c4163f8dcd9d06b1ab6a9850cf to your computer and use it in GitHub Desktop.
Save and291/ce5704c4163f8dcd9d06b1ab6a9850cf to your computer and use it in GitHub Desktop.
Dismiss indefinite Snackbar depending on touch action and touch point
package com.example.abusik.snackbarhideontap
import android.graphics.Point
import android.graphics.Rect
import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.v7.app.AppCompatActivity
import android.view.MotionEvent
import android.view.View
import kotlinx.android.synthetic.main.activity_main.coordinator_layout
import kotlinx.android.synthetic.main.activity_main.toolbar
class MainActivity : AppCompatActivity() {
private var snackbar: Snackbar? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
// show indefinite snackbar
snackbar = Snackbar.make(coordinator_layout, "Hello world!", Snackbar.LENGTH_INDEFINITE).apply {
addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
super.onDismissed(transientBottomBar, event)
/**
* Set [snackbar] to null for [MainActivity.dispatchTouchEvent] to perform better
*/
if (transientBottomBar == snackbar) {
snackbar = null
}
}
})
show()
}
}
/**
* On each touch event:
* Check is [snackbar] present and displayed
* and dismiss it if user touched anywhere outside it's bounds
*/
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
// dismiss shown snackbar if user tapped anywhere outside snackbar
snackbar?.takeIf { it.isShown }?.run {
val touchPoint = Point(Math.round(ev.rawX), Math.round(ev.rawY))
if (!isPointInsideViewBounds(view, touchPoint)) {
dismiss()
snackbar = null // set snackbar to null to prevent this block being executed twice
}
}
// call super
return super.dispatchTouchEvent(ev)
}
/**
* Defines bounds of displayed view and check is it contains [Point]
* @param view View to define bounds
* @param point Point to check inside bounds
* @return `true` if view bounds contains point, `false` - otherwise
*/
private fun isPointInsideViewBounds(view: View, point: Point): Boolean = Rect().run {
// get view rectangle
view.getDrawingRect(this)
// apply offset
IntArray(2).also { locationOnScreen ->
view.getLocationOnScreen(locationOnScreen)
offset(locationOnScreen[0], locationOnScreen[1])
}
// check is rectangle contains point
contains(point.x, point.y)
}
}
@hiberfake
Copy link

Great approach!

A good adjustment might be to just consider a started pressed gesture with action MotionEvent.ACTION_DOWN. Otherwise, the block is called several times for further events.

class MainActivity : AppCompatActivity() {

    ....

    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
        snackbar?.takeIf { it.isShown }?.run {
            if (MotionEvent.ACTION_DOWN == ev.action) {
                val point = Point(Math.round(ev.rawX), Math.round(ev.rawY))
                if (!point.isInsideViewBounds(view)) {
                    dismiss()
                }
            }
        }
        return super.dispatchTouchEvent(ev)
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment