I've run into an issue that I just can't find an answer to.
I'm using a floating action menu, when dealing with longClick()
actions in a inline list component inside a custom view. Like this:
When I display the action menu (using ActionMode.Callback2
) I can't find a way to dismiss it by tapping outside the action menu.
What I'm looking for is a solution like in the Chrome app (see video below):
I've tried different solutions i.a. using View.onTouch()
/MotionEvent.ACTION_OUTSIDE
, TouchDelegate()
and another usingregisterForContextMenu()
, but none of them worked.
So I was wondering if there's anyone that knows a way to make ActionMode.Callback2
dismiss when clicked outside it's containing view, any help would be appreciated. My ActionMode.Callback2
implementation can be seen below:
@RequiresApi(Build.VERSION_CODES.M)
class SampleViewItemActionModeCallback private constructor(private val sample: Sample,
private val listener: SampleView.SampleItemClickListener?,
private val touchDelegate: TouchDelegate? = null,
@MenuRes private var menuResId: Int = 0,
private var contentLeft: Int = 0,
private var contentTop: Int = 0,
private var contentRight: Int = 0,
private var contentBottom: Int = 0)
: ActionMode.Callback2() {
private var mode: ActionMode? = null
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
this.mode = mode
mode.menuInflater.inflate(menuResId, menu)
return true
}
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
return false
}
override fun onDestroyActionMode(mode: ActionMode) {
this.mode = null
}
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_edit -> {
listener?.onSampleEditClick(sample)
mode.finish()
true
}
R.id.action_delete -> {
listener?.onSampleDeleteClick(sample)
mode.finish()
true
}
else -> {
mode.finish()
false
}
}
}
override fun onGetContentRect(mode: ActionMode, view: View, outRect: Rect) {
outRect.set(contentLeft, contentTop, contentRight, contentBottom)
view.touchDelegate
view.setOnTouchListener { v, event ->
Timber.i("Event: ${event.actionMasked} happened inside view: ${isMotionEventInsideView(v, event)}. Parent is: ${v.parent}")
true
}
}
companion object {
operator fun invoke(view: SampleView,
listener: SampleView.SampleItemClickListener?,
touchDelegate: TouchDelegate? = null,
@MenuRes menuResId: Int,
contentLeft: Int = 0,
contentTop: Int = 0,
contentRight: Int = view.width,
contentBottom: Int = view.height): ActionMode {
val callback = SampleViewItemActionModeCallback(
sample = checkNotNull(view.sample),
listener = listener,
touchDelegate = touchDelegate,
menuResId = menuResId,
contentLeft = contentLeft,
contentTop = contentTop,
contentRight = contentRight,
contentBottom = contentBottom
)
return view.startActionMode(callback, ActionMode.TYPE_FLOATING)
}
}
private fun isMotionEventInsideView(view: View, event: MotionEvent): Boolean {
val viewRect = Rect(
view.left,
view.top,
view.right,
view.bottom
)
return viewRect.contains(
view.left + event.x.toInt(),
view.top + event.y.toInt()
)
}
}
Fist extend a view's touchable area, docs here I do it like this in my custom view:
override fun onAttachedToWindow() {
super.onAttachedToWindow()
extendTouchableArea()
}
/**
* Extend touchable area to the whole screen.
* It is needed to handle touch events outside of view to finish action mode.
*/
private fun extendTouchableArea() {
val rect = Rect()
getWindowVisibleDisplayFrame(rect)
parentView.touchDelegate = TouchDelegate(
rect,
this
)
}
Then override dispatchTouchEvent
and close action mode like this:
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
if (actionMode != null && event.action == MotionEvent.ACTION_DOWN) {
// Finish action mode on any touch event
finishActionMode()
return false
}
return super.dispatchTouchEvent(event)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With