Android: How to check if a rectangle contains touched point?
Asked Answered
Q

5

20

I would try to develop an application in which I can draw a planimetry. So, each room has got its own ID or name and, if I touch a room, I want to show a Toast Message with that ID or name. The problem is how check if and which path is touched!!

I saw a lot of topic discussions that talked about this problem. Someone says to use the getBounds method and, after, contains method for checking if touched point is in Rect. But, I guess getBounds returns the smallest Rect that contains path, right?

So, rooms have different custom geometric forms and, for this reason, if I get bounds about 2 close rooms, method could return a shared set of points. Bad! Each room has got only their area points. How can I solve this problem?

In iOS i could use PathContainsPoint method, but, unfortunaly, Android Path doesn't have something similar.

Quiteria answered 6/3, 2012 at 16:40 Comment(0)
Q
50

Ok i solved my problem. I post the example code:

Path p;
Region r;

@Override
public void onDraw(Canvas canvas) {

    p = new Path();

    p.moveTo(50, 50);
    p.lineTo(100, 50);
    p.lineTo(100, 100);
    p.lineTo(80, 100);
    p.close();      

    canvas.drawPath(p, paint);

    RectF rectF = new RectF();
    p.computeBounds(rectF, true);
    r = new Region();
    r.setPath(p, new Region((int) rectF.left, (int) rectF.top, (int) rectF.right, (int) rectF.bottom));

}   

public boolean onTouch(View view, MotionEvent event) {

    Point point = new Point();
    point.x = event.getX();
    point.y = event.getY();
    points.add(point);
    invalidate();
    Log.d(TAG, "point: " + point);

    if(r.contains((int)point.x,(int) point.y))
        Log.d(TAG, "Touch IN");
    else
        Log.d(TAG, "Touch OUT");

    return true;
}
Quiteria answered 14/5, 2012 at 15:47 Comment(5)
This identify a touch event in a given bounding polygon around the path rather than on the polyline.Nuts
Exactly what i was looking forQuiteria
It's not important. I saved each touched point for my purposesQuiteria
FYI, creating new RectF and Region in onDraw is not good for performance, better reuse if possible.Idou
I have another path instead of the event (x,y) So how I can check that one of my paths is inside of the current path or not?Sconce
G
13

As Edward Falk say, best way is to use path.op() becouse Region is square. And one point can be in 2 or 3 Regions.

For example:

enter image description here

All regions will contain blue point, but on fact only path4 contains this point.

int x, y;
Path tempPath = new Path(); // Create temp Path
tempPath.moveTo(x,y); // Move cursor to point
RectF rectangle = new RectF(x-1, y-1, x+1, y+1); // create rectangle with size 2xp
tempPath.addRect(rectangle, Path.Direction.CW); // add rect to temp path
tempPath.op(pathToDetect, Path.Op.DIFFERENCE); // get difference with our PathToCheck
if (tempPath.isEmpty()) // if out path cover temp path we get empty path in result
{ 
    Log.d(TAG, "Path contains this point");
    return true;
}
else
{
    Log.d(TAG, "Path don't contains this point");
    return false;
}
Gangrene answered 11/12, 2018 at 11:30 Comment(0)
S
2

Here's a thought: create a new path which is a tiny square around the point that was touched, and then intersect that path with your path to be tested using path.op() and see if the result is empty.

Strop answered 20/11, 2015 at 5:14 Comment(7)
Yes, but you are limited to API 19 and above. Is there any other solution ?Pepsinogen
If your path is a rectangle, use kinghomer's answer above. If your path is a polygon, see sromku's answer here: https://mcmap.net/q/661783/-test-of-point-inside-polygon-in-android. I have a more general solution that handles a path comprised of arcs, circles, and line segments, but it's a little hairy. I can post it next week if there's enough interest.Strop
Thanks, I have solved it by subclassing Path and keeping track of all points, then I've used this https://mcmap.net/q/63261/-how-can-i-determine-whether-a-2d-point-is-within-a-polygon formula and rewritten it into Java.Pepsinogen
That's pretty clever actually. And thanks for the link; that's definitely going into my toolbox. (Fun fact: I had a class from Prof. Franklin way back when)Strop
I guess if you want I could post an answer with whole solution. Not sure if thats gonna help anyonePepsinogen
This answer doesn't work. Perhaps this is intended behaviour, perhaps a bug. I used Path.op(__, Path.Op.INTERSECT) to see whether a path intersects a tiny rectangle centered on (x, y) and it would sometimes return a non-empty intersection when (x,y) was relatively close to the path, but not inside it. NB my paths are made up of straight lines which sometimes go in on themselves. Also note this wasn't simply because my "tiny rectangle" created for op() wasn't tiny enough!Fini
@Pepsinogen I'm interested in your solution can you post the answer for it?Gertie
M
1

Go through the path and check if the position on the path is near to the touched point. Following method worked for both linear paths and the complex paths.

fun Path.doIntersect(x: Float, y: Float, width: Float): Boolean {
    val measure = PathMeasure(this, false)
    val length = measure.length
    val delta = width / 2f
    val position = floatArrayOf(0f, 0f)
    val bounds = RectF()
    var distance = 0f
    var intersects = false
    while (distance <= length) {
        measure.getPosTan(distance, position, null)
        bounds.set(
            position[0] - delta,
            position[1] - delta,
            position[0] + delta,
            position[1] + delta
        )
        if (bounds.contains(x, y)) {
            intersects = true
            break
        }
        distance += delta / 2f
    }
    return intersects
}
Marlenamarlene answered 14/10, 2021 at 9:15 Comment(0)
R
0

Region and path.op() approaches work well, but suffer from a lack of precision. After some tests, it seems like they define a zone at the segment level of the path, like so: image segment zone. Another approach is to work with polygons. While giving better results, it still registers some unexpected touches, and misses obvious ones. But there is another solution, which consists in retrieving a set of points along the path, and registering touches inside an area surrounding them. For that, we can use PathMeasure class to get the desired number of points, and store them in a List<Float>:

PathMeasure pathMeasure = new PathMeasure(path, false);
List<Float> listFloat = new ArrayList<>();
for (float i = 0; i < 1.1; i += 0.1) {
    float[] pointCoordinates = new float[2];
    pathMeasure.getPosTan(pathMeasure.getLength() * i, pointCoordinates, null);
    listFloat.add(pointCoordinates[0]);
    listFloat.add(pointCoordinates[1]);
}

Then, we need to loop over the List<Float> and check if a touch event is inside a defined zone surrounding these points. Here, a zone covers a tenth of the path length, since we collected ten points:

float areaLength = pathMeasure.getLength() / 20; // since we collect ten points, we define a zone covering a tenth of path length    
for (int i = 0; i < listFloat.size() - 1; i += 2) {
    if (touchX > listFloat.get(i) - areaLength
     && touchX < listFloat.get(i) + areaLength
     && touchY > listFloat.get(i+1) - areaLength
     && touchY < listFloat.get(i+1) + areaLength) {
        Log.d(TAG, "path touched");
    }

}

These successive zones capture touch events with a better precision, following more closely the path: image points zone. This method can be improved, by working with Rect class to ensure that these zones are not overlapping each other, and also to bound the start and end point with a better precision.

Robinetta answered 6/6, 2020 at 12:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.