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.
Related
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.
I would like to change a SeekBar's behaviour. Therefore I need to edit two methods in AbsSeekBar, one of them is protected the other is
How can I do it?
Now I can't simply copy said methods from the AbsSeekbar and override them in my custom SeekBar class - I end up missing all field parameters.
I also can't just edit the AbsSeekBar class itself, since it is not part of my project. Any help is appreciated.
These are the two methods:
public abstract class AbsSeekBar extends ProgressBar {
.
.
.
#Override
public boolean onTouchEvent(MotionEvent event) {
if (!mIsUserSeekable || !isEnabled()) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (isInScrollingContainer()) {
mTouchDownX = event.getX();
} else {
setPressed(true);
if (mThumb != null) {
invalidate(mThumb.getBounds()); // This may be within the padding region
}
onStartTrackingTouch();
trackTouchEvent(event);
attemptClaimDrag();
}
break;
case MotionEvent.ACTION_MOVE:
if (mIsDragging) {
trackTouchEvent(event);
} else {
final float x = event.getX();
if (Math.abs(x - mTouchDownX) > mScaledTouchSlop) {
setPressed(true);
if (mThumb != null) {
invalidate(mThumb.getBounds()); // This may be within the padding region
}
onStartTrackingTouch();
trackTouchEvent(event);
attemptClaimDrag();
}
}
break;
case MotionEvent.ACTION_UP:
if (mIsDragging) {
trackTouchEvent(event);
onStopTrackingTouch();
setPressed(false);
} else {
// Touch up when we never crossed the touch slop threshold should
// be interpreted as a tap-seek to that location.
onStartTrackingTouch();
trackTouchEvent(event);
onStopTrackingTouch();
}
// ProgressBar doesn't know to repaint the thumb drawable
// in its inactive state when the touch stops (because the
// value has not apparently changed)
invalidate();
break;
case MotionEvent.ACTION_CANCEL:
if (mIsDragging) {
onStopTrackingTouch();
setPressed(false);
}
invalidate(); // see above explanation
break;
}
return true;
private void trackTouchEvent(MotionEvent event) {
final int width = getWidth();
final int available = width - mPaddingLeft - mPaddingRight;
final int x = (int) event.getX();
float scale;
float progress = 0;
if (isLayoutRtl() && mMirrorForRtl) {
if (x > width - mPaddingRight) {
scale = 0.0f;
} else if (x < mPaddingLeft) {
scale = 1.0f;
} else {
scale = (float)(available - x + mPaddingLeft) / (float)available;
progress = mTouchProgressOffset;
}
} else {
if (x < mPaddingLeft) {
scale = 0.0f;
} else if (x > width - mPaddingRight) {
scale = 1.0f;
} else {
scale = (float)(x - mPaddingLeft) / (float)available;
progress = mTouchProgressOffset;
}
}
final int max = getMax();
progress += scale * max;
setHotspot(x, (int) event.getY());
setProgress((int) progress, true);
}
If you absolutely have to change how a private method works, you'll need to copy the entire class into your project, edit that copy, and use that version of the class instead of the original. Its something that should probably be avoided if possible.
I'm not sure I understand 'I end up missing all field parameters'
The only way you can edit a method's functionality in Java is by extending the class.
Example:
public class CustomSeekbar extends AbsSeekBar {
public CustomSeekbar(Context context) {
super(context);
}
#Override
protected boolean verifyDrawable(Drawable who) {
//Do your own method functionality
//Return your own true/false
return super.verifyDrawable(who);
}
}
In this case you don't have to return super.verifyDrawable if you don't want to, and you can supply your own response.
This is true for all protected or public methods in the AbsSeekBar class.
By reflection, you can modify fields of the class if you so please.
For example:
Field drawableField = absSeekBarInstance.getClass().getField("mThumb");
drawableField.setAccessible(true);
drawableField.set(absSeekBarInstance, someDrawable);
Or to GET the field values, you just say
drawableField.get(absSeekBarInstance);
To go even further, you'll need to provide me more information.
Private methods cannot be changed. It's just not possible.
Aspect-oriented programming in android
AspectJ comes close, but you can't do what you're describing. Thats just how Java works. In Objective-c you can do this, it is called 'swizzling'.
But yes, in Java, you cannot change the functionality of compiled methods.
Your best bet would be to copy AbsSeekBar and all of its required classes into your own project.
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.
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;
}
}
}
...
}
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.