Android multitouch! hack anyone?
Asked Answered
B

3

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;
        }
Bivalve answered 9/5, 2011 at 15:20 Comment(8)
This changed in Android 3.0 (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.Lampe
Thanks man i didn't knew that...Bivalve
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 :DBivalve
Comment by user without comment privileges (Ravs): JQCorreia, can you please point us to the code for this?Heterochromatin
Anne or Ravs (i don't get who is asking =)) there it is: pastebin.com/hiE1aTCwBivalve
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 doubtBivalve
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).Flews
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 :)Bivalve
H
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

Hartsell answered 5/11, 2014 at 5:42 Comment(0)
D
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.

Doorplate answered 24/1, 2013 at 4:25 Comment(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.Spode
E
0
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    if(ev.getPointerCount() > 1) return false;
    return super.dispatchTouchEvent(ev);
}

insert this on activity or view

Eskilstuna answered 28/9, 2020 at 10:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.