Detect scale and translate gestures without ScaleGestureDetector - android

I try to implement multi-touch-gesture detection for API level 7. That means I do not have a ScaleGestureDetector. At the moment I have something like this but it does not work well and I have more open questions on it than a full understanding:
public boolean onTouchEvent(MotionEvent ev)
{
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) // why mask it with ACTION_MASK?
{
case MotionEvent.ACTION_DOWN:
{
mLastTouchX=ev.getX();
mLastTouchY=ev.getY();
mActivePointerId = ev.getPointerId(0);
break;
}
case MotionEvent.ACTION_POINTER_DOWN:
{
mLastTouchX2=ev.getX();
mLastTouchY2=ev.getY();
if (ev.getPointerCount()>1)
mActivePointerId2 = ev.getPointerId(1);
break;
}
case MotionEvent.ACTION_MOVE:
{
int pointerIndex;
float x=0.0f,y=0.0f;
try
{
if ((mActivePointerId!=INVALID_POINTER_ID) || (mActivePointerId2!=INVALID_POINTER_ID))
{
// get one of the active pointers - unfortunately here I'm not sure which one is the active one so I only can guess
pointerIndex= ev.findPointerIndex(mActivePointerId);
x= ev.getX(pointerIndex);
y= ev.getY(pointerIndex);
}
if ((mActivePointerId!=INVALID_POINTER_ID) && (mActivePointerId2!=INVALID_POINTER_ID))
{
float d1,d2;
pointerIndex = ev.findPointerIndex(mActivePointerId2);
if (pointerIndex<0) return false;
float x2 = ev.getX(pointerIndex);
float y2 = ev.getY(pointerIndex);
d1=android.util.FloatMath.sqrt((x-x2)*(x-x2)+(y-y2)*(y-y2));
d2=android.util.FloatMath.sqrt((mLastTouchX-mLastTouchX2)*(mLastTouchX-mLastTouchX2)+
(mLastTouchY-mLastTouchY2)*(mLastTouchY-mLastTouchY2));
if ((d1>0) && (d2>0)) // seems to be a scale gesture with two pointers
{
float w,h,s;
transOffsetX=0.0f;
transOffsetY=0.0f;
s=d1/d2;
mScaleFactor*=s;
matrix.postScale(s,s);
w=(scrWidth-(scrWidth*s))/2.0f;
h=(scrHeight-(scrHeight*s))/2.0f;
matrix.postTranslate(w,h);
imgOffsetX+=w;
imgOffsetY+=h;
}
mLastTouchX2 = x2;
mLastTouchY2 = y2;
}
else if (mScaleFactor==1.0) // seems to be a translate gesture with only one pointer
{
mScaleFactor=1.0f;
transOffsetX+=(x-mLastTouchX);
transOffsetY+=(y-mLastTouchY);
matrix.setTranslate(transOffsetX,transOffsetY);
}
if ((mActivePointerId!=INVALID_POINTER_ID) || (mActivePointerId2!=INVALID_POINTER_ID))
{
mLastTouchX = x;
mLastTouchY = y;
}
}
catch (ArrayIndexOutOfBoundsException aioobe)
{
// this is really strange, this exception can be caused by
// pointerIndex= ev.findPointerIndex(mActivePointerId);
// x= ev.getX(pointerIndex);
// above which seems to be a Android bug?
}
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
{
breakMapThread=true;
mActivePointerId = INVALID_POINTER_ID;
mActivePointerId2 = INVALID_POINTER_ID;
// gestrue seems to be finished so trigger update of the view here
...
break;
}
}
return true;
}
The whole thing is working really lousy. Scale gestures cause high, additional translations, a single tap into the view causes a translation too and translations are not very accurate. Beside of that I found some motion event constants ACTION_POINTER_1/2/3_DOWN/UP which never seem to be used. So I'm absolutely unsure if my whole assignment form _DOWN/_UP to the pointers one and two are correct.
Any ideas, hints, tips to get this thing working?

One idea is to find the source of ScaleGestureDetector and include it into your project.
But there are problems: sometimes the system detects click and longclick (especially when a finger goes out of window boundaries).

Related

Motion Event Action Move is not working in android studio emulator

I have a problem with my android emulator, I have a custom view and for controlling the touch event I use onTochEvent in my class. so for different kind of events (ex:‌down, up, move ...), I have righted a case. for down and up the emulator shows no problem but with the move, there is no action. on my phone, everything is just fine! i have tried different kinds of API too but didn't work. this is my onTouch code :
#Override
public boolean onTouchEvent(MotionEvent event) {
boolean result = super.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
xd = event.getX();
yd = event.getY();
break;
case MotionEvent.ACTION_MOVE:
float xm = event.getX();
float ym = event.getY();
if(card.left < xm && card.right > xm){
if(card.top < ym && card.bottom > ym){
card.top += yd - ym;
card.left += xd - xm;
card.right += xd - xm;
card.bottom += yd - ym;
postInvalidate();
}
}
break;
case MotionEvent.ACTION_UP:
break;
case MotionEvent.ACTION_CANCEL:
break;
case MotionEvent.ACTION_OUTSIDE:
break;
default:
}
return result;
}
android studio 3.3 api 24. Thanks.
The view needs to return true on first ACTION_DOWN event, only then it will receive successive touch events.
return super.onTouchEvent(event); only when you don't want to handle any particular kind of touch event.

