Multitouch for all Views

Here is the new Version of my implementation of the MultiTouchActivity.

screenshot2

 

Features:

  • forwards all MotionEvents to the underlying View
  • Included Move Events
  • Sends MouseUp to View if the pointer is out of the View
  • sends MoveEvents  to a View if the Pointer is outside of the View (but registered to view with MouseDown) if you add the View to the moveOutsideEnabledViews  with addMoveOutsideEnabledViews(View);. (see the TestButton)

Further informations:

Download Example App:


logo-app
Multitouch for all Views
Pascal Welsch
0   
pulsante-google-play-store
pulsante-appbrain
qrcode-app

Get full Project Source here:

Have a quick look at the Code here:

package de.passsy.multitouch;

import java.util.ArrayList;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;

public class MultiTouchActivity extends Activity implements OnTouchListener {

	private View parent;

	private final ArrayList[] recentTouchedViewsIndex = new ArrayList[10];

	private final ArrayList[] downTouchedViewsIndex = new ArrayList[10];

	private final ArrayList<view> moveOutsideEnabledViews = new ArrayList<view>();

	private int mTouchSlop = 24;

	public void addMoveOutsideEnabledViews(final View view) {
		moveOutsideEnabledViews.add(view);
	}

	private void dealEvent(final int actionPointerIndex,
			final MotionEvent event, final View eventView,
			final int actionResolved) {
		int rawX, rawY;
		final int location[] = { 0, 0 };
		eventView.getLocationOnScreen(location);
		// Log.v(&quot;tag&quot;, location + &quot;&quot;);
		rawX = (int) event.getX(actionPointerIndex) + location[0];
		rawY = (int) event.getY(actionPointerIndex) + location[1];

		final int actionPointerID = event.getPointerId(actionPointerIndex);
		ArrayList<view> hoverViews = getTouchedViews(rawX, rawY);

		if (actionResolved == MotionEvent.ACTION_DOWN) {
			downTouchedViewsIndex[actionPointerID] = (ArrayList<view>) hoverViews
					.clone();
		}
		// deletes all views which where not clicked on ActionDown
		if (downTouchedViewsIndex[actionPointerID] != null) {
			final ArrayList<view> tempViews = (ArrayList<view>) hoverViews
					.clone();
			tempViews.removeAll(downTouchedViewsIndex[actionPointerID]);
			hoverViews.removeAll(tempViews);
		}

		if (recentTouchedViewsIndex[actionPointerID] != null) {
			final ArrayList<view> recentTouchedViews = recentTouchedViewsIndex[actionPointerID];

			final ArrayList<view> shouldTouchViews = (ArrayList<view>) hoverViews
					.clone();
			if (!shouldTouchViews.containsAll(recentTouchedViews)) {
				shouldTouchViews.removeAll(recentTouchedViews);
				shouldTouchViews.addAll(recentTouchedViews);

				final ArrayList<view> outsideTouchedViews = (ArrayList<view>) shouldTouchViews
						.clone();
				outsideTouchedViews.removeAll(hoverViews);
			}

			recentTouchedViewsIndex[actionPointerID] = hoverViews;
			hoverViews = shouldTouchViews;
		} else {
			recentTouchedViewsIndex[actionPointerID] = hoverViews;
		}

		if (actionResolved == MotionEvent.ACTION_UP) {
			recentTouchedViewsIndex[actionPointerID] = null;
			downTouchedViewsIndex[actionPointerID] = null;
		}

		dumpEvent(event);
		for (final View view : hoverViews) {
			int x, y;
			view.getLocationOnScreen(location);
			x = rawX - location[0];
			y = rawY - location[1];

			// View does not recognize that the Pointer is
			// outside if the Pointer is not far away (&gt;mTouchSlop)
			if (recentTouchedViewsIndex[actionPointerID] != null) {
				if (pointInView(x, y, mTouchSlop, view.getWidth(),
						view.getHeight())) {
					// Log.v(&quot;tag&quot;, &quot;added because &lt; mTouchSlop&quot;);

					if (!recentTouchedViewsIndex[actionPointerID]
							.contains(view)) {
						recentTouchedViewsIndex[actionPointerID].add(view);
					}
				} else if (moveOutsideEnabledViews.contains(view)) {
					Log.v(&quot;tag&quot;, &quot;outside but gets event&quot;);
					recentTouchedViewsIndex[actionPointerID].add(view);
				}
			}
			final MotionEvent me = MotionEvent.obtain(event.getDownTime(),
					event.getEventTime(), actionResolved, x, y,
					event.getPressure(actionPointerIndex),
					event.getPressure(actionPointerIndex),
					event.getMetaState(), event.getXPrecision(),
					event.getYPrecision(), event.getDeviceId(),
					event.getEdgeFlags());
			me.setLocation(x, y);

			if (!me.equals(event)) {
				// deals the Event
				view.onTouchEvent(me);
			}

			// debug
			if (actionResolved == MotionEvent.ACTION_MOVE) {
				Log.v(&quot;tag&quot;,
						&quot;#&quot; + actionPointerIndex + &quot; Rawx:&quot; + rawX + &quot; rawy:&quot;
								+ rawY + &quot; x:&quot; + x + &quot; y:&quot; + y + &quot; &quot;
								+ view.toString());
			}
		}

	}

