This should be a pretty simple thing to do. The user puts his finger on the screen and drags it around the screen. There are two events firing on onTouch:
MotionEvent.ACTION_DOWN
MotionEvent.ACTION_MOVE
Now, how can I calculate the speed of the ACTION_MOVE gesture ? The user drags the finger slower or faster during a gesture, so I think I need to calculate the speed between two intermediate touched points: the lastTouchedPointX,lastTouchedPointY and the event.getX(),event.getY().
Has anyone done this before ?
What you need can be achieved by using the standard VelocityTracker class. More details on Google's Best Practice for User Input while tracking movement here. Most of the code below (which demonstrates the use of VelocityTracker by displaying the speed on X and Y axis of each fling/move) is taken from the previous resource link:
import android.os.Bundle;
import android.app.Activity;
import android.support.v4.view.VelocityTrackerCompat;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.widget.TextView;
public class MainActivity extends Activity {
private VelocityTracker mVelocityTracker = null;
private TextView mTextView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mTextView = new TextView(this);
mTextView.setText("Move finger on screen to get velocity.");
setContentView(mTextView);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
int index = event.getActionIndex();
int action = event.getActionMasked();
int pointerId = event.getPointerId(index);
switch (action) {
case MotionEvent.ACTION_DOWN:
if (mVelocityTracker == null) {
// Retrieve a new VelocityTracker object to watch the velocity
// of a motion.
mVelocityTracker = VelocityTracker.obtain();
} else {
// Reset the velocity tracker back to its initial state.
mVelocityTracker.clear();
}
// Add a user's movement to the tracker.
mVelocityTracker.addMovement(event);
break;
case MotionEvent.ACTION_MOVE:
mVelocityTracker.addMovement(event);
// When you want to determine the velocity, call
// computeCurrentVelocity(). Then call getXVelocity()
// and getYVelocity() to retrieve the velocity for each pointer ID.
mVelocityTracker.computeCurrentVelocity(1000);
// Log velocity of pixels per second
// Best practice to use VelocityTrackerCompat where possible.
mTextView.setText("X velocity: "
+ VelocityTrackerCompat.getXVelocity(mVelocityTracker,
pointerId)
+ "\nY velocity: "
+ VelocityTrackerCompat.getYVelocity(mVelocityTracker,
pointerId));
break;
case MotionEvent.ACTION_UP:
break;
case MotionEvent.ACTION_CANCEL:
// Return a VelocityTracker object back to be re-used by others.
mVelocityTracker.recycle();
break;
}
return true;
}
}
#Override
public boolean onTouchEvent(MotionEvent event, MapView mapView) {
if(event.getAction() == MotionEvent.ACTION_DOWN) {
oldX = event.getX();
oldY = event.getY();
//start timer
} else if (event.getAction() == MotionEvent.ACTION_UP) {
//long timerTime = getTime between two event down to Up
newX = event.getX();
newY = event.getY();
float distance = Math.sqrt((newX-oldX) * (newX-oldX) + (newY-oldY) * (newY-oldY));
float speed = distance / timerTime;
}
}
It's a question about how accurate you want to perform this calculation.
However the basic procedure is to get the timestamp of each corresponding ACTION_DOWN, ACTION_UP couple and calculate the difference.
Then you need to determine the covered pixels. This could be done with simple trigonometry.
When you have both time difference and covered pixels you can calculate the pixels per time speed as an average of the two points (down and up).
You can do this for every point when the finger is moving over the screen to get a better result.
Image in Canvas with touch events
from this example get touch event time & calculate time & distance to get speed
Related
We have an Android app that is meant to be mounted on the dash of a vehicle that is operating in rough terrain, so everything is really shaky. We've found that making a single tap on the screen in this kind of situation is difficult, as the taps are often interpreted as small drags.
What I need is for touch events that have a little wiggle before the finger comes up to be interpreted as clicks, not drags. I've been doing some reading into the way the 'touch slop' works in Android and I can see that they already account for this. All I really need to understand is how to increase the 'touch slop' for a subclass of an Android button widget.
Is this possible in just a couple of lines of code? Or do I need to do my own implementations of onInterceptTouchEvent and 'onTouchEvent`? If the latter, can anyone give me some direction on how this would work?
Here is what I did, hope that help you guys.
private Rect mBtnRect;
yourView.setOnTouchListener(new OnTouchListener() {
private boolean isCancelled = false;
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
isCancelled = false;
parent.requestDisallowInterceptTouchEvent(true); // prevent parent and its ancestors to intercept touch events
createClickArea(v);
// your logic touch down
return true;
case MotionEvent.ACTION_UP:
if(!isCancelled) {
// Click logic
}
// release mBtnRect when cancel or up event
mBtnRect = null;
return true;
case MotionEvent.ACTION_CANCEL:
isCancelled = true;
releaseTouch(parent, v);
return true;
case MotionEvent.ACTION_MOVE:
if(!isBelongTouchArea(event.getRawX(), event.getRawY())) {
isCancelled = true;
releaseTouch(parent, v);
}
return true;
default:
break;
}
}
// Create the area from the view that user is touching
private final void createClickArea(View v) {
// for increase rect area of button, pixel in used.
final int delta = (int) mContext.getResources().getDimension(R.dimen.extension_area);
final int[] location = new int[2];
// Get the location of button call on screen
v.getLocationOnScreen(location);
// Create the rect area with an extension defined distance.
mBtnRect = new Rect(v.getLeft() - delta, location[1] + v.getTop() - delta, v.getRight(), location[1] + v.getBottom() + delta);
}
//Check the area that contains the moved position or not.
private final boolean isBelongTouchArea(float rawX, float rawY) {
if(mBtnRect != null && mBtnRect.contains((int)rawX, (int)rawY)) {
return true;
}
return false;
}
private void releaseTouch(final ListView parent, View v) {
parent.requestDisallowInterceptTouchEvent(false);
mBtnRect = null;
// your logic
}
For our simple use-case with a android.opengl.GLSurfaceView, we solved the same problem you have by saying, "if the distance moved in the gesture is less than some_threshold, interpret it as a click". We used standard Euclidean distance between two 2D points = sqrt((deltaX)^2 + (deltaY)^2)), where deltaX and deltaY are the X and Y components of the distance moved in the user's motion gesture.
More precisely, let (x1,y1) be coordinates where user's finger gesture 'started' and let (x2,y2) be coordinates where the gesture 'ended'.
Then, deltaX = x2 - x1 (or it can also be x1 - x2 but sign doesn't matter for the distance 'cz we square the value) and similarly for deltaY.
From these deltas, we calculate the Euclidean distance and if it's smaller than a threshold, the user probably intended it to be a click but because of the device's touch slop, it got classified as a move gesture instead of click.
Implementation-wise, yes we overrode android.view.View#onTouchEvent(MotionEvent) and
recorded (x1,y1) when masked action was android.view.MotionEvent#ACTION_POINTER_DOWN or android.view.MotionEvent#ACTION_DOWN
and when masked action was android.view.MotionEvent#ACTION_MOVE, (x2,y2) are readily available from the event's getX and getY methods (see corresponding documentation)
currently i am looking for ideas how to implement a dwelling time to the onTouch function. Normally the onTouch event does only process the events Up, Down, and Motion.
I want the user to be able to touch down anywhere, after that he can swipe to an object on the screen and if he stays still (=little to no movements) for a certain time an action is fired.
Is there a ways to get events for not moving while touching or any other type of event i can use for this behaviour? My current solution is rather... ugly
touch = new Vector2D(event.getX(),event.getY());
//if (not moving && touching && (System.nanoTime-currentNanoSeconds) > Value)
if(event.getAction()==MotionEvent.ACTION_MOVE){
currentNanoSeconds=System.nanoTime();
System.out.println(currentNanoSeconds);
}
if(event.getAction()== MotionEvent.ACTION_UP){
currentAngle=(float) angleBetween2Lines(midPoint, lineEnd, touch);
System.out.println((System.nanoTime()-currentNanoSeconds)/1000000);
touching=false;
checkTargets();
//forces redraw!
invalidate();
}
if(event.getAction()==MotionEvent.ACTION_DOWN)
touching=true;
return true;
thanks in advance.
I think you just need to add some spatial conditions to this answer. Something like (very loosely):
maxMovement = ?;
final Handler handler = new Handler();
Runnable mLongPressedinSameArea = new Runnable() {
public void run() {
if (*pseudo code*: the deltas between up/down X and Y are not greater than maxMovement){
Log.i("", "Long press in same area!");
}
}
};
#Override
public boolean onTouchEvent(MotionEvent event, MapView mapView){
if(event.getAction() == MotionEvent.ACTION_DOWN)
downX = event.getX();
downY = event.getY();
handler.postDelayed(mLongPressedinSameArea, 1000);//1 second linger time.
if(event.getAction() == MotionEvent.ACTION_UP))
upX = event.getX();
upY = event.getY();
handler.removeCallbacks(mLongPressedinSameArea);
return super.onTouchEvent(event, mapView);
}
I am using a small square RelativeLayout as a game pad for movement in a neighboring GLSurfaceView.
I set it up so that in the onTouch method of the RelativeLayout's onTouchListenner I call a method built into the GLSurfaceView that updates the translation and rotation coordinates for my drawing.
I have everything working fine, except for the fact that touch events are only triggered if the user moves his or her finger on the RelativeLayout.
I would like it to feel a little bit like a joystick: if you leave your finger pressed on the top of the RelativeLayout, then you will keep going "up" ( or more programatically: the event.getX() and event.getY() that were sent last should get looped, or re-sent, in the GLSurfaceView until the user either moves his finger inside the RelativeLayout, or stops touching the RelativeLayout altogether.)
What should I use to detect whether or not the RelativeLayout is currently being touched (even if there is no motion in the said touch)?
Thanks!
So this is what I came up pretty much simultaneously with csmcklvey
if(event.getAction()==MotionEvent.ACTION_UP)
gM.isTouchedL = false;
else
gM.isTouchedL = true;
return true;
Where .isTouchedL is the "control boolean" I use in the GLSurfaceView
Green lit csmc's answer anyways! :)
I have used rotation animation in one of my apps, which is using the onTouchEven of view. Major function has been performed in event "ACTION_MOVE". It will help you, to get through.
public boolean onTouch(View v, MotionEvent event)
{
final float xc = volumeButton.getWidth() / 2;
final float yc = volumeButton.getHeight() / 2;
final float x = event.getX();
final float y = event.getY();
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
{
// volumeButton.clearAnimation();
// mCurrAngle = Math.toDegrees(Math.atan2(x - xc, yc - y));
break;
}
case MotionEvent.ACTION_MOVE:
{
mPrevAngle = mCurrAngle;
mCurrAngle = Math.toDegrees(Math.atan2(x - xc, yc - y));
animate(mPrevAngle, mCurrAngle, 100);
break;
}
case MotionEvent.ACTION_UP :
{
mPrevAngle = mCurrAngle = 0;
break;
}
}
return true;
}
You might be able to use the event.getAction() method of the MotionEvent parameter of your onTouch() method. You can check to see if it is MotionEvent.ACTION_DOWN or MotionEvent.ACTION_UP.
if (event.getAction() == MotionEvent.ACTION_DOWN) {
//start something
}
else if (event.getAction() == MotionEvent.ACTION_UP) {
//stop something
}
My thought is that you might be able to start or stop a thread in this manner but either way this might at least give you some more information about when the user actually presses and lifts his/her finger.
I'm developing a game with 2 functions one is called singleClick() and the other
moveClick(float dx, float dy)
singleClick() is called from MotionEvent.ACTION_UP
moveClick() is called from MotionEvent.ACTION_MOVE (where dx = x- previousX)
My problem: If I try a move action with my finger, the single click is also called. The opposite is also the same, when I single click something with my finger, the moveClick() is called.
I can handle the moveClick() being called at single click but the singleClick() calls when player is moving really screws up my game controls.
How can I know which is which??
I'm thinking this may have something todo with consuming the touch events. In your onclick listener keep in mind that if you return true that signals you have consumed the event, if you return false that event will continue to fall through and trigger the next listener.
Well if you don't want to call singleClick() when the player is moving where he touches, then you can't call singleClick from ACTION_UP, because ACTION_UP gets called each time. Maybe add a flag that keeps track of whether its a move event. If the total euclidean distance, sqrt(dx^2 + dy^2) > threshold, then change the flag isMove to true. If the total distance is less than that threshold, then call singleClick() in your ACTION_UP.
You might also want to think about timing how long the touch event is, or the total absolute distance (i.e. if someone draws a circle and comes back to the first point, my previous method would register as a single click). Think about how you want the flag to work.
I was hoping for an elegant solution, maybe something built-in with android development.. but I guess there is none so I took Benoir's advice and wrote this very not-elegant code (which seems to work well enough)
long millisLastTouch=0, millisDif,millisStartTouch=0;
final long minMillisSm = 200, minMillisBig = 300;
boolean isMove = true;
public void run(){//my main thread/loop
.....
millisDif = System.currentTimeMillis() - millisLastTouch;
if (!isMove && millisDif>minMillisBig) {
G.currentScreen.singleClick(mPreviousX,mPreviousY);
isMove = true;//to prevent multiple single clicks
}
.....
}//end of main loop
public boolean onTouchEvent(MotionEvent e) {
float x = e.getX(); float y = e.getY();
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN: { }//do nothing
case MotionEvent.ACTION_UP:{
mPreviousX = x; mPreviousY = y; }
case MotionEvent.ACTION_MOVE:{
boolean newAction;
millisDif = System.currentTimeMillis() - millisLastTouch;
if (millisDif> minMillisSm) {
newAction = true;
millisStartTouch = System.currentTimeMillis();
}
else newAction = false;
millisLastTouch = System.currentTimeMillis();
millisDif = System.currentTimeMillis() - millisStartTouch;
if (newAction == false && millisDif > minMillisSm){
float dx = x - mPreviousX; float dy = y - mPreviousY;
G.currentScreen.clickMove(x,y,dx,dy);
isMove = true;
}
else isMove = false;
mPreviousX = x; mPreviousY = y;
}
}//switch
return true;
}//onTouchEvent
It's time-based only, not distance. If the user touches screen for enough time it will translate as a move action. Otherwise, it remembers the x and y position and will call singleClick() from the main thread
I was wondering how to get accurate, live get(x) and get(y) values for a MotionEvent? What is happening is that when I touch a specific area on the screen, I tell an action to happen.
The problem is that once I touch the screen and take my finger off, it still thinks my finger is at the same location (since that was the last location I touched). So when I have more than one Down event (for multitouch) it throws everything off. Is there a way to reset the X and Y values so when I let off the screen, they go back to 0 or null (or whatever)? Thanks
What you are describing isn't a problem. You the programmer are responsible for keeping track of the touch locations and what they mean. If you care about motion you need to keep track of the previous touch and the current touch each time a touch occurs. I find something like this works great:
public int x=-1,y=-1,prevX=-1, prevY=-1;
public boolean onTouch(View v, MotionEvent event)
{
prevX = x;
prevY = y;
int x = (int)event.getX();
int y = (int)event.getY();
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
// there is no prev touch
prevX = -1;
prevY = -1;
// touch down code
break;
case MotionEvent.ACTION_MOVE:
// touch move code
break;
case MotionEvent.ACTION_UP:
// touch up code
break;
}
return true;
}
Just catch the MotionEvent.ACTION_UP or ACTION_MOVE event, depending on when the action needs to happen (since you want it live, you should use the ACTION_MOVE event). You should handle setting external variables when ACTION_DOWN occurs, and handle the results on ACTION_MOVE or ACTION_UP.