I'm trying to figure out all the multi - touch event handling in android for couple of hours and I just don't get it.
If I have multi touch event MotionEvent stores all fingers data right?
Say I touch the screen with 3 fingers simultaneously or move 2 fingers on the screen simultaneously when asking getActionIndex or ( getActionMasked ) I get one result but as I move or ( touch the screen with 3 fingers ) so how do I know for which finger getActionIndex or ( getActionMasked ) refers to?
Thank u in advance
ID is used to identify and track pointers (let just say pointer is your finger). While index for a particular touch gesture might change (you might lift the finger etc) but id remains the same (thus used for tracking gestures).
int pointerCount = m.getPointerCount();
for (int i = 0; i < pointerCount; i++)
{
int x = (int) m.getX(i);
int y = (int) m.getY(i);
int id = m.getPointerId(i);
if(id==0) {
...First touch....
}
........
I am also new to android development. Was reading about multi touch yesterday. Hope this helps.
Related
I look for a way how to inject input events such as touch or keyboard events into the own application, from code within that particular app. NOT into an Android OS or to other apps b/c that needs a signature-level permission android.permission.INJECT_EVENTS or rooted device and I have to avoid both. I look for a code that runs on a non-rooted device without anyhow elevated rights.
I'm somehow making a bit of progress with code based on the following concept:
View v = findViewById(android.R.id.content).getRootView();
v.dispatchTouchEvent(e);
I think getRootView and dispatch it should works well, if there is any problem with it. Please just brief it.
Or there is an more high level but tricky way to do this.
in View.java, there is a hide api called getViewRootImpl() which will return a object of android.view.ViewRootImpl of this Activity. And there is a method called enqueueInputEvent(InputEvent event) on it. So that you can use reflection to get this method.
As I have just finished a project which using Keyboard to send touch commands to the screen, I think when you are trying to inject these kind of events to the activity, multiple touch might be a problem. When you try to send another finger to the screen. The event action must be ACTION_POINTER_DOWN with the index shift of the array named properties. And you should also handle the properties and coordses arrays which including the info of all the fingers each time, even if you just need to move only one finger at this time. The finger's id which is set to properties[i].id is used to identify your finger. But the index of the array is not solid.
for (int i = 0; i < size; i++) {
properties[i].id = fingerId;
properties[i].toolType = MotionEvent.TOOL_TYPE_FINGER;
coordses[i].x = currentX;
coordses[i].y = currentY;
coordses[i].pressure = 1;
coordses[i].size = DEFAULT_SIZE;
}
event = MotionEvent.obtain(
mDownTime, SystemClock.uptimeMillis(),
((size - 1) << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
+ MotionEvent.ACTION_POINTER_DOWN,
size,
properties, coordses, DEFAULT_META_STATE, DEFAULT_BUTTON_STATE,
DEFAULT_PRECISION_X, DEFAULT_PRECISION_Y, DEFAULT_DEVICE_ID,
DEFAULT_EDGE_FLAGS, InputDevice.SOURCE_TOUCHSCREEN, DEFAULT_FLAGS);
I managed to solve that in a way that it handles not only a Activity root window but also to a windows that are created for various overlays such as menus and dialogs.
The code is here: https://github.com/TeskaLabs/Remote-Access-Android/blob/master/tlra/src/main/java/com/teskalabs/tlra/android/inapp/InAppInputManager.java
In our own application you can just use the input cli command:
public static final void tapButton (int x, int y) throws IOException, InterruptedException {
Runtime.getRuntime().exec("input tap " + x + " " + y);
}
I have an Imageview with different number of touch points on them. It basically an app which is detecting the swipe between 2 touch points and not allowing the user to swipe any other point or in or out of other direction. It should constrict user to just swipe between two touch points.
Just take a look at following picture:
Now the user should start swiping from point 1 to point 2. if the swipe is not started from starting point 1, it should not color the path between point 1 and point 2.
But if the user successfully swipe between the point 1 and point 2 now swipe between point 2 to 3 should be enabled. Thus user should go through Point 1 to 2, Point 2 to 3 , Point 3 to 4 , point 4 to point 5 to complete round 1.
Please tell me how to achieve this functionality . I know about gestures, gesture overlay etc but none of them fits to my condition as they uses general touch events and gesture directions.
Please suggest me the way to achieve this and keep in mind I want to make this app to be able to run on all type of devices , so I can simply give the hard coded x,y values.
Edit : on Demand
I am posting the link of the app on play store who has same functionality , But I do not know How they are achieving this functionality .
https://play.google.com/store/apps/details?id=al.trigonom.writeletters
If each touch point can be created as individual views(e.g. ImageView), then you can create an inViewInBounds() function.
I used code from here to create code where I needed to detect finger press movement over multiple imageViews:
Rect outRect = new Rect();
int[] location = new int[2];
//Determine if a touch movement is currently over a given view.
private boolean inViewInBounds(View view, int x, int y){
view.getDrawingRect(outRect);
view.getLocationOnScreen(location);
outRect.offset(location[0], location[1]);
return outRect.contains(x, y);
}
To use this function, on a parent view to all those child touch points, set the Click and Touch Listeners:
//This might look redundant but is actually required: "The empty OnClickListener is required to keep OnTouchListener active until ACTION_UP"
parentView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {}
});
//All the work gets done in this function:
parentView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
int x = (int)event.getRawX();
int y = (int)event.getRawY();
// ** myTouchPoint might be an array that you loop through here...
if ( inViewInBounds(myTouchPoint, x, y) ) doLogic(myTouchPoint);
return false;
}
});
The code above only shows detecting when one of your views are 'touched'.
if none are 'touched' but a view is 'active' (e.g. When a touch is detected, set a variable like: viewLastTouched = myTouchPoint) then you would call something like drawingLine(viewLastTouched, x, y) function - for whatever it needed to do to draw the line and/or detect boundaries etc.
They are not using android native java code to build this app.
The app is running with this code
import Runtime.MMFRuntime;
public class Main extends MMFRuntime {
}
This in turn is from https://github.com/ClickteamLLC/android/blob/master/docs/index.md
This is used to package apps / games written using - http://www.clickteam.com/clickteam-fusion-2-5
I develop a game that need user to have a single touch to draw over to link multiple objects. Currently using mouse button as touch input with this code:
if(Input.GetButton("Fire1"))
{
mouseIsDown = true;
...//other codes
}
It works fine with mouse but it will give me problem when I compile it for multi touch device. In multitouch device, if you have 2 fingers down, the middle point will be taken as the input. But if I already touch an object then with second finger down to screen, it will mess up my game input and give me havoc design.
What I want is to limit it to 1 touch only. If the second finger come touching, ignore it. How can I achieve that?
Below is all you need:
if ((Input.touchCount == 1) && (Input.GetTouch (0).phase == TouchPhase.Began)) {
mouseIsDown = true;
...//other codes
}
It will only fire when one finger is on the screen because of Input.touchCount == 1
You are using Input.GetButton. A button is not a touch. You probably want to look at Input.GetTouch
If you need to support multiple input-type devices, you may want to consider scripting your own manager to abstract this out somewhat.
I would roll with a bit of polling!
so in your Update() I typically do something like this:
foreach (Touch touch in Input.touches)
{
// Get the touch id of the current touch if you're doing multi-touch.
int pointerID = touch.fingerId;
if (touch.phase == TouchPhase.Began)
{
// Do something off of a touch.
}
}
If you're looking for more info check this out:
TouchPhases in Unity
I have a bitmap that I can drag around on my app, but there is a small bug, if let's say someone clicks with one finger on the bitmap and starts moving it around, and then while moving it around clicks with another finger, the bitmap "teleports" over there. How can I detect if the user switched fingers so bitmaps wont teleport? this is my Action_MOVE code:
case MotionEvent.ACTION_MOVE:
if (dragging) {
CurrentMobEntry.getKey().x = (int) x
- CurrentMobEntry.getValue().getNormalbit()
.getWidth() / 2;
CurrentMobEntry.getKey().y = (int) y
- CurrentMobEntry.getValue().getNormalbit()
.getHeight() / 2;
CurrentMobEntry.getValue().getDestroyedP().x=(int)x
+CurrentMobEntry.getValue().getNormalbit()
.getWidth() / 2;
CurrentMobEntry.getValue().getDestroyedP().y = (int)y
+ CurrentMobEntry.getValue().getNormalbit()
.getHeight() / 2;
}
break;
From the documentation:
Some devices can report multiple movement traces at the same time. Multi-touch screens emit one movement trace for each finger. The individual fingers or other objects that generate movement traces are referred to as pointers. Motion events contain information about all of the pointers that are currently active even if some of them have not moved since the last event was delivered.
The number of pointers only ever changes by one as individual pointers go up and down, except when the gesture is canceled.
So what you want to do is to allow only for one pointer (finger) to operate in any given time. I can't seem to find a simple solution to this so we are going to use a workaround:
Each pointer gets a unique, constant ID. On any given touch event, you will check to see if that pointer initiated the event and then if true consume it, else ignore.
For a full example of this check the Android Developer's blog.
I'm currently developing an air-hockey simulation for android. For the multiplayer mode I'm tracking two touch events on the screen, which works well as long as the touch points don't get to close.
When the two fingers get to close, android only recognizes one touch event, in the middle of both points.
To make it even worse, android sometimes messes up the IDs after the collision.
I already thought about estimating the next touch points ans assigning IDs manually, does anybody know a better way, or knows about somebody who already fixed this problem programmatically?
NOTE: I'm testing on a Samsung Galaxy S 3
Not necessarily a logical fix to the issue, nevertheless a possible solution to the application:
If I'm not completely mistaken, air-hockey games shouldn't allow opponents to intrude on each others game field. If we assume a thick border cross the center of the screen (in portrait mode), then I wouldn't be allowed to do anything beyond that border, hence there is no point in tracking my finger after it reaches the border line.
Encapsulating your tracked touch events into valid physical locations as described might just help you in ignoring invalid points (given that the physical locations doesn't intersect, that is).
You might also have to keep track of direction of the touch vector: if the vector is stretching from the center of the screen towards "your end" it might be the opponents intruding finger or your own returning finger. In neither case should they affect the hockey puck (perhaps).
It may depend on the device you are using, but I'm using the code below in a Huawei X5 and It never mess up fingers, even if they touch each other or it I twist them over the screen.
private static PointF touchScreenStartPtArr[] = new PointF[10];
private static PointF touchScreenStopPtArr[] = new PointF[10];
private static PointF touchScreenCurrPtArr[] = new PointF[10];
OnTouchListener onTouchListenerMulti = new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction() & MotionEvent.ACTION_MASK;
int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
int fingerId = event.getPointerId(pointerIndex);
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
touchScreenStartPtArr[fingerId].x = event.getX(pointerIndex);
touchScreenStartPtArr[fingerId].y = event.getY(pointerIndex);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_CANCEL:
touchScreenStopPtArr[fingerId].x = event.getX(pointerIndex);
touchScreenStopPtArr[fingerId].y = event.getX(pointerIndex);
break;
case MotionEvent.ACTION_MOVE:
int pointerCount = event.getPointerCount();
for (int i = 0; i < pointerCount; i++) {
touchScreenCurrPtArr[fingerId].x = event.getX(i);
touchScreenCurrPtArr[fingerId].y = event.getY(i);
}
break;
}
return true;
}
};
Note that I'm using fingerId and not pointerId to identify the correct finger, as pointer id may change when one finger is released.
Hope it works for you.
Here's the way I see it.
The touchscreen hardware gives you a resolution below which two touches are the same as one. This is something you cannot change.
Now the question is, what to do when two touches merge? (This is something that can be tested for programmatically one would think; e.g. if 2 touch pts -> 1 touch pt. AND prev touch pt 1 is close enough to prev touch pt 2...). In your case, I would move both pucks along the merged touch gesture until they separate, then return individual control.
Of course, I see several problems with this, like which touch controls which puck after the split? Maybe one person lifted their finger during the merge.
You could have both players lose control of their puck if a merge occurs. This could simulate the shock to the wrist as your hand bashes into your opponent's :)
I also like #dbm idea.
Hope this helps. Probably didn't :)