10

I have to let this slip for now as a purely academic issue but i would very much like to see a solution in near time.

Due to the way that Android handles multitouch you can (as i see it) only trap the event in a single view. I've tried an hack for this envolving a container layout that intercepts the events sees what View it belongs by seeing the coords and changing the action itself so that it seems to the component that it's a single touch event. I compose such events and then route it to the Views.

Does anyone have a better idea to do this?

If someone wants the code for what i described above just ask and i post it!

Have fun and good luck :D JQCorreia

public class Container extends LinearLayout
{      
        LinkedHashMap<Integer,View> pointers = new LinkedHashMap<Integer,View>();
        ArrayList<View> views  = new ArrayList<View>();

        public Container(Context context) {
                super(context);
                initialize(context);

        }

        public Container(Context context, AttributeSet attrs) {
                super(context, attrs);
                initialize(context);
        }

        private void initialize(Context context)
        {

        }
        @Override
        public void onLayout(boolean changed, int l, int t, int r, int b)
        {
                super.onLayout(changed, l, t, r, b);
                views = LayoutUtil.flattenLayout(this,false);
                for(View foo : views)
                {
                        Rect rect = new Rect();
                        foo.getGlobalVisibleRect(rect);
                }
        }

        @Override
        public boolean onInterceptTouchEvent(MotionEvent event)
        {
                return true;
        }
        @Override
        public boolean onTouchEvent(MotionEvent event)
        {
                int action = event.getAction() & MotionEvent.ACTION_MASK;
                if(action==MotionEvent.ACTION_DOWN)
                {
                        for(View v: views)
                        {
                                Rect r = new Rect();
                                v.getGlobalVisibleRect(r);
                                if (event.getX() > r.left && event.getX() < r.right
                                                && event.getY() > r.top
                                                && event.getY() < r.bottom) {
                                        pointers.put(event.getPointerId(0),v);
                                        pointers.get(event.getPointerId(0)).onTouchEvent(event);
                                        break;
                                }
                        }
                }
                if(action==MotionEvent.ACTION_POINTER_DOWN)
                {
                        int pid = event.getAction() >> MotionEvent.ACTION_POINTER_ID_SHIFT;
                        int index = event.findPointerIndex(pid);

                        for(View v: views)
                        {

                                Rect r = new Rect();
                                v.getGlobalVisibleRect(r);
                                if (event.getX(index) > r.left
                                                && event.getX(index) < r.right
                                                && event.getY(index) > r.top
                                                && event.getY(index) < r.bottom) {


                                        pointers.put(pid,v);
                                        MotionEvent copy = MotionEvent.obtain(event);
                                        copy.setAction(MotionEvent.ACTION_DOWN);
                                        copy.setLocation(event.getX(index), event.getY(index));
                                        pointers.get(pid).onTouchEvent(copy);
                                }
                        }
                }
                if(action==MotionEvent.ACTION_POINTER_UP)
                {
                        int pid = event.getAction() >> MotionEvent.ACTION_POINTER_ID_SHIFT;
                        int index = event.findPointerIndex(pid);

                        if(pointers.get(pid)!=null) // If the touch was outside any view
                        {
                                MotionEvent copy = MotionEvent.obtain(event);
                                copy.setAction(MotionEvent.ACTION_UP);
                                pointers.get(pid).onTouchEvent(copy);
                                pointers.remove(pid);
                        }
                }

                if(action==MotionEvent.ACTION_MOVE)
                {
                        for(int i = 0; i<event.getPointerCount();i++)
                        {
                                int pid = event.getPointerId(i);
                                MotionEvent copy = MotionEvent.obtain(event);
                                copy.setLocation(event.getX(i), event.getY(i));

                                if(pointers.get(pid)==null) continue; // If the touch was outside any view
                                pointers.get(pid).onTouchEvent(copy);
                        }
                }

                if(action==MotionEvent.ACTION_UP)
                {
                        if(pointers.get(event.getPointerId(0))!=null)
                        {
                                pointers.get(event.getPointerId(0)).onTouchEvent(event);
                                pointers.remove(event.getPointerId(0));
                        }
                }
                return true;
        }

}

