I am using MotionEvent and Matrix to drag my bitmap but instead the bitmap gets "translated" to a nearby region (most of the times in the opposite direction of press). I want to be able to drag it using Matrix translations. Here's my code.
switch(action){
case MotionEvent.ACTION_DOWN: {
final float touchX = event.getX();
final float touchY = event.getY();
//check to see if the user is pressing at most 80 pixels far from the bitmap
if(((Math.abs(touchX - player.prevX)<=80) && ((Math.abs(touchY - player.prevY)<=80)))){
//if so then proceed to drag it to a new location
player.isDraggable = true;
}
break;
}
case MotionEvent.ACTION_MOVE: {
if(player.isDraggable){
player.x = event.getX();
player.y = event.getY();
float dx = player.x - player.prevX, dy = player.y - player.prevY;
player.matrix.postTranslate(dx, dy);
player.prevX = player.x;
player.prevY = player.y;
player.isDraggable = false;
}
break;
}
}
public void draw(Canvas canvas) {
canvas.drawBitmap(background, 0, 0, null);
canvas.drawBitmap(player.bmp, player.matrix, null);
for(int i=0;i<particles.size();i++){
particles.get(i).drawShower(canvas);
}
}
Your onTouch() method should return true to be able to get
continuously values from the MotionEvent.
You set the isDraggable attribute of your player immediately to
false, when an attempt to drag the image appears. After that, you check each time, if it is true, but it wont be. I don't know, what is the logic behind that, but try something else!
Related
I have programmed a simple test app, where Drawable objects (the yellow translucent tiles at the screenshot below) can be dragged around on a scrollable and scallable (by using Matrix) Canvas:
Here is the code handling dragging:
private Drawable mDragged = null;
private float mPrevX;
private float mPrevY;
public boolean onTouchEvent(MotionEvent e) {
// Convert touch coordinates to Canvas coordinates
float[] point = new float[] {e.getX(), e.getY()};
Matrix inverse = new Matrix();
mMatrix.invert(inverse);
inverse.mapPoints(point);
float x = point[0];
float y = point[1];
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
Drawable tile = hitTest(x, y);
if (tile != null) {
mDragged = tile;
mPrevX = x;
mPrevY = y;
return true;
}
break;
case MotionEvent.ACTION_MOVE:
if (mDragged != null) {
float dX = x - mPrevX;
float dY = y - mPrevY;
mPrevX = x;
mPrevY = y;
Rect rect = mDragged.copyBounds();
rect.left += dX;
rect.top += dY;
rect.right = rect.left + mDragged.getIntrinsicWidth();
rect.bottom = rect.top + mDragged.getIntrinsicHeight();
mDragged.setBounds(rect);
invalidate();
return true;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (mDragged != null) {
mDragged = null;
return true;
}
break;
}
// Handle Canvas scrolling and scaling here
}
It works, but I have a problem - when I touch a tile and then drag it around - the tile departs from my finger (or from my mouse pointer, when using Android emulator).
I.e. the distance between the dragged Drawable and the finger (or mouse pointer - as can be seen on the above screenshot) increases, while it is being dragged around.
This can't be a multi-touch related issue - because there is only single touch on the emulator.
What is the root cause here?
Just a hunch here, but it's probably due to two things being compounded together:
Your coordinates are float values, but the Rect consists of int values, which can cause some rounding issues
On each ACTION_MOVE event, you are adding the (potentially) rounded values, and changing the baseline (mPrevX & mPrevY) instead of calculating the absolute distance from where you first started dragging
I would store the current position of the tile when dragging starts (including offset from the cursor position), and then base all the movement calculations off of that point.
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.
I'm trying to make a basic "Paint"-esque app for a class, and part of what I'm trying to implement is the ability to draw a circle that scales in size as the user moves their finger to pick the center, and drags it outward to the desired radius. The problem is, no matter what I try it just ends up repeatedly stamping a small circle where ever the user moves their finger. The idea is the app will get the coordinates for the center (tX, tY) on the press down, draw a circle with a radius r every time the user moves, then finalize it when they lift their finger.
Edit: the commented out sections are parts where I was testing different things.
Code for my onMotionEvent custom class:
#Override
public boolean onTouchEvent(MotionEvent event) {
//detect user touch
float tX = event.getX();
float tY = event.getY();
float rX = 0;
float rY = 0;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
drawPath.moveTo(tX, tY);
break;
case MotionEvent.ACTION_MOVE:
if(shapes){
rX = (int) event.getX();
rY = (int) event.getY();
r = (float) Math.sqrt(Math.pow(tX - rX, 2) + Math.pow(tY - rY, 2));
drawCanvas.drawCircle(tX, tY, r, drawPaint);
invalidate();
}else{
drawPath.lineTo(tX, tY);
}
break;
case MotionEvent.ACTION_UP:
/*rX = event.getX();
rY = event.getY();
r = (float) Math.sqrt(Math.pow(tX - rX, 2) + Math.pow(tY - rY, 2));*/
if(shapes){
if(shape_sel == 0){
//drawCanvas.drawCircle(tX, tY, r, drawPaint);
drawPath.reset();
/*}else if(shape_sel == 1){
}
else{
*/}
}else{
drawCanvas.drawPath(drawPath, drawPaint);
drawPath.reset();
}
break;
default:
return false;
}
invalidate();
return true;
}
You aren't saving tx and ty on touch down, you're resetting them on every event. You need to save tx and ty in class level variables inside the down case.
We have a longpress detection on a button. When detected, the view should be dismissed (back to the game view) and the object chosen with the button shall be at the coordinates of the longpress. That's the easy part, the hard part is to then change the coordinates with the finger, without releasing it.
Okay so i'm trying to reformulate this: We have two views displayed on top of eachother. What we want is a longclick detected in the top view, to then dismiss the top view, and without releasing the finger, get touchevents in the bottom view.
This is our class gameView: (this code contains a lot of code unneccesay for the problem)
#Override
public boolean onTouchEvent(MotionEvent event) {
gestureDetector.onTouchEvent(event);
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mode = DRAG;
//We assign the current X and Y coordinate of the finger to startX and startY minus the previously translated
//amount for each coordinates This works even when we are translating the first time because the initial
//values for these two variables is zero.
startX = event.getX() - previousTranslateX;
startY = event.getY() - previousTranslateY;
break;
case MotionEvent.ACTION_MOVE:
translateX = event.getX() - startX;
translateY = event.getY() - startY;
gestureHandler.setTranslation(translateX, translateY);
//We cannot use startX and startY directly because we have adjusted their values using the previous translation values.
//This is why we need to add those values to startX and startY so that we can get the actual coordinates of the finger.
double distance = Math.sqrt(Math.pow(event.getX() - (startX + previousTranslateX), 2) +
Math.pow(event.getY() - (startY + previousTranslateY), 2)
);
if(distance > 0) {
dragged = true;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
mode = ZOOM;
scaleToX = event.getX();
scaleToY = event.getY();
break;
case MotionEvent.ACTION_UP:
mode = NONE;
dragged = false;
//All fingers went up, so let's save the value of translateX and translateY into previousTranslateX and
//previousTranslate
previousTranslateX = translateX;
previousTranslateY = translateY;
int x = (int)((-translateX/scaleFactorX)+(event.getX()/this.scaleFactorX));
int y = (int)((-translateY/scaleFactorY)+(event.getY()/this.scaleFactorY));
map.click(getContext(), x, y);
for(CastleTile castleTile : map.getCastleTiles()) {
if(castleTile.getRect().contains((int)((-translateX/scaleFactorX)+(event.getX()/this.scaleFactorX)), (int)((-translateY/scaleFactorY)+(event.getY()/this.scaleFactorY)))){
Log.d("Castle to your service", "Boes");
castleSettings.show();
}
}
break;
case MotionEvent.ACTION_POINTER_UP:
mode = DRAG;
//This is not strictly necessary; we save the value of translateX and translateY into previousTranslateX
//and previousTranslateY when the second finger goes up
previousTranslateX = translateX;
previousTranslateY = translateY;
break;
}
detector.onTouchEvent(event);
//We redraw the canvas only in the following cases:
//
// o The mode is ZOOM
// OR
// o The mode is DRAG and the scale factor is not equal to 1 (meaning we have zoomed) and dragged is
// set to true (meaning the finger has actually moved)
if ((mode == DRAG && scaleFactorX != 1f && scaleFactorY != 1f && dragged) || mode == ZOOM) {
invalidate();
}
return true;
}
#Override
public boolean onLongClick(View v) {
castleSettings.dismiss();
return true;
}
And this is the gestureHandler:
public void onLongPress(MotionEvent event) {
map.longClick(context, (int)((-translateX/scaleFactorX)+(event.getX()/this.scaleFactorX)), (int)((-translateY/scaleFactorY)+(event.getY()/this.scaleFactorY)));
}
Sorry for eventual lack of information or bad explaination :)
I am trying to rotate one of the transparent PNGs on this image. The number part is what I want to rotate. I am able to do this, but it is not what I am trying to achieve
I want to rotate the numbers like on a real combination lock. So the user will touch and move their finger in a circle. I looked at less precise image rotation on touch/move events, and they were not sufficient.
this is currently my code
public boolean onTouch(View v, MotionEvent event) {
double r=Math.atan2(event.getX()-lockNumbers.getWidth(), lockNumbers.getHeight()-event.getY());
int rotation=(int)Math.toDegrees(r);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
x=event.getX();
y=event.getY();
updateRotation(rotation);
break;
case MotionEvent.ACTION_UP:
break;
}//switch
return true;
}//onTouch
private void updateRotation(double rot){
float newRot=new Float(rot);
Bitmap bitmap=BitmapFactory.decodeResource(getResources(),R.drawable.numbers);
Matrix matrix=new Matrix();
matrix.postRotate(newRot,bitmap.getWidth(),bitmap.getHeight());
if(y>250){
Bitmap reDrawnBitmap=Bitmap.createBitmap(bitmap,0,0,bitmap.getWidth(),bitmap.getHeight(),matrix,true);
lockNumbers.setImageBitmap(reDrawnBitmap);
}
else{
Bitmap reDrawnBitmap=Bitmap.createBitmap(bitmap,0,0,bitmap.getWidth(),bitmap.getHeight(),matrix,true);
lockNumbers.setImageBitmap(reDrawnBitmap);
}
}
it also resizes the bitmap when you touch it because of the matrix parameter. This is not the desired effect.
The user will pretty much be required to go in a circle with their finger.
Write below code into your touch event.
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// reset the touched quadrants
for (int i = 0; i < quadrantTouched.length; i++) {
quadrantTouched[i] = false;
}
allowRotating = false;
startAngle = getAngle(event.getX(), event.getY());
break;
case MotionEvent.ACTION_MOVE:
double currentAngle = getAngle(event.getX(), event.getY());
rotateDialer((float) (startAngle - currentAngle));
startAngle = currentAngle;
break;
case MotionEvent.ACTION_UP:
allowRotating = true;
break;
}
// set the touched quadrant to true
quadrantTouched[getQuadrant(event.getX() - (dialerWidth / 2), dialerHeight - event.getY() - (dialerHeight / 2))] = true;
detector.onTouchEvent(event);
return true;
And use below link for more reference.
Rotate Dialer Example
By accepting #Dipak Keshariya's answer i am putting way to to get the angle rotation which can help you identify selected part of wheel.
private float copy[] = new float[9];
matrix.getValues(copy);
float wheelAngle = Math.round(Math.atan2(copy[Matrix.MSKEW_X],
copy[Matrix.MSCALE_X]) * (180 / Math.PI));
I presume you want to rotate an image at the point where a user touches the screen? If so, extend the SimpleOnGestureListener like this example:
public class MyGestureDetector extends SimpleOnGestureListener
{
#Override
public void onLongPress(MotionEvent event)
{
int X = (int)event.getX();
int Y = (int)event.getY();
...Rotate the image
}
}
Once you've got the screen coordinates of the touch event, you could apply a Rotation Animation about the point - see here for more details: http://developer.android.com/guide/topics/graphics/2d-graphics.html