1

Given: view A, that holds view B.

my layout

Action: I am performing touch and hold on view A with finger 1, and then on view B with finger 2. Then release and the same operation but firstly view B, and then view A. And I expect that views will receive similar events in both cases.

What hapens: when I touch and hold A with finger 1, and then touch and hold B with finger 2, A and B are receiving two separate MotionEvents, each of them holds ACTION_MOVE coords for one pointer:

Logcat:

-----first finger down on view A-------
    ACTION_DOWN[#0 (pid 0)=897,392]
    
    ----action move for first finger----
    ACTION_MOVE[#0 (pid 0)=897,392]
    ACTION_MOVE[#0 (pid 0)=897,392]
    ACTION_MOVE[#0 (pid 0)=897,392]
    ACTION_MOVE[#0 (pid 0)=895,392]
    ACTION_MOVE[#0 (pid 0)=896,393]
    ACTION_MOVE[#0 (pid 0)=896,394]
    ACTION_MOVE[#0 (pid 0)=896,395]
    ACTION_MOVE[#0 (pid 0)=896,396]
    ACTION_MOVE[#0 (pid 0)=895,396]
    ACTION_MOVE[#0 (pid 0)=895,396]
    
    ---second finger down on view B-------
    ACTION_DOWN[#0 (pid 1)=224,87]
    
    ---action move for 1st finger----
    ACTION_MOVE[#0 (pid 0)=895,396]
    
    ---action move for 2nd finger----
    ACTION_MOVE[#0 (pid 1)=224,87]
    
    --first--
    ACTION_MOVE[#0 (pid 0)=895,396]
    
    --second--
    ACTION_MOVE[#0 (pid 1)=224,87]
    
    etc . . .
    ACTION_MOVE[#0 (pid 0)=895,397]
    ACTION_MOVE[#0 (pid 1)=224,87]
    ACTION_MOVE[#0 (pid 0)=895,397]
    ACTION_MOVE[#0 (pid 1)=224,87]
    ACTION_MOVE[#0 (pid 0)=895,397]
    ACTION_MOVE[#0 (pid 1)=223,87]
    ACTION_MOVE[#0 (pid 0)=895,397]
    
        ...

BUT, when I firstly touch B, and then A - Android think that it's a multi-touch event and starts to send MotionEvent object that holds ACTION_MOVE coords for 2 pointers to view B only.

Logcat:

   ----------holding finger at view B------------------
    event ACTION_MOVE[#0 (pid 0)=65,33]
    event ACTION_MOVE[#0 (pid 0)=62,33]
    event ACTION_MOVE[#0 (pid 0)=60,33]
    event ACTION_MOVE[#0 (pid 0)=58,33]
    event ACTION_MOVE[#0 (pid 0)=56,32]
    event ACTION_MOVE[#0 (pid 0)=55,32]
    event ACTION_MOVE[#0 (pid 0)=54,32]
    event ACTION_MOVE[#0 (pid 0)=54,32]
    event ACTION_MOVE[#0 (pid 0)=53,32]
    event ACTION_MOVE[#0 (pid 0)=52,32]
    event ACTION_MOVE[#0 (pid 0)=52,32]
    event ACTION_MOVE[#0 (pid 0)=51,32]
    event ACTION_MOVE[#0 (pid 0)=51,33]
    
    ----------press on view A with 2nd finger, while holding B with first finger-------------
    event ACTION_POINTER_DOWN(pid 1); [#0 (pid 0)=51,33; #1 (pid 1)=1050,-226]
    
    ---------action move obj that holds coords for 2 pointers...---------
    event ACTION_MOVE[#0 (pid 0)=50,33;#1 (pid 1)=1055,-225]
    event ACTION_MOVE[#0 (pid 0)=48,34;#1 (pid 1)=1068,-223]
    event ACTION_MOVE[#0 (pid 0)=47,34;#1 (pid 1)=1082,-224]
    event ACTION_MOVE[#0 (pid 0)=45,35;#1 (pid 1)=1106,-221]
    event ACTION_MOVE[#0 (pid 0)=42,35;#1 (pid 1)=1131,-219]
    event ACTION_MOVE[#0 (pid 0)=38,35;#1 (pid 1)=1157,-217]
    event ACTION_MOVE[#0 (pid 0)=34,35;#1 (pid 1)=1178,-215]
    event ACTION_MOVE[#0 (pid 0)=27,36;#1 (pid 1)=1195,-213]
    event ACTION_MOVE[#0 (pid 0)=20,37;#1 (pid 1)=1211,-213]
    event ACTION_MOVE[#0 (pid 0)=11,39;#1 (pid 1)=1228,-212]

What I want: I want separate MotionEvents (first case) in both scenarios (first A, then B; and first B, then A)

How I tried to solve it:

  1. I tried process only 1 set of coords at view B's onTouch(), and then return false, and then consume event at view A, BUT in this scenario I ain't receive anything but ACTION_DOWN at View B.

  2. I tried to dispatch event in activity in dispatchTouchEvent() method and then manually call view A and view B dispatchTouchEvent() methods, but It hasn't succeed. I tried to pass to ViewGroup (parent layout) and it hasn't succeed too =( onTouch is not called.

But this 2 of my tries I consider as a HACK, I would like to make OS switch back from multi-touch to separate touch events. Do you know how to do that?

StayCool
  • 421
  • 1
  • 9
  • 23
  • 2
    Please provide your onTouch() method source code for both Views – emandt Mar 11 '21 at 11:15
  • Did you try to make the same test by ungrouping the View B from View A by adding B above A (changing the Z-order instead of B to be a child of A) and check if you get the same results? – MariosP Mar 12 '21 at 16:32
  • Just some clarification on A and B:I assume that B is a child of A. Is B a view or a ViewGroup? Where does each view currently capture the event stream (onTouch(), onTouchEvent(), onInterceptTouchEvent() or displatchTouchEvent())? I have found [this](https://stackoverflow.com/a/46862320/6287910) helpful. – Cheticamp Mar 12 '21 at 17:51
  • @MariosP actually, A and B are separate views. And they are on same level of hierarchy, but yes, B is above A, and A is below B. I will do tests you suggested and will be back – StayCool Mar 13 '21 at 17:47
  • @Cheticamp no, A and B are separate views, and they don't relate to each other. One detail is B is above A, and is below B. Both views have OnTouchListener, so I process touches exactly In onTouch method of listener. Thanks for the link. I've read it already, very helpful in gerenal, but not for this particular case – StayCool Mar 13 '21 at 17:49

2 Answers2

0

I've just created a new project, set a Layout as yours and then set a different "onTouch()" event for both Views. I cannot reproduce your situation. If both "onTouch()" events returns TRUE the results are these:

//DOWN for A
onTouchA(MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=382.0, y[0]=175.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=10987450, downTime=10987450, deviceId=5, source=0x1002 })
onTouchA(MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=365.0, y[0]=186.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=10993036, downTime=10987450, deviceId=5, source=0x1002 })
//DOWN for B
onTouchB(MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=1, x[0]=409.0, y[0]=260.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=10995795, downTime=10987450, deviceId=5, source=0x1002 })
onTouchA(MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=363.0, y[0]=184.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=10995795, downTime=10987450, deviceId=5, source=0x1002 })
onTouchB(MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=1, x[0]=409.0, y[0]=256.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=10995888, downTime=10987450, deviceId=5, source=0x1002 })
onTouchA(MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=363.0, y[0]=184.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=10995888, downTime=10987450, deviceId=5, source=0x1002 })
onTouchB(MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=1, x[0]=409.8291, y[0]=254.1709, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=1, eventTime=10995909, downTime=10987450, deviceId=5, source=0x1002 })
//UP for A
onTouchA(MotionEvent { action=ACTION_UP, actionButton=0, id[0]=0, x[0]=361.0, y[0]=191.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=10998121, downTime=10987450, deviceId=5, source=0x1002 })
onTouchB(MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=1, x[0]=410.0, y[0]=234.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=10998156, downTime=10987450, deviceId=5, source=0x1002 })
onTouchB(MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=1, x[0]=410.0, y[0]=235.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=10998285, downTime=10987450, deviceId=5, source=0x1002 })
onTouchB(MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=1, x[0]=409.0, y[0]=235.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=10998460, downTime=10987450, deviceId=5, source=0x1002 })
onTouchB(MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=1, x[0]=408.0, y[0]=235.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=10998530, downTime=10987450, deviceId=5, source=0x1002 })
//UP for B
onTouchB(MotionEvent { action=ACTION_UP, actionButton=0, id[0]=1, x[0]=405.0, y[0]=235.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=10998846, downTime=10987450, deviceId=5, source=0x1002 })

=================================

//DOWN for B
onTouchB(MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=241.0, y[0]=195.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=11125227, downTime=11125227, deviceId=5, source=0x1002 })
onTouchB(MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=221.0, y[0]=196.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=11126010, downTime=11125227, deviceId=5, source=0x1002 })
onTouchB(MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=220.0, y[0]=196.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=11126044, downTime=11125227, deviceId=5, source=0x1002 })
//DOWN for A
onTouchA(MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=1, x[0]=347.0, y[0]=156.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=11128089, downTime=11125227, deviceId=5, source=0x1002 })
onTouchB(MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=216.0, y[0]=196.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=11128089, downTime=11125227, deviceId=5, source=0x1002 })
onTouchA(MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=1, x[0]=347.0, y[0]=156.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=11129071, downTime=11125227, deviceId=5, source=0x1002 })
onTouchB(MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=216.0, y[0]=195.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=11129071, downTime=11125227, deviceId=5, source=0x1002 })
onTouchA(MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=1, x[0]=343.0, y[0]=156.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=11130531, downTime=11125227, deviceId=5, source=0x1002 })
//UP for B
onTouchB(MotionEvent { action=ACTION_UP, actionButton=0, id[0]=0, x[0]=219.91565, y[0]=194.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=11132305, downTime=11125227, deviceId=5, source=0x1002 })
onTouchA(MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=1, x[0]=337.0, y[0]=156.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=11133099, downTime=11125227, deviceId=5, source=0x1002 })
onTouchA(MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=1, x[0]=337.0, y[0]=157.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=11133228, downTime=11125227, deviceId=5, source=0x1002 })
//UP for A
onTouchA(MotionEvent { action=ACTION_UP, actionButton=0, id[0]=1, x[0]=337.0, y[0]=157.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=11133240, downTime=11125227, deviceId=5, source=0x1002 })

XML:

<?xml version="1.0" encoding="utf-8"?>
<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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<TextView
    android:id="@+id/textView"
    android:layout_width="300dp"
    android:layout_height="400dp"
    android:background="@color/teal_200"
    android:text="Hello World!"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintHorizontal_bias="0.495"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintVertical_bias="0.498" />
<TextView
    android:id="@+id/textView2"
    android:layout_width="200dp"
    android:layout_height="200dp"
    android:text="Hello World!"
    android:background="@color/teal_700"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

MainActivity code:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    TextView cTextView = findViewById(R.id.textView);
    cTextView.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            Log.e(TAG, "onTouchA("+event.toString()+")");
            return true;
        }
    });

    TextView cTextView2 = findViewById(R.id.textView2);
    cTextView2.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            Log.e(TAG, "onTouchB("+event.toString()+")");
            return false;
        }
    });
}
emandt
  • 2,547
  • 2
  • 16
  • 20
-2

To prevent multi-touch issues you can simply turn off splitMotionEvents or windowEnableSplitTouch attribute inside ViewGroup

  • I don't need to prevent multi-touch event, I need android to split motion events, so every child gets only his pointer. And when I do "splitMotionEvent" = false -- I can't any longer use 2 views at the same time. When I set flag to false I can see that event are somehow duplicated between views, brb testing now ) – StayCool Mar 05 '21 at 08:47
  • Okay, I turned off splitMotionEvents. What's next?) I am getting all event's in one place, not splitted. And view A never gonna receive it's events =) – StayCool Mar 05 '21 at 12:54