Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android: Dismiss Floating ActionMode when tapping outside ActionMode dialog

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:

Image of floating action menu

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):

Example of dismiss action on tap outside action menu

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()
        )
    }
}
like image 916
Bohsen Avatar asked Oct 19 '25 12:10

Bohsen


1 Answers

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)
}
like image 106
Nikolay Krasilnikov Avatar answered Oct 21 '25 02:10

Nikolay Krasilnikov



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!