Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rejecting peripheral touch inputs

Given a nested view structure such as:

Nested app layout

How do I disable input on the grey periphery, such that even as the user holds a finger on it, but touches the yellow view, the touch event would register in dispatchTouchEvent() for that view unhindered?

EDIT: To explain a bit further, I need some kind of palm rejection system in the grey area. In the yellow area the user is able to draw with his finger. All of that works fine, but on some phones with border-less displays you might accidentally touch the grey area, which registers as input and the drawing is ruined.

This happens only when the user is touching the screen in multiple places at once. One might blame the user at this point for buying into the gimmick, but I've tried it myself and it's really easy to accidentally touch the edge, and prevent events from flowing properly.

After I've posted this question, I have come up with a somewhat hacky solution that uses multi-touch events. It works better than before, but because it's not really a multi-touch event, sometimes it goes stale and stops registering inputs altogether. Besides, it would be nice to be able to capture real multi-touch events in the yellow box eventually (for example to make zoom-into-your-drawing gesture).

The basic premise of my solution so far is:

  1. Setup the view. In this test case I just got one View, that draws all the areas in its onDraw().
  2. Capture the MotionEvent in dispatchTouchEvent()
  3. Pick out the portion of the event with the x,y inside the yellow area:

Then:

// ... event:MotionEvent, pointerCoordsOut:MotionEvent.PointerCoords
for (pidx in 0 until event.pointerCount) {
    event.getPointerCoords(pidx, pointerCoordsOut)
    if (inYellowArea(pointerCoordsOut.x, pointerCoordsOut.y)) {
         //pointerCoordsOut now has (x,y) that I need
    }
}

Finally, adjust the code to accept ACTION_* and ACTION_POINTER_* events, and make them do something reasonable. This was easy enough in the demo case, but I think this is where the solution will ultimately fail.

So I would still love for there to be a proper solution for palm rejection on borders as-if that event was not there at all, as opposed to in my case part of a complicated gesture that I am trying to decipher.

EDIT:

Still open for suggestions.

like image 370
Gleno Avatar asked Feb 08 '19 09:02

Gleno


1 Answers

UPDATED:
layout:

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/darker_gray"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/orange"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginBottom="64dp"
        android:layout_marginEnd="64dp"
        android:layout_marginStart="64dp"
        android:layout_marginTop="64dp"
        android:background="@android:color/holo_orange_dark"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

    </FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

code:

class MainActivity : AppCompatActivity() {

    private val touchableRect = Rect()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        root.post {
            orange.getGlobalVisibleRect(touchableRect)
        }

        root.setOnTouchListener { v, event ->
            Log.d("logi", "root touched : ${event.actionMasked}")
            false
        }

        orange.setOnTouchListener { v, event ->
            Log.d("logi", "orange touched : ${event.actionMasked}")
            true
        }
    }

    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        val isConsumed = ev?.let {
            if (touchableRect.contains(it.x.toInt(), it.y.toInt())) {
                orange.dispatchTouchEvent(it)
            } else {
                true
            }
        }

        return isConsumed ?: true
    }
}
like image 142
kovac777 Avatar answered Oct 15 '22 18:10

kovac777