// This is the LayoutUtil.flattenLayout method
        public static ArrayList<View> flattenLayout(View view, boolean addViewGroups)
        {
                ArrayList<View> viewList = new ArrayList<View>();
                if(view instanceof ViewGroup)
                {
                        if(((ViewGroup)view).getChildCount()==0)
                                viewList.add(view);
                        else
                        {
                                if(addViewGroups)
                                {
                                        viewList.add(view);
                                }
                                ViewGroup viewgroup = (ViewGroup) view;
                                for(int i = 0; i < viewgroup.getChildCount();i++)
                                {
                                        viewList.addAll(flattenLayout(viewgroup.getChildAt(i),false));
                                }
                        }      
                }
                else if(view instanceof View)
                {
                        viewList.add(view);
                }
                return viewList;
        }
Benjamin Podszun
  • 9,679
  • 3
  • 34
  • 45
JQCorreia
  • 727
  • 6
  • 15
  • This changed in Android 3.0 (http://developer.android.com/sdk/android-3.0.html), you can use `android:splitMotionEvents` or `android:windowEnableSplitTouch`. For applications for before Honeycomb, I would override the `onInterceptTouchEvent` method of the `ViewGroup`. – Joseph Earl May 09 '11 at 17:48
  • Thanks man i didn't knew that... – JQCorreia May 09 '11 at 18:55
  • But anyway until we have 3.0 in the majority of the devices this thread could be a 'tank' of solutions to this problem. At least it was my idea :D – JQCorreia May 09 '11 at 18:57
  • Comment by user without comment privileges ([Ravs](http://stackoverflow.com/users/1038804/ravs)): JQCorreia, can you please point us to the code for this? – Anne Nov 30 '11 at 23:39
  • 2
    Anne or Ravs (i don't get who is asking =)) there it is: http://pastebin.com/hiE1aTCw – JQCorreia Dec 06 '11 at 14:25
  • I dont guarantee bug free code but this does the trick most of the times even with sensitive components such as virtual gamepads and such. Feel free to ask if you have any doubt – JQCorreia Dec 06 '11 at 14:26
  • Thanks a lot for your help. I moved that code here, so that this question/discussion stays relevant even if pastebin expires. Feel free to revert. FWIW: I used your code as a starting point, it helped me a lot to see that I don't miss something entirely (i.e. 'official' ways to do it). – Benjamin Podszun Dec 29 '11 at 09:35
  • OKay, makes sense! No problem, and if you have any question feel free. BTW if you find something that improves the existing spotless (*ahahmmm*) piece of code let me know :) – JQCorreia Jan 02 '12 at 14:21

3 Answers3

10

The best solution here is to put

android:splitMotionEvents = false 

inside LinearLayout or any Layout your view (Button, TextView, etc) is.

-cheers happy codings

ralphgabb
  • 10,298
  • 3
  • 47
  • 56
2

You need to override onInterceptTouchEvent as well to capture motion events. When you return true from onInterceptTouchEvent, all subsequent events (whether inside your view bounds or not) are captured in calls to onTouchEvent up until (and including) the point where the last pointer goes up.

Traditionally, you put enough logic in onInterceptTouchEvent to determine that a pointer has gone down, AND that it has moved beyond some threshold before returning true, but that depends on whether you want to support drag in horizontal and/or vertical directions in parent views. If an ACTION_POINTER_DOWN event is sufficient to trigger the capture, then you can return true immediately.

Robin Davies
  • 7,547
  • 1
  • 35
  • 50
  • 1
    yes this. But note, onInterceptTouchEvent will ONLY get called if a child view will handle true onTouchEvent (e.g. is clickable). Otherwise you only get onTouchEvent because there's no one to "intercept" the event from. – user123321 Jan 24 '13 at 04:53
0
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    if(ev.getPointerCount() > 1) return false;
    return super.dispatchTouchEvent(ev);
}

insert this on activity or view