Last MotionEvent with strange coordinates - android

We have puzzle game Fruit Dating that is some time already out. Now we got report from one of our players that he can not touch any item in menu. We sent him special version with onscreen debugging. We focused on touches and he returned us this result:
There is every touch event intercepted. I split set of touch events for every single "game touch" in the picure with red lines on the left. Every game touch event is like this: down - (one or more drag) - up. What is strange is that before there is touch up there is always big jump in touch coordinates with touch dragged event.
I think that most of the drag events is just bouncing but there is always at least one that jumps coordinates to lower left corner. Following up event is then with these incorrect coordinates. As we are checking whether the player is touching up in the same area (see area parameter) as he began when touching down then there is never any match and player has feeling that the game is stuck.
The player that reported error has THL W200 phone. We previously tested on lot of different physical devices but never encountered this problem...
Does anyone have some idea why there is motion event (drag) with such a strange coordinates? Did any of you encountered it?
Our game is JNI with thin Java layer. Motionevents are sent to c++ from view like this:
#Override
public boolean onTouchEvent(final MotionEvent aEvent)
{
final int action = aEvent.getAction();
queueEvent(new Runnable()
{
int motionEvent = -1;
public void run()
{
if (action == MotionEvent.ACTION_DOWN)
{
motionEvent = ANDROID_TOUCH_ACTION_DOWN;
}
else if (action == MotionEvent.ACTION_UP)
{
motionEvent = ANDROID_TOUCH_ACTION_UP;
}
else if (action == MotionEvent.ACTION_MOVE)
{
motionEvent = ANDROID_TOUCH_ACTION_MOVE;
}
SBCEngine.engine_on_touch((int) aEvent.getX(), (int) aEvent.getY(), motionEvent);
}
});
return true;
}

