I found out in one of my earlier questions that setting up specific x,y coordinates as Touch Listeners was too specific and that just one position would be too small to pick up a touch input.
#ligi mentioned Regions in a comment under my question. I looked into it and realised that it's what I need, I just can't find out how to implement one.
Here is the code for a TouchListener for a specific x,y coordinate:
public boolean onTouchEvent(MotionEvent ev) {
if (!mDragging) {
return false;
}
int x = (int) ev.getX();
int y = (int) ev.getY();
if (x == 100 && y == 200) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "ACTION DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "ACTION MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "ACTION UP");
break;
}
}
Does anyone know how to set up a Region instead? Say if a user finger moves into the region area, do something. I can't find any basic implementations anywhere else.
Here's an image if the desc. wasn't clear:
You could do something like this
public boolean onTouchEvent(MotionEvent ev) {
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "ACTION DOWN OR MOVE");
// check if the event is in region 1
if(x >= "left_x_region1" && x <= "right_x_region1"
&& y >= "top_x_region1" && y <= "bottom_x_region1")
// do something for region1
// check if the event is in region 2
else if(x >= "left_x_region2" && x <= "right_x_region2"
&& y >= "top_x_region2" && y <= "bottom_x_region2")
// do something for region1
// continue for other cases
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "ACTION UP");
break;
}
}
Or you could simplify it even further by making a list of what those actual values are, or even better create Rects from those values
List<Rect> regions = new ArrayList<Rect>();
regions.add(new Rect("left_x_region1", "top_y_region1", "right_x_region1", "bottom_y_region1");
regions.add(new Rect("left_x_region2", "top_y_region2", "right_x_region2", "bottom_y_region2");
// continue for each region
and then check your event in those regions inside your ACTION_DOWN || ACTION_MOVE section of the switch statement
for(int i = 0; i < regions.size(); i++){
if(regions.get(i).contains(x, y)){
// do stuff for region i
return;
}
}
What this does is checks to see if the motion event occurred within the bounds of the region.
I would imagine you'd use a rect in Android. You'd specify the rectangle(s) and see if the rect(s) contains your value. In the example below I made a list of rectangles so there are multiple hotspots. You could ideally just use one if you want just one specific rectangular region. The .contains internal method allows you to specify regions which your x,y coordinates would potentially entail.
List<Rect> retangles;
...
for(Rect rect : rectangles){
if(rect.contains(x,y)){
System.out.println("Touched Rectangle, do what you need to do.");
}
}
or for just one
Rect rect = new Rect(0, 0, 200, 100);
if(rect.contains(x,y))
System.out.println("Touched Rectangle, do what you need to do.");
Related
After doing some tutorials on touch events and drawing on a canvas I tried to combine what I learned and make an application that creates a rectangle using 2 touch points. I am able to drag and create a rectangle using a single touch point but as soon as I use a second finger the rectangle starts behaving weirdly, either not drawing the rectangle at all or disappearing after dragging it a bit. Furthermore, if for example I draw a rectangle by having one touch point being on the lower left corner and the other one on the upper right corner of the screen the rectangle disappears if I drag my fingers to the point where they cross each other.
gif of the application running
private float xDown = 0,yDown = 0, xUp = 0, yUp = 0;
boolean touched = false;
#Override
protected void onDraw (Canvas canvas) {
canvas.drawColor(Color.TRANSPARENT);
if(touched) {
canvas.drawRect(xDown, yDown, xUp, yUp, mPaint);
}
}
#Override
public boolean onTouchEvent (MotionEvent event) {
int fingers = event.getPointerCount();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
if (fingers == 1) {
xDown = event.getX(0);
yDown = event.getY(0);
xUp = 0;
yUp = 0;
}
if (fingers == 2) {
xUp = event.getX(1);
yUp = event.getY(1);
xDown = event.getX(0);
yDown = event.getY(0);
touched = true;
}
break;
case MotionEvent.ACTION_MOVE:
if (fingers == 1) {
xUp = event.getX();
yUp = event.getY();
touched = true;
}
if (fingers == 2) {
xUp = event.getX(1);
yUp = event.getY(1);
xDown = event.getX(0);
yDown = event.getY(0);
touched = true;
}
break;
case MotionEvent.ACTION_UP:
if (fingers == 1) {
xUp = event.getX();
yUp = event.getY();
touched = true;
}
if (fingers == 2) {
xUp = event.getX(1);
yUp = event.getY(1);
xDown = event.getX(0);
yDown = event.getY(0);
touched = true;
}
break;
}
invalidate();
return true;
MotionEvent.ACTION_DOWN will only happen with the FIRST touch event to the screen.
"ACTION_DOWN
Constant for getActionMasked(): A pressed gesture has started..." (From https://developer.android.com/reference/android/view/MotionEvent#ACTION_DOWN)
For your SECOND or more fingers, you need to check for ACTION_POINTER_DOWN
"ACTION_POINTER_DOWN
Constant for getActionMasked(): A non-primary pointer has gone down..." (From https://developer.android.com/reference/android/view/MotionEvent#ACTION_POINTER_DOWN)
Because you are using only two fingers, you won't need to worry about the ActionIndex ( getActionIndex() ). The first finger touching the screen is always an ACTION_DOWN and every touch down after is an ACTION_POINTER_DOWN.
On the ACTION_UP it is the reverse. Every touch up (except the last touch) is ACTION_POINTER_UP and the last touch up is an ACTION_UP.
NOTE: The first touch (ACTION_DOWN) always has and index of 0.
The index stays assigned to the touch until the touch is removed from the screen. Because you are hard-coding the indexes, any accidental touches to the screen will cause some unexpected results. For a test program, this is fine but you will need to handle the indexes eventually.
Example:
Finger 1 (ACTION_DOWN) touches the screen and gets an index of 0.
Finger 2 (ACTION_POINTER_DOWN) touches the screen and gets an index of 1.
Finger 3 (ACTION_POINTER_DOWN) touches the screen and gets an index of 2
Finger 2 (index 1) is lifted off the screen (ACTION_POINTER_UP)
Finger 1 (index 0) and finger 3 (index 2) are still in the MotionEvent and their indexes remain assigned to them.
The simplest change for you to make is Change:
case MotionEvent.ACTION_DOWN:
if (fingers == 1) {
...
to:
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
if (fingers == 1) {
...
and also Change:
case MotionEvent.ACTION_UP:
if (fingers == 1) {
...
to:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
if (fingers == 1) {
...
because you are already handling the finger count & indexes.
This change will make you code "more correct" because it will capture the second DOWN and the first UP but it will not fix your issue. ACTION_MOVE is already catching both touch points when they move and you are already handling the two indexes and assigning the X's & Y's. So the issue has to be elsewhere in you program.
I'm using motion event, action_down, action_move etc, to detect when there is no finger movement but the finger is still on the screen. For instance, the user moves in a vertical line from top of the screen to the bottom and then stops movement without lifting the finger. How do I detect when there is no movement but the finger is still on the screen after the drag/swipe?
EDIT: What I'm trying to do is count every time I change direction of movement in a vertical direction. And to do that I'm trying to detect when I stop movement against the change of movement. For instance, I move down the screen and then move back up, that counts as two counts. Here is my code, please don't provide me with code as a direct answer but hints or clues so that I can try and figure it out myself (my code might look a bit confusing, I'm just trying out different things):
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
oldX = event.getX();
oldY = event.getY();
oldSpeedY = System.currentTimeMillis();
break;
case MotionEvent.ACTION_MOVE:
posY = event.getY();
posSpeedY = System.currentTimeMillis();
float timeElapsed = (posSpeedY - oldSpeedY) / 1000;
float diffSpeed = posSpeedY - oldSpeedY;
if (changeOfMovement(posY, oldY)) {
//if (diffSpeed == 0)
count += 1;
}
break;
case MotionEvent.ACTION_UP:
// count seconds here
break;
}
Toast.makeText(getApplicationContext(), "Swipe: " + count,
Toast.LENGTH_SHORT).show();
return false;
}
public boolean changeOfMovement(float posY, float oldY) {
int newY = Math.round(posY);
double distance = Math.abs(newY - oldY);
oldY = newY;
//float speed = (float) (distance / time);
//if (distance < 25)
//return false;
//if (speed == 0)
//return true;
if (distance < 25)
return true;
return false;
}
The finger touches the screen until you receive either of the MotionEvent actions ACTION_UP or ACTION_CANCEL
The ACTION_DOWN Event is still valid until the finger is lifted from the screen or you can wait till a ACTION_UP Event is detected
I'm not sure if my situation is like yours, but mine I was want to detect if the user stopped moving inside circle within one second and starts moving again, by comparing between last two currentTimeMillis.
So, what I've done is I initialized fixed ArrayList to save the last two times in move event:
public class FixedArrayList extends ArrayList {
int fixedSize = 10;
public FixedArrayList(){}
public FixedArrayList(int fixedSize){
this.fixedSize = fixedSize;
}
#SuppressWarnings("All")
public void addItem(Object object){
if(size() < fixedSize){
add(object);
} else{
remove(0);
add(object);
}
}
}
Now, I've initialized my new class with fixed 2 items to be saved:
FixedArrayList savedLastMove = new FixedArrayList(2);
int secondsToWait = 1;
public boolean onTouchEvent(MotionEvent event) {
int action = MotionEventCompat.getActionMasked(event);
switch (action) {
case (MotionEvent.ACTION_MOVE):
currentSystemTime = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis());
savedLastMove.addItem(currentSystemTime);
if(savedLastMove.size() >= 2 && ((long)savedLastMove.get(1) - (long)savedLastMove.get(0)) >= secondsToWait){
//Do what you want after secondsToWait
}
return true;
}
I hope that will help! Because it solved my problem.
I want to have four different areas on the screen and be able to determine if each of those areas are touched. Every area should have a corresponding boolean value that is true if touched. When one area is true that part of the canvas will become a different color.
It is very important that each area works independently, so if area 1 and two are true and the user lets go of area 1 it will instantly become false without affecting area 2.
Thanks!
Edit:
I've tried so many things but I've just started over again. This is from a class that has a SurfaceView with a canvas. I can't figure out what goes where.
public boolean onTouch(View v, MotionEvent ev) {
// TODO Auto-generated method stub
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
x = ev.getX();
y = ev.getY();
if (canvasHeight != 0 && canvasWidth != 0) {
if (x < canvasWidth/2 && y < canvasWidth/2){
x1 = x;
y1 = y;
}
if (x < canvasWidth && y > canvasHeight){
x2 = x;
y2 = y;
}
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
Here's how I'd approach your problem:
Create rects that define the four regions of the screen that can be
pressed.
Using multi-touch, check if the coordinated are in any of the defined
rects. If this is the case, then set the boolean value of that region
of the screen to true, so a color will be rendered to that region. If
the coordinate is not in the rect, then set the boolean to false.
I hope this gets you started!
Update:
I suggest you start off simple and don't use multitouch. In you touch method you can get the x and y value of the touch. Once the screen is touched you could call a method like this with x and y being your parameters eg. checkRegion(x,y).
The method could return an int of the region (since in this case you can only touch one at a time):
public int checkRegion(int x, int y) {
int clickedRegion;
// Some code that will return the region number: 1 = top left, 2 = top right, 3 = bottom left, 4 = bottom right
return clickedRegion;
}
First off, you need to check all four areas;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
x = ev.getX();
y = ev.getY();
if (canvasHeight != 0 && canvasWidth != 0) {
//you need four sections, not the two?
if (x < canvasWidth/2 && y < canvasWidth/2){
//set your respective canvas color to what you want it for this quadrant
}
if (x < canvasWidth/2 && y > canvasWidth/2){
//set your respective canvas color to what you want it for this quadrant
}
if (x < canvasWidth && y > canvasHeight){
//set your respective canvas color to what you want it for this quadrant
}
if (x < canvasWidth && y < canvasHeight){
//set your respective canvas color to what you want it for this quadrant
}
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
Now, assuming you can get that to work, all you need to do is do it again in the action_pointer_down and it will register as a "secondary" click.
If the action is Action_up, just figure out where the user liftes his finger and do the same thing. One issue you should think about is this; what if a user clicks in Quadrant 1, but lifts it up in quadrant 3? That is a slightly more complicated condition, but I think you can skip it for now.
How can i determine the direction of gesture ? The use case is shown in the image link. what is the right logic for detecting in which direction the person is trying to move the ball in the circular path ? I have called the direction method in move gesture...Can someone help me fine tune this ... ?
http://www.shrenikvikam.com/wp-content/uploads/2011/04/214e422a43E11S3.png-150x134.png
private String getDirection(float firstTouchX, float finalTouchX){
if((firstTouchX - finalTouchX)>0)
return "Left";
else
return "Right";
}
#Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
// MotionEvent class constant signifying a finger-down event
case MotionEvent.ACTION_DOWN: {
Log.d("ACTION DOWN","Value ->");
final float x = event.getX();
final float y = event.getY();
initialTouchX = x;
initialTouchY = y;
break;
}
// MotionEvent class constant signifying a finger-drag event
case MotionEvent.ACTION_MOVE: {
final float x = event.getX();
final float y = event.getY();
final String direction = getDirection(initialTouchX,x);
Log.d("ACTION MOVE","diff in initial and cur value of x ->" + direction + (initialTouchX - x) + initialTouchX + "y->" + initialTouchY);
break;
}
// MotionEvent class constant signifying a finger-up event
case MotionEvent.ACTION_UP: {
Log.d("ACTION UP","Value ->");
break;
}
}
return true;
}
It seems like the slope of the line formed by the two touch points should be equal to the tangent of the circle at that point. This link has most of the math to pursue such a solution
I have used the difference between angles to the center point of the circle with good success. That may be the way to go as well.
If you are trying to determine which way to move the ball around the ciricle, it doesn't make sense for getDirection to return "Left" and "Right", it should be working with "Clockwise" and "Counterclockwise". Consider, for example, when the ball is at the 20 marker in your image: At this point every point on the circle is "Right" of where you are now...
In order to determine if the ball is moving clockwise or counterclockwise you need to consider both the x and y co-ordinates of the touch points, the x co-ordinate alone is not sufficient. You also need to know where the centre of the circle is. I would suggest in order to determine the direction of movement, you calculate the angle between the touch points and the centre of the circle.
prevTouchX = event.getHistoricalX(event.getHistorySize()-1);
currentTouchX = event.getX();
if(currentTouchX<prevTouchX){
Log.d("LEFT",event.getX()+" and "+event.getY());
}
if(currentTouchX>prevTouchX){
Log.d("RIGHT",event.getX()+" and "+event.getY());
}
Similarly for UP/DOWN
I use this code for rotating a view. It works very well. Have a look at this;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
firstX = event.getX();
firstY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
int dx =(int) (event.getX() - firstX);
int dy =(int) (event.getY() - firstY);
Log.d("Distance Rotate Touch",Integer.toString((int) (firstX-dx)));
if (signView.getRotation()<180){
if (firstX - dx > 15 && firstY - dy > 15 ){
View.setRotation(signView.getRotation()-5);
}else if(firstX - dx < -15 && firstY - dy < 15 ){
View.setRotation(signView.getRotation()+5);
}
}else {
if (firstX - dx > 10 && firstY - dy < -10 ){
View.setRotation(signView.getRotation()+5);
}else if(firstX-dx < -10 && firstY - dy < -15 ){
View.setRotation(signView.getRotation()-5);
}
}
break;
default: return true;
}
Greets,
Does anyone how I go about detecting when a user presses on a bitmap which is inside a canvas?
Thanks
You should work with something like so:
public boolean onTouchEvent(MotionEvent event){
int action = event.getAction();
int x = event.getX() // or getRawX();
int y = event.getY();
switch(action){
case MotionEvent.ACTION_DOWN:
if (x >= xOfYourBitmap && x < (xOfYourBitmap + yourBitmap.getWidth())
&& y >= yOfYourBitmap && y < (yOfYourBitmap + yourBitmap.getHeight())) {
//tada, if this is true, you've started your click inside your bitmap
}
break;
}
}
That's a start, but you need to handle case MotionEvent.ACTION_MOVE and case MotionEvent.ACTION_UP to make sure you properly deal with the user input. The onTouchEvent method gets called every time the user: puts a finger down, moves a finger already on the screen or lifts a finger. Each time the event carries the X and Y coordinates of where the finger is. For example if you want to check for someone tapping inside your bitmap, you should do something like set a boolean that the touch started inside the bitmap on ACTION_DOWN, and then check on ACTION_UP that you're still inside the bitmap.
Steve,
Google has a great article and tutorial for handling UI Events # http://developer.android.com/guide/topics/ui/ui-events.html. This is what got me started and it was great for me :-)
This will detect a touch and check if it is not transparent. You need this if your bitmaps are not rectangles. myBitmap is just a simple container class I use.
private boolean clickOnBitmap(MyBitmap myBitmap, MotionEvent event) {
float xEnd = myBitmap.originX() + myBitmap.width();
float yEnd = myBitmap.originY() + myBitmap.height();;
if ((event.getX() >= myBitmap.originX() && event.getX() <= xEnd)
&& (event.getY() >= myBitmap.originY() && event.getY() <= yEnd) ) {
int pixX = (int) (event.getX() - myBitmap.originX());
int pixY = (int) (event.getY() - myBitmap.originY());
if (!(myBitmap.getBitmap().getPixel(pixX, pixY) == 0)) {
return true;
} else {
return false;
}
}
return false;
}
This is the on touch code for completeness
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (clickOnBitmap(dog, event)) {
Toast.makeText(getContext(), "dog", Toast.LENGTH_SHORT).show();
} else if (clickOnBitmap(mouse,event)) {
Toast.makeText(getContext(), "mouse", Toast.LENGTH_SHORT).show();
}
return true;
case MotionEvent.ACTION_OUTSIDE:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
return true;
}
return false;
}