Android: Erratic behavior when Drawing rectangle using 2 multi touch points - android

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.

Related

View not moving with finger with onTouchListener

I'm trying to make a View's x value move along with a finger as it drags across the screen. Although the view moving is smooth, it only moves ~1/3 of the distance that the finger does. The View in my case happens to be a RecyclerView, but I think this is irrelevant to the problem. What am I doing wrong in the following implementation?
view.setOnTouchListener((v, event) -> {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_UP:
view.animate().translationX(0).setDuration(200);
break;
case MotionEvent.ACTION_MOVE:
if (event.getHistorySize() < 1) break;
final float latestX = event.getX(),
secondLatestX = event.getHistoricalX(event.getHistorySize() - 1),
firstX = event.getHistoricalX(0),
secondX = (event.getHistorySize() > 1) ?
event.getHistoricalX(1) : latestX;
final float firstY = event.getHistoricalY(0),
secondY = (event.getHistorySize() > 1) ?
event.getHistoricalY(1) : event.getY();
// if initial change x is greater than y
if (Math.abs(secondX - firstX) > Math.abs(secondY - firstY)) {
view.setX(messageList.getX() + (latestX - secondLatestX));
return true;
}
break;
}
return false;
});
If the code needs some explanation:
firstX is the first x value that the finger touched on the screen
secondX is the second x value that the finger touched on the screen
(as in, the next time onTouch was fired with ACTION_MOVE)
latestX is the most recent x value of the finger
secondLatestX is the second most recent x value of the finger
All y variables are the
same as their x counter-parts but in the y direction
Am I calculating the variables incorrectly? I cannot seem to find a logic issue in anything.
Since I could not find any way to do this, I just decided to create my own library from the ground up. Here it is:
https://github.com/GregoryConrad/SlideDetector
As from your Use-case what i can understand is you want to move the view based on your touch.
Check out my project Long press and Move the square in infinite View.
ZoomIt

Regions in Android, basic touch implementing

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.");

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.

Transfer longpress from one android view to another, without releasing finger

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 :)

Multiple touch events

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.

Categories

Resources