Finally found the solution, so if anyone is interested...
The above code is not safe. Before the event is processed the referenced event object may change. The code was taken from book on porting Doom and Quake to Android and you may found similar code at web. It worked on all tested devices until yesterday. It failed on THL W200 device.
I changed the code to this:
#Override
public boolean onTouchEvent(final MotionEvent aEvent)
{
// get masked (not specific to a pointer) action
int maskedAction = aEvent.getActionMasked();
switch (maskedAction)
{
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
{
// get pointer index from the event object
int pointerIndex = aEvent.getActionIndex();
// get pointer ID
int pointerId = aEvent.getPointerId(pointerIndex);
//Log.d("POINTER", "pointer down: ID = " + pointerId + " x=" + aEvent.getX(pointerIndex) + ", y = " + aEvent.getY(pointerIndex));
queueEvent(new MotionEventRunnable(pointerId, maskedAction,
(int) aEvent.getX(pointerIndex),
(int) aEvent.getY(pointerIndex)));
break;
}
case MotionEvent.ACTION_MOVE:
{
// a pointer was moved
int size = aEvent.getPointerCount();
for (int i = 0; i < size; i++)
{
// Get the pointer ID and index
int pointerId = aEvent.getPointerId(i);
int pointerIndex = aEvent.findPointerIndex(pointerId);
//Log.d("POINTER", "pointer move: ID = " + pointerId + " x=" + aEvent.getX(pointerIndex) + ", y = " + aEvent.getY(pointerIndex));
queueEvent(new MotionEventRunnable(pointerId, maskedAction,
(int) aEvent.getX(pointerIndex),
(int) aEvent.getY(pointerIndex)));
}
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_CANCEL:
{
// get pointer index from the event object
int pointerIndex = aEvent.getActionIndex();
// get pointer ID
int pointerId = aEvent.getPointerId(pointerIndex);
//Log.d("POINTER", "pointer up: ID = " + pointerId + " x=" + aEvent.getX(pointerIndex) + ", y = " + aEvent.getY(pointerIndex));
queueEvent(new MotionEventRunnable(pointerId, maskedAction,
(int) aEvent.getX(pointerIndex),
(int) aEvent.getY(pointerIndex)));
break;
}
}
return true;
}
with MotionEventRunnable class like this:
class MotionEventRunnable implements Runnable
{
private int mPointerID;
private int mActionID;
private int mX, mY;
//-----------------------------------------------------
public MotionEventRunnable(int aPointerID, int aActionID, int aX, int aY)
{
mPointerID = aPointerID;
mActionID = aActionID;
mX = aX;
mY = aY;
}
//-----------------------------------------------------
#Override
public void run()
{
// send to native
SBCEngine.engine_on_touch(mPointerID, mX, mY, mActionID);
}
}
The code is more complex as it also handles multitouch but the point is that I cache all the required values: pointer id, action, x and y. It solved my problems.

Related

android display a response while user performs a gesture

How to show a response while gesture input is in progress?
I'm using swipe down gesture to reload my WebView.
Using this for gesture detection:How to detect swipe direction between left/right and up/down
Problem
SimpleGestureListener captures the result of user input only. It cannot be used to show a response, e.g. animation, while the user is performing a gesture.
Imperfect inelegant solution:
flaw: Shows animation independent of SimpleOnGestureListener; a response to user gesture input is displayed and gesture input may still fail or vice versa.
private volatile float userTouchY = -1;
// translating WebView every onTouch event call is inefficient.
// only translate when translateYBy value is greater than a threshold.
private float translateYBy = 0;
/**
* Shift webView without animation.
* #param num
*/
private void __shiftWebViewDownBy(int num){
float current = webView.getY();
if(current >= webviewTranslationLimit) return;
current += num;
if(current> webviewTranslationLimit) current = webviewTranslationLimit;
webView.setY(current);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
//Skip everything unless at the content top.
if(webView.getScrollY() > 0) return false;
int action = event.getAction();
if(action == MotionEvent.ACTION_MOVE){
float y = event.getY();
float dif = y - userTouchY;
if(dif < webviewTranslationLimit/10){
//dif less than screenHeight*1/40, ignore input
}else if(dif < -(webviewTranslationLimit/5)){
//swipe up
userTouchY = -1;//not swipe down cancelling
__shiftWebViewTopTo(0);
}else if(userTouchY < y) {
//swipe down
translateYBy += dif;
if(userTouchY == -1){
//userTouchY at the initial value, ignore for this once.
userTouchY = y;
}else{
userTouchY = y;
if(translateYBy > 5 ){
__shiftWebViewDownBy((int)translateYBy);
translateYBy = 0;
}
}
}
}else{
Log.d(TAG,"action not move:" +MotionEvent.actionToString(action));
// Webview shift down should only occur while moving
// Once finger is off,cancel
__shiftWebViewTopTo(0);
userTouchY = -1;
}
boolean result = gestureDetector.onTouchEvent(event);
return result;
}
Writing a GestureListener of a sort from scratch obviously solves the problem and I can simply use Toast to message the user for a failed gesture input, but there ought to be an easier, more readable solution.

How to clear child layout in android?

I am developing an android application where I am creating dynamic Images arrow on relative layout. The images are created on a x,y coordinated of the click area of relative layout. Below is the code the I am using for it.
presciptionScreenArrowImg.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (canSelectMedianStatus == 2) {
if (event == simulationEvent)
return false;
int action = event.getAction();
int x = (int) event.getX();
int y = (int) event.getY();
Log.e("onTouchListener", "User touch at X:" + x + " Y:" + y);
pointerArrow = new ImageView(getApplication());
pointerArrow.setImageResource(R.drawable.pointer);
pointerArrow.setId(imageArrayTag);
imageArrayTag++;
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(40, 40);
params.topMargin = y;
params.leftMargin = x;
pointerArrow.setLayoutParams(params);
presciptionScreenArrowImg.addView(pointerArrow);
long length = 0;
if (action == MotionEvent.ACTION_DOWN) {
// click(v, x, y);
}
}
return false;
}
});
Now, I need is there on button click the last Image drawn should remove first. Basically I need an undo functionality to remove Images as LIFO structure.
let consider presciptionScreenArrowImg is your main layout which contains your all views so for removing
int index=presciptionScreenArrowImg.getChildCount();
if(index>0)
presciptionScreenArrowImg.removeViewAt(index-1);
from above get count of child and remove last view if you have any problem let me know
Store the views in a Queue, and when you add a new view check if the queue is full. If it is, pop a view from the queue, and call presciptionScreenArrowImg.remove(poppedView);

Scroller.getCurrY returns max value after fling