	private void dumpEvent(final MotionEvent event) {
		final String names[] = { &quot;DOWN&quot;, &quot;UP&quot;, &quot;MOVE&quot;, &quot;CANCEL&quot;, &quot;OUTSIDE&quot;,
				&quot;POINTER_DOWN&quot;, &quot;POINTER_UP&quot;, &quot;7?&quot;, &quot;8?&quot;, &quot;9?&quot; };
		final StringBuilder sb = new StringBuilder();
		final int action = event.getAction();
		final int actionCode = action &amp; MotionEvent.ACTION_MASK;
		sb.append(&quot;event ACTION_&quot;).append(names[actionCode]);
		if (actionCode == MotionEvent.ACTION_POINTER_DOWN
				|| actionCode == MotionEvent.ACTION_POINTER_UP) {
			sb.append(&quot;(pid &quot;).append(
					action &gt;&gt; MotionEvent.ACTION_POINTER_ID_SHIFT);
			sb.append(&quot;)&quot;);
		}
		sb.append(&quot;[&quot;);
		for (int i = 0; i &lt; event.getPointerCount(); i++) {
			sb.append(&quot;#&quot;).append(i);
			sb.append(&quot;(pid &quot;).append(event.getPointerId(i));
			sb.append(&quot;)=&quot;).append((int) event.getX(i));
			sb.append(&quot;,&quot;).append((int) event.getY(i));
			if (i + 1 &lt; event.getPointerCount()) {
				sb.append(&quot;;&quot;);
			}
		}
		sb.append(&quot;]&quot;);
		Log.d(&quot;tag&quot;, sb.toString());
	}

	private ArrayList<view> getChildViews(final View view) {
		final ArrayList<view> views = new ArrayList<view>();
		if (view instanceof ViewGroup) {
			final ViewGroup v = ((ViewGroup) view);
			if (v.getChildCount() &gt; 0) {
				for (int i = 0; i &lt; v.getChildCount(); i++) {
					views.add(v.getChildAt(i));
				}

			}
		}
		return views;
	}

	private ArrayList<view> getTouchedViews(final int x, final int y) {

		final ArrayList<view> touchedViews = new ArrayList<view>();
		final ArrayList<view> possibleViews = new ArrayList<view>();

		if (parent instanceof ViewGroup) {
			possibleViews.add(parent);
			for (int i = 0; i &lt; possibleViews.size(); i++) {
				final View view = possibleViews.get(i);

				final int location[] = { 0, 0 };
				view.getLocationOnScreen(location);

				if (((view.getHeight() + location[1] &gt;= y)
						&amp; (view.getWidth() + location[0] &gt;= x)
						&amp; (view.getLeft() &lt;= x) &amp; (view.getTop() &lt;= y))
						|| view instanceof FrameLayout) {
					touchedViews.add(view);
					possibleViews.addAll(getChildViews(view));
				}

			}
		}

		return touchedViews;

	}

	@Override
	public void onCreate(final Bundle instance) {
		super.onCreate(instance);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
		getWindow().clearFlags(
				WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
		parent = findViewById(android.R.id.content).getRootView();
		parent.setOnTouchListener(this);
		mTouchSlop = ViewConfiguration.get(getApplicationContext())
				.getScaledTouchSlop();
	}

	@Override
	public boolean onTouch(final View v, final MotionEvent event) {

		// index of the pointer which starts this Event
		final int actionPointerIndex = event.getActionIndex();

		// resolve the action as a basic type (up, down or move)
		int actionResolved = event.getAction() &amp; MotionEvent.ACTION_MASK;
		if (actionResolved &lt; 7 &amp;&amp; actionResolved &gt; 4) {
			actionResolved = actionResolved - 5;
		}

		if (actionResolved == MotionEvent.ACTION_MOVE) {
			for (int ptrIndex = 0; ptrIndex &lt; event.getPointerCount(); ptrIndex++) {
				// only one event for all move events.
				dealEvent(ptrIndex, event, v, actionResolved);
				Log.v(&quot;tag&quot;, &quot;move&quot; + ptrIndex);
			}

		} else {
			dealEvent(actionPointerIndex, event, v, actionResolved);
		}

		return true;
	}

	private boolean pointInView(final float localX, final float localY,
			final float slop, final float width, final float height) {
		return localX &gt;= -slop &amp;&amp; localY &gt;= -slop &amp;&amp; localX &lt; ((width) + slop)
				&amp;&amp; localY &lt; ((height) + slop);
	}
}
package de.passsy.multitouch;

import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.SeekBar;

public class MultitouchtestActivity extends MultiTouchActivity {
	/** Called when the activity is first created. */

	private Button btn1;
	private Button btn2;
	private Button btn3;
	private TestButton btn4;
	private SeekBar seekbar;

	@Override
	public void onCreate(final Bundle savedInstanceState) {
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
		getWindow().clearFlags(
				WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);

		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		btn1 = (Button) findViewById(R.id.button1);
		btn1.setText("My Multitouch is not enabled");
		btn2 = (Button) findViewById(R.id.button2);
		btn2.setOnTouchListener(this);
		btn2.setText("My Multitouch is enabled");
		btn3 = (Button) findViewById(R.id.button3);
		btn3.setOnTouchListener(this);
		btn3.setText("My Multitouch is enabled");
		btn4 = (TestButton) findViewById(R.id.button4);
		btn4.setOnTouchListener(this);
		addMoveOutsideEnabledViews(btn4);
		seekbar = (SeekBar) findViewById(R.id.seekbar);
		seekbar.setOnTouchListener(this);
		addMoveOutsideEnabledViews(seekbar);
	}
}
package de.passsy.multitouch;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.Button;

public class TestButton extends Button {

	public TestButton(final Context context, final AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
	}

	@Override
	public boolean onTouchEvent(final MotionEvent event) {
		Log.v("tag", "I get touched");
		setText("I recive a MotionEvent");
		if (event.getAction() == MotionEvent.ACTION_UP) {
			setText("I can recive Move events outside of my View");
		}
		return super.onTouchEvent(event);
	}
}
, , , , , , , , , , , , , , , , , , ,

27 Comments

  • [...] Activity with multitouch for Buttons Januar 11 2012 by passsy in Android, Software |1 Comment new Version avaiable here: http://www.passsy.de/multitouch-for-all-views/ [...]

  • Chen sagt:

    Briiliant work! Really useful!

  • Nash sagt:

    Thank you so much…very usefull!! It would be awesome if you include seekbar listener example

  • Giuseppe sagt:

    Good job!
    I have a question, how I can use multitouch to know when the user touches the buttons and not know when the user clicks (touch-and-release)?

  • Giuseppe sagt:

    Good job!
    I have a question, how I can use multitouch to know when the user touches the buttons and not when the user clicks (touch-and-release the buttons)?
    for example: i whant to extend this to multitouch:

    btnStartGame.setOnTouchListener(new OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
    if(event.getAction()==MotionEvent.ACTION_DOWN)
    {
    callSomeFunction();
    }
    return true;
    }
    });

    thanks ;)

  • passsy sagt:

    Look in the example in TestButton.java

    just pass your code in the onTouchEvent methode

    @Override
    public boolean onTouchEvent(final MotionEvent event) {
    if(event.getAction()==MotionEvent.ACTION_DOWN) {
    callSomeFunction();
    }
    return super.onTouchEvent(event);
    }

    You always have to extend the standard Controls and override the onTouchEvent Methode.

  • Giuseppe sagt:

    thanks for the reply, now works :)

  • Giuseppe sagt:

    One last question:
    how I can handle the event in the MultitouchtestActivity class without calling a static method from TestButton?
    thanks :)

  • Hassy sagt:

    Hi
    good job!this is really useful.
    I have a question.
    I want to create two buttons and get the MotionEvent.ACTION_MOVE at
    same time and I want to get which button was clicked and action_moved fired.
    how can I get button Id ?

    thanks a lot.

  • Senén sagt:

    Hello, and thanks for your job.
    I have exacly the same question as Hassy.
    Could you help us?
    Thanks again

  • passsy sagt:

    Sorry I don’t get it. Can you give me a real example?

    You can create a new listener and register a event on it. Does it help?

  • Senén sagt:

    I’m kind off starting with Android so sorry if I don’t explain myself properly or I dont get what you are explaining.
    What I want to do is to press two buttons at the same time, when “down” do something, and when “up” do something else. Different actions for each button.
    If I create two TestButton they do the same thing. And if I create a “btn5.setOnTouchListener(new OnTouchListener() {})” on MultitouchtestActivity I lose the moultitouch property.
    Is it clear now?
    Thx :)

  • Hassy sagt:

    Hi again

    I solved the problem.I was able to create two buttons and get the MotionEvent.ACTION_MOVE at same time!

    I created a method in TestButton(custom button) class and it returns boolean.
    public boolean getFlg() {
    return flg;
    }

    if (event.getAction() == MotionEvent.ACTION_MOVE) {
    flg = true;
    getFlg();
    }

    //get action moved
    if (Button1.getFlg() == true && Button2.getFlg() == true) {
    Log.v(“Both”, “Moved!”);
    }

    I think this could help Senén too.

    thank you.

  • Giuseppe sagt:

    Hello, sometimes when I press the buttons very quickly I have a bug like this:
    http://pastebin.com/D5vHJ9vF
    how can I fix this?
    thanks

  • passsy sagt:

    are you using the latest version? I can’t reproduce this error. Ideed line 50 is a comment line. Did you changed something in code?

  • Giuseppe sagt:

    Yes, i use the latest version.
    I did not change anything, I just added @SuppressWarning.
    my line 50 is original line 47:
    downTouchedViewsIndex[actionPointerID] = (ArrayList) hoverViews
    .clone();

    Thanks for reply :)

  • newbie sagt:

    i get the following error when building the project with “ant debug”

    can you help me how to fix it?

    [javac] C:\android\tools\multitouchtest\src\de\passsy\multitouch\MultiTouchActivity.java:217: cannot find symbol
    [javac] symbol : method getActionIndex()
    [javac] location: class android.view.MotionEvent
    [javac] final int actionPointerIndex = event.getActionIndex();
    [javac] ^
    [javac] 1 error
    [javac] 7 warnings

    BUILD FAILED

  • Miguel sagt:

    Hi,

    is there a way I could get the ‘sliding’ to work? So that if I touch and move from button to button the button selection follows the movement.

    Thanks

  • passsy sagt:

    It’s kind of tricky. I’m not sure, but i think the buttons receive the move event. But they need an onPointerDown Event to change their state.

    but i think your are on your own ;) but it shouldn’t be too hard to implement this

  • phani sagt:

    How to use this code when tab Activity is parent to our Activity.

  • Alex sagt:

    Hey!

    At first, i wanna say thanks for this project, it’s awesome!

    Might you please show, how to set different actions on e.g. button 2 and 3? That would be so great!

    nice blog btw

  • rob sagt:

    Hi !

    nice code, works like magic :) except one case :(

    i try to use 2 seekbars, one of them rotated for 90° or 270°

    so when i touch the vertical first only this one works

    when i touch the horizontal seekbar first, the vertical works too
    but has behavior like a horizontal

    is there trick to fix this?

    greets rob

  • passsy sagt:

    I think you just overwrote the onDraw method to rotate the Seekbar by 90°. you have to override the onTouch method too.

  • koko sagt:

    hii,i newbie on android developing,i want to make a simple piano aplication,but i have a trouble to make my aplication support multitouch,i still be confuse to make it,,

    i have 3 buttons,when i press buttonA,play soundA,and when i press buttonB,play soundB,and then when i press buttonC,play SoundC.now i have trouble when i want to press two buttons on same time,ex buttonA & buttonC,the sound is not playing,how can resolve it ?

  • loco sagt:

    how to i know which button i press on class testbutton,example..

    public boolean onTouchEvent(final MotionEvent event) {
    Log.v(“tag”, “I get touched”);
    setText(“I recive a MotionEvent”);
    if (event.getAction() == MotionEvent.ACTION_UP) {
    if ( i press button1 ) do methodA
    if ( i press button2 ) do methodB
    }
    return super.onTouchEvent(event);
    }
    how we know user click button1 or button2, or both.. thanks :)

Hinterlasse eine Antwort

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>