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
Related
I looked on many posts but couldnt find the answer.
I would like to rotate an root image with another small image that is located on the lower side of the root image.
while rotation its working but the first time I press it, it jumps to 45 degrees because of the math.tan(), I think I have a calculation problem.
rotateImage.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent event) {
final int action = MotionEventCompat.getActionMasked(event);
switch (action) {
case MotionEvent.ACTION_UP:
break;
case MotionEvent.ACTION_DOWN:
rotateX = event.getRawX() - rotateImage.getWidth() / 2;
rotateY = event.getRawY() - rotateImage.getHeight() / 2;
break;
case MotionEvent.ACTION_MOVE:
float angle = (float) Math.toDegrees(Math.atan2(event.getRawY() - rotateY, event.getRawX() - rotateX));
if (angle < 0){
angle += 360;
}
mBinding.getRoot().setRotation(angle);
}
return true;
}
});
please advice.
I think this library can help you.
https://github.com/kencheung4/android-StickerView
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 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!
MY imageview does not move when ontouch event is called...Though I can see ACTION_UP and ACTION_MOVE happening from the log traces.
Here is my code:-
public boolean onTouch(View v, MotionEvent motion) {
float dy ;
float dx;
double r = 0;
float angle = 0;
switch(v.getId())
{
case R.id.arrow:
switch(motion.getActionMasked())
{
case MotionEvent.ACTION_MOVE:
Log.w(this.getClass().getName(),"In MOTION MOVE");
mX1 = motion.getX();
mY1 = motion.getY();
break;
case MotionEvent.ACTION_UP:
Log.w(this.getClass().getName(),"In MOTION UP");
mX2 = motion.getX();
mY2 = motion.getY();
dy = -( mY2-mY1);
dx = -(mX2-mX1);
r = Math.atan2(dy, dx);
angle = (int)Math.toDegrees(r);
updateRotation(angle);
break;
default:
break;
}
Log.d(this.getClass().getName(), "Value of angle in ontouch: " + Double.toString(angle));
break;
default:
break;
}
return true;
}
Basically I am trying to calculate the angular displacement when user touches the imageview and changes its position.
======================================
Edit:- Below is my update rotation function:-
public void updateRotation(double angle)
{
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.arrow);
int w = bitmap.getWidth();
int h = bitmap.getHeight();
String filePath = null;
Matrix mtx = new Matrix();
Log.d(this.getClass().getName(), "Value: " + Double.toString(angle));
if(angle > 90)
angle = 90;
mtx.postRotate((float) angle);
bitmap = Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true);
Drawable draw =new BitmapDrawable(bitmap);
mImageView.setBackgroundDrawable(draw);
}
=========================================================
Edit 2: Modified function
public boolean onTouch(View v, MotionEvent motion) {
float dy ;
float dx;
double r = 0;
switch(v.getId())
{
case R.id.arrow:
switch(motion.getActionMasked())
{
case MotionEvent.ACTION_DOWN:
mX1 = motion.getX();
mY1 = motion.getY();
break;
case MotionEvent.ACTION_MOVE:
Log.w(this.getClass().getName(),"In MOTION MOVE");
mX1 = motion.getX();
mY1 = motion.getY();
break;
case MotionEvent.ACTION_UP:
Log.w(this.getClass().getName(),"In MOTION UP");
mX2 = motion.getX();
mY2 = motion.getY();
dy = -( mY2-mY1);
dx = -(mX2-mX1);
r = Math.atan2(dy, dx);
mAngle = (int)Math.toDegrees(r);
updateRotation();
break;
default:
break;
}
Log.d(this.getClass().getName(), "Value of angle in ontouch: " + Float.toString(mAngle));
break;
default:
break;
}
return true;
}
=============================================
Edit 3:- Strange thing is that if i modify my onTouch event and perform all the calculations in ACTION_MOVE the imageview responds for each touch event, but this is not what I want as I cannot do the proper angluar rotation in ACTION_MOVE event
Ruchira,
From looking at your code and working with many onTouch innovations myself, I can tell you that the problem is definitely in your onTouchEvent(). You record the mX1 and mY1 in your ACTION_MOVE without recording the original ACTION_DOWN event. This means that every time you move your finger, the onTouchEvent() method thinks that this is the original position. Try this instead:
public boolean onTouch(View v, MotionEvent motion) {
float dy ;
float dx;
double r = 0;
float angle = 0;
switch(v.getId())
{
case R.id.arrow:
switch(motion.getActionMasked())
{
case MotionEvent.ACTION_DOWN:
mX1 = motion.getX();
mY1 = motion.getY();
break;
case MotionEvent.ACTION_MOVE:
Log.w(this.getClass().getName(),"In MOTION MOVE");
//Just to support real time rotation, you could:
mX2 = motion.getX();
mY2 = motion.getY();
//... some rotation code.
break;
case MotionEvent.ACTION_UP:
Log.w(this.getClass().getName(),"In MOTION UP");
// All of this should be fine.
mX2 = motion.getX();
mY2 = motion.getY();
dy = -( mY2-mY1);
dx = -(mX2-mX1);
r = Math.atan2(dy, dx);
angle = (int)Math.toDegrees(r);
updateRotation(angle);
break;
default:
break;
}
Log.d(this.getClass().getName(), "Value of angle in ontouch: " + Double.toString(angle));
break;
default:
break;
}
return true;
}
That should at least track your numbers correctly, so that they may actually be used.
Additional Considerations
The most common mistake is to make sure that you have not set any of your tracking variables to final. If this is the case, the code will set it once, but not again...
When dealing with Drawables, it is important to get rid of the original Drawable and its callback. The reason for this is a well known issue that was never resolved in Android because a decision couldn't be made as to when exactly it was necessary and when it wasn't. Essentially, Android will eventually run out of memory, but will not necessarily tell you and just fail to update the image. This is how you do this (when replacing an image).
Drawable trash = mImageView.getBackgroundDrawable();
if (trash != null)
{ trash.setCallback(null);
trash.recycle();
trash = null;
}
mImageView.setBackgroundDrawable(draw);
Finally, you have to let the image know that it has to redraw itself. This isn't always the case, but is true more often than not. mImageView.invalidate(); will often resolve the issue, and should be placed on the last line in your updateRotation() method.
FuzzicalLogic