I have a Scroller is used for a fling. I initialize it with an initial velocity and some limits. When I later get the new Y value, getCurrY returns the Y limit passed into fling.
Here's heavily simplified code:
public class SimpleList extends ScrollView implements Runnable
{
private VelocityTracker velocityTracker = null; // initialization redacted
private Scroller scroller = null;
#Override
public boolean onTouchEvent(MotionEvent ev)
{
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK)
{
case MotionEvent.ACTION_DOWN:
velocityTracker.addMovement (ev);
return true;
case MotionEvent.ACTION_MOVE:
{
velocityTracker.addMovement (ev);
return true;
}
case MotionEvent.ACTION_UP:
velocityTracker.computeCurrentVelocity (1000);
int index = ev.getActionIndex();
int pointerId = ev.getPointerId(index);
int velY = (int) VelocityTrackerCompat.getYVelocity (velocityTracker, pointerId);
velocityTracker.recycle();
scroller = new Scroller (getContext());
scroller.fling (0, 0, 0, velY,
Integer.MIN_VALUE, Integer.MIN_VALUE,
Integer.MAX_VALUE, Integer.MAX_VALUE);
postDelayed (this, 100);
return true;
}
return false;
}
public void run()
{
if (scroller.isFinished())
return;
if (scroller.computeScrollOffset())
{
System.out.println ("curVel " + scroller.getCurrVelocity());
System.out.println ("newY " + scroller.getCurrY());
}
}
}
The initial velocity seems reasonable - around 1000 px/sec. The logged velocity is a bit less, which also seems reasonable. Yet, the "newY" value is the MAX_VALUE passed into fling.
Any ideas?
Any ideas?
One has to use OverScroller.
The documentation for Scroller wasn't clear to me - I thought that the min and max values were only used to limit the amount of scroll. However they are also used to determine the scroll range, meaning that computeScrollOffset uses the limits to scale the animation, not just to limit it.

detect move event by index pointer in android

I am working on a multitouch program that needs to record only the movements made by the second finger or index pointer.
Now the documentation says that we can use MotionEvent.ACTION_POINTER_INDEX_MASK and & it with action and shift by INDEX_SHIFT to get the pointer that made the action like going up or down. But this technique does not work on move.
Is there anyway that we can detect the move action made by a certain pointer alone?
Thx,
yes, you can have something like this in your View class:
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_MOVE:
if(event.getPointerCount()>1){
//where 1 is the index of the second finger
final int Y = event.getY(1);
final int X = event.getX(1);
}
break;
}
}
so depending on what finger you want to get the movent you can set the get to that index. Rember that values may be from 0 (the first pointer that is down) to getPointerCount()-1.
I tested this on 2.2 Gingerbread so I hope it be useful for you :)
You can get the effective pointer index by checking which pointer changed:
private final int MAX_POINTER = 5; // 5 different touch pointers supported on most devices
private float mLastTouchPositionX[];
private float mLastTouchPositionY[];
#Override
public boolean onTouchEvent(MotionEvent aEvent)
int tActionIndex = aEvent.getActionIndex();
int tPointerCount = aEvent.getPointerCount();
/*
* Check which pointer changed on move
*/
if (tMaskedAction == MotionEvent.ACTION_MOVE) {
for (int i = 0; i < tPointerCount && i < MAX_POINTER; i++) {
if (mLastTouchPositionX[i] != aEvent.getX(i) || mLastTouchPositionY[i] != aEvent.getY(i)) {
mLastTouchPositionX[i] = aEvent.getX(i);
mLastTouchPositionY[i] = aEvent.getY(i);
// Found new action index
tActionIndex = i;
break;
}
}
}
...
}

How to config the response time for LongClick?

In Android, View.onLongClickListener() takes about 1 sec to treat it as a long click. How can I configure the response time for a long click?
The default time out is defined by ViewConfiguration.getLongPressTimeout().
You can implement your own long press:
boolean mHasPerformedLongPress;
Runnable mPendingCheckForLongPress;
#Override
public boolean onTouch(final View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
if (mPendingCheckForLongPress != null) {
v.removeCallbacks(mPendingCheckForLongPress);
}
// v.performClick();
}
break;
case MotionEvent.ACTION_DOWN:
if( mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new Runnable() {
public void run() {
//do your job
}
};
}
mHasPerformedLongPress = false;
v.postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout());
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
// Be lenient about moving outside of buttons
int slop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
if ((x < 0 - slop) || (x >= v.getWidth() + slop) ||
(y < 0 - slop) || (y >= v.getHeight() + slop)) {
if (mPendingCheckForLongPress != null) {
v. removeCallbacks(mPendingCheckForLongPress);
}
}
break;
default:
return false;
}
return false;
}
This may be late to answer but in case of someone needed.
You can change the time out if your phone is rooted.
simply set the value in database to desired time out value. This will effect system wide long press event.
directory: /data/data/com.android.providers.settings/databases
file : settings.db
table : secure
name : long_press_timeout
By coding ? you may need system app privilege. not so sure actually.
but if you back track the ViewConfiguration.getLongPressTimeout() method, you will see that the constant value is coming from Settings.Secure.LONG_PRESS_TIMEOUT which is a mapping for settings.db
time out set operation is written in View class so it effects every view.

Categories

Resources