pinch to zoom don't center zoom

Im trying to do a pinch to zoom on a imageview. The problem is that the when i zoom it does'nt scale where i pinched it scales up to the left corner. Im not sure why i does this and i seems that there is a lot of people having the same problem but i havent found a soloution to it yet.
public override bool OnTouchEvent(MotionEvent ev)
{
_scaleDetector.OnTouchEvent(ev);
MotionEventActions action = ev.Action & MotionEventActions.Mask;
int pointerIndex;
switch (action)
{
case MotionEventActions.Down:
_lastTouchX = ev.GetX();
_lastTouchY = ev.GetY();
_activePointerId = ev.GetPointerId(0);
break;
case MotionEventActions.Move:
pointerIndex = ev.FindPointerIndex(_activePointerId);
float x = ev.GetX(pointerIndex);
float y = ev.GetY(pointerIndex);
if (!_scaleDetector.IsInProgress)
{
// Only move the ScaleGestureDetector isn't already processing a gesture.
float deltaX = x - _lastTouchX;
float deltaY = y - _lastTouchY;
_posX += deltaX;
_posY += deltaY;
Invalidate();
}
_lastTouchX = x;
_lastTouchY = y;
break;
case MotionEventActions.Up:
case MotionEventActions.Cancel:
// We no longer need to keep track of the active pointer.
_activePointerId = InvalidPointerId;
break;
case MotionEventActions.PointerUp:
// check to make sure that the pointer that went up is for the gesture we're tracking.
pointerIndex = (int) (ev.Action & MotionEventActions.PointerIndexMask) >> (int) MotionEventActions.PointerIndexShift;
int pointerId = ev.GetPointerId(pointerIndex);
if (pointerId == _activePointerId)
{
// This was our active pointer going up. Choose a new
// action pointer and adjust accordingly
int newPointerIndex = pointerIndex == 0 ? 1 : 0;
_lastTouchX = ev.GetX(newPointerIndex);
_lastTouchY = ev.GetY(newPointerIndex);
_activePointerId = ev.GetPointerId(newPointerIndex);
}
break;
}
return true;
}
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
canvas.Save();
canvas.Translate(_posX, _posY);
canvas.Scale(_scaleFactor, _scaleFactor, _lastTouchX, _lastTouchY);
_icon.Draw(canvas);
canvas.Restore();
}
I think it might have to do with this code in the beginning of the class where the bounds of the image is set to 0. But if i delete that code the image wont render.
public GestureRecognizer(Context context, ImageView imgview)
: base(context, null, 0)
{
//_icon = context.Resources.GetDrawable(Resource.Drawable.ic_launcher);
_icon = imgview.Drawable;
_icon.SetBounds(0, 0, _icon.IntrinsicWidth, _icon.IntrinsicHeight);
_scaleDetector = new ScaleGestureDetector(context, new MyScaleListener(this));
}
Have a look at MultiTouchController library. It makes it much easier to write multitouch applications for Android and pinch to zoom, rotate and drag is nicely implemented. Here is the link.

Multitouch - PointerIndex out of range

I try to fix an issue which appears in my code when I added multitouch functions in my app.
The problem seems to come from ACTION_POINTER_DOWN :
private float oldDist = 0;
backCard.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent me) {
switch(me.getAction()){
case MotionEvent.ACTION_DOWN:
firstX = (int) me.getX();
case MotionEvent.ACTION_POINTER_DOWN:
if(me.getPointerCount() >= 2){
oldDist = getSpacing(me);
System.out.println(oldDist);
}
break;
case MotionEvent.ACTION_MOVE:
float newDist = getSpacing(me);
if(newDist - oldDist > 200 && oldDist != 0){
System.out.println("Enabled");
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
break;
}
return true;
}
private float getSpacing(MotionEvent me){
float difx = me.getX(0) - me.getX(1);
float dify = me.getY(0) - me.getY(1);
float spacing = (float) Math.sqrt(difx*difx + dify*dify);
return spacing;
}
});
When I use it without the getPointerCount() condition in ACTION_POINTER_DOWN, I have an out of range error. But if I use the condition, the log doesn't show anything I print in the code. (Of course I use 2 fingers ! :) ), so the condition is never true, even if several fingers touch the screen at the same time.
How can I fix that ? Thank you.
My device is a GS3.
Use me.getActionMasked() instead of me.getAction()

Jumping ImageView while dragging. getX() and getY() values are jumping

I've created an onTouchListener for dragging Views. Images drag smoothly if I use getRawX() and getRawY(). The problem with that is the image will jump to the second pointer when you place a second pointer down then lift the first pointer.
This onTouchListener attempts to fix that issue by keeping track of the pointerId. The problem with this onTouchListener is while dragging an ImageView, the ImageView jumps around pretty crazily. The getX() and getY() values jump around.
I feel like I'm doing this correctly. I don't want to have to write a custom view for this because I've already implemented a scaleGestureDetector and written a custom rotateGestureDetector that work. Everything works fine but I need to fix the issue I get when using getRawX() and getRawY().
Does anybody know what I'm doing wrong here?
Here's my onTouchListener:
final View.OnTouchListener onTouchListener = new View.OnTouchListener()
{
#Override
public boolean onTouch(View v, MotionEvent event)
{
relativeLayoutParams = (RelativeLayout.LayoutParams) v.getLayoutParams();
final int action = event.getAction();
switch (action & MotionEvent.ACTION_MASK)
{
case MotionEvent.ACTION_DOWN:
{
final float x = event.getX();
final float y = event.getY();
// Where the user started the drag
lastX = x;
lastY = y;
activePointerId = event.getPointerId(0);
break;
}
case MotionEvent.ACTION_MOVE:
{
// Where the user's finger is during the drag
final int pointerIndex = event.findPointerIndex(activePointerId);
final float x = event.getX(pointerIndex);
final float y = event.getY(pointerIndex);
// Calculate change in x and change in y
final float dx = x - lastX;
final float dy = y - lastY;
// Update the margins to move the view
relativeLayoutParams.leftMargin += dx;
relativeLayoutParams.topMargin += dy;
v.setLayoutParams(relativeLayoutParams);
// Save where the user's finger was for the next ACTION_MOVE
lastX = x;
lastY = y;
v.invalidate();
break;
}
case MotionEvent.ACTION_UP:
{
activePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_CANCEL:
{
activePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_POINTER_UP:
{
// Extract the index of the pointer that left the touch sensor
final int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = event.getPointerId(pointerIndex);
if(pointerId == activePointerId)
{
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
lastX = (int) event.getX(newPointerIndex);
lastY = (int) event.getY(newPointerIndex);
activePointerId = event.getPointerId(newPointerIndex);
}
break;
}
}
return true;
}
};
image1.setOnTouchListener(onTouchListener);
The issue was simple, but unexpected. getRawX/Y() returns absolute coordinates, while getX/Y() returns coordinates relative to the view. I would move the view, reset lastX/Y, and the image wouldn't be in the same spot anymore so when I get new values they'd be off. In this case I only needed where I originally pressed the image (not the case when using `getRawX/Y').
So, the solution was to simply remove the following:
// Save where the user's finger was for the next ACTION_MOVE
lastX = x;
lastY = y;
I hope this will help somebody in the future, because I've seen others with this problem, and they had similar code to me (resetting lastX/Y)
After a lot of research, I found this to be the issue, getRawX is absolute and getX is relative to view. hence use this to transform one to another
//RawX = getX + View.getX
event.getRawX == event.getX(event.findPointerIndex(ptrID1))+view.getX()
I have One tip & think it will help.
invalidate() : does only redrawing a view,doesn't change view size/position.
What you should use is
requestLayout(): does the measuring and layout process. & i think requestLayout() will be called when call setLayoutParams();
So Try by removing v.invalidate()
or try using view.layout(left,top,right,bottom) method instead of setting layoutParams.

how to make left right activity slider?

Any suggestions for how to make an activity slider with "slide left" and "slide right" like a typical image slider??
I tried with: (implements OnTouchListener)
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
{
// code
break;
}
case MotionEvent.ACTION_UP:
{
// code
break;
}
case MotionEvent.ACTION_MOVE:
{
// code
break;
}
}
return true;
}
but don't have LEFT, RIGHT choice.
I don't need use buttons, just I need do somethings like the image slider for ipad2 but with activities for a customer app.
Thanks
You need to calculate by your own for slide left and right movement
MotionEvent.ACTION_UP
A pressed gesture has finished, the motion contains the final release location as well as any intermediate points since the last down or move event.
MotionEvent.ACTION_DOWN
A pressed gesture has started, the motion contains the initial starting location.
Use onTouchEvent(), and calculate difference in X and difference in Y by where the user presses down and lifts up. Use these values to figure out the direction of the move.
float x1, x2, y1, y2;
String direction;
switch(event.getAction()) {
case(MotionEvent.ACTION_DOWN):
x1 = event.getX();
y1 = event.getY();
break;
case(MotionEvent.ACTION_UP) {
x2 = event.getX();
y2 = event.getY();
float differenceInX = x2-x1;
float differenceInY = y2-y1;
// Use dx and dy to determine the direction
if(Math.abs(differenceInX) > Math.abs(differenceInY)) {
if(differenceInX > 0) direction = "right";
else direction = "left";
} else {
if(differenceInY > 0) direction = "down";
else direction = "up";
}
}
}

Categories

Resources