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.
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 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.");
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 :)
Is there a way to get the coordinates of a touch event on a scrollable map?
Meaning when I touch at one point on the screen, getX() and getY() return the respective values and then when I scroll the map, the coordinates returned should be relative to the map, not the screen.
eg. I have a 750x750 background map, and my device screen size is 480x800.
when I first touch, say the coordinates returned are (100, 200). now when I scroll the map and touch somewhere else, I get the coordinates as 200, 200.
I want to get the coordinates with respect to the map and not the screen.
I've been trying to figure this out for a long time and have scoured the net and other sites in vain.
please help.
thanks in advance
\m/
i need the coordinates coz i'm developing a game in which i have a large map and objects placed on it. when i scroll the map, i need the objects to move along with in the same position.
here is my code:
#Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
touchStart.set(ev.getX(), ev.getY());
x = ev.getX();
y = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
newX = ev.getX() - touchStart.x + prevPicStart.x;
newY = ev.getY() - touchStart.y + prevPicStart.y;
if ((newX <= 0 && newX > 0 - mBG.getWidth() + Craz.DISP_WIDTH)) {
picStart.x = newX;
}
if ((newY <= 0 && newY > 0 - mBG.getHeight() + Craz.DISP_HEIGHT)) {
picStart.y = newY;
}
invalidate();
break;
case MotionEvent.ACTION_UP:
prevPicStart.x = picStart.x;
prevPicStart.y = picStart.y;
break;
}
return true;
}
#Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
canvas.drawBitmap(mBG, picStart.x, picStart.y, paint);
canvas.translate(picStart.x, picStart.y);
mBDoor.draw(canvas);
}
Its pretty easy. When you scroll your map, you have an offset. If you scroll to the right (the background will move to the left), your offset will be negative. Lets say, you have an offset on x of -50, and you click the screen coordinates 100 you simply do the math:
mapCoordX = screenX - offsetX; // makes: 100 - (-50) = 150
just looking at the X coordinate, for Y it should be the same.
I have written a tutorial about a map of tiles and scrolling over it. Maybe you take a look at it, too.
Pseudocode!
for (int id = 0; id < mapSize; id++) {
Tile tile = new Tile(id);
startX = id * tileWidth;
startY = id % rowLenght + id / rowLenght;
tile.setBackground(createCroppedBitmap(background, startX, startY));
}