Multitouch for all Views
Here is the new Version of my implementation of the MultiTouchActivity.
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:
- http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/view/View.java?av=f
- http://www.zdnet.com/blog/burnette/how-to-use-multi-touch-in-android-2-part-3-understanding-touch-events/1775
Download Example 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("tag", location + "");
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 (>mTouchSlop)
if (recentTouchedViewsIndex[actionPointerID] != null) {
if (pointInView(x, y, mTouchSlop, view.getWidth(),
view.getHeight())) {
// Log.v("tag", "added because < mTouchSlop");
if (!recentTouchedViewsIndex[actionPointerID]
.contains(view)) {
recentTouchedViewsIndex[actionPointerID].add(view);
}
} else if (moveOutsideEnabledViews.contains(view)) {
Log.v("tag", "outside but gets event");
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("tag",
"#" + actionPointerIndex + " Rawx:" + rawX + " rawy:"
+ rawY + " x:" + x + " y:" + y + " "
+ view.toString());
}
}
}
private void dumpEvent(final MotionEvent event) {
final String names[] = { "DOWN", "UP", "MOVE", "CANCEL", "OUTSIDE",
"POINTER_DOWN", "POINTER_UP", "7?", "8?", "9?" };
final StringBuilder sb = new StringBuilder();
final int action = event.getAction();
final int actionCode = action & MotionEvent.ACTION_MASK;
sb.append("event ACTION_").append(names[actionCode]);
if (actionCode == MotionEvent.ACTION_POINTER_DOWN
|| actionCode == MotionEvent.ACTION_POINTER_UP) {
sb.append("(pid ").append(
action >> MotionEvent.ACTION_POINTER_ID_SHIFT);
sb.append(")");
}
sb.append("[");
for (int i = 0; i < event.getPointerCount(); i++) {
sb.append("#").append(i);
sb.append("(pid ").append(event.getPointerId(i));
sb.append(")=").append((int) event.getX(i));
sb.append(",").append((int) event.getY(i));
if (i + 1 < event.getPointerCount()) {
sb.append(";");
}
}
sb.append("]");
Log.d("tag", 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() > 0) {
for (int i = 0; i < 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 < possibleViews.size(); i++) {
final View view = possibleViews.get(i);
final int location[] = { 0, 0 };
view.getLocationOnScreen(location);
if (((view.getHeight() + location[1] >= y)
& (view.getWidth() + location[0] >= x)
& (view.getLeft() <= x) & (view.getTop() <= 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() & MotionEvent.ACTION_MASK;
if (actionResolved < 7 && actionResolved > 4) {
actionResolved = actionResolved - 5;
}
if (actionResolved == MotionEvent.ACTION_MOVE) {
for (int ptrIndex = 0; ptrIndex < event.getPointerCount(); ptrIndex++) {
// only one event for all move events.
dealEvent(ptrIndex, event, v, actionResolved);
Log.v("tag", "move" + 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 >= -slop && localY >= -slop && localX < ((width) + slop)
&& localY < ((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);
}
}
--seekbar, -arraylist-removeall, activity, Android, android-button-seekbar-multi-touch, android-event-getpointerid-view, android-multitouchactivity, android-view-gettop-window-feature_no_title, code-for-addview-with-x-and-y-android, getlocationonscreen-contains-action_move, motionevent-obtain, motionevent-obtain-multi-touch, multi-touch-android-2-button, multitouch, multitouch-android-layout-params, multitouch-android-seekbar, pointinview, seekbar-multi, view, windowmanager-multitouch-android





[...] 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/ [...]
Briiliant work! Really useful!
Thank you so much…very usefull!! It would be awesome if you include seekbar listener example
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)?
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
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.
thanks for the reply, now works
One last question:
how I can handle the event in the MultitouchtestActivity class without calling a static method from TestButton?
thanks
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.
Hello, and thanks for your job.
I have exacly the same question as Hassy.
Could you help us?
Thanks again
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?
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
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.
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
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?
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
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
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
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
[...] Multitouchng code [...]
How to use this code when tab Activity is parent to our Activity.
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