Last active
February 13, 2019 12:30
-
-
Save and291/ce5704c4163f8dcd9d06b1ab6a9850cf to your computer and use it in GitHub Desktop.
Dismiss indefinite Snackbar depending on touch action and touch point
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.