0

I made an app that allows the user to fingerprint on a custom view. But he is only able to draw straight, wide line. The problem is using

canvas.drawLine(x, y, end.x, end.y, paint);

to draw a line, we can't detect touch on the line. I tried to using a Rect as a touch zone, but a Rect is a straight rect, it can't match to the line zone :

In red the line draw by the user, in blue the rect that matches the touch zone

Didn't find a way to work with a path, as it was too narrow to click on it.

NitroG42
  • 5,336
  • 2
  • 28
  • 32

3 Answers3

1

Here how I made it. I have a list of object that are drawn on my canvas

@Override
protected void onDraw(final Canvas canvas) {
    super.onDraw(canvas);
    for (DrawObject drawObject : mDrawObjects) {
        drawObject.draw(canvas);
    }
}

On the onTouchevent, I iterate on this list, passing the motion event coordinate :

ListIterator iterator = mDrawObjects.listIterator(mDrawObjects.size());
iterator.previous();
while (iterator.hasPrevious()) {
    final DrawObject drawObject = (DrawObject) iterator.previous();
    boolean isInBound = drawObject.isInBounds(motionEvent.getX(), motionEvent.getY());
}

My drawObject is a line, so it contains a starting point, and an ending point.

public boolean isPointOnLine(PointF lineStaPt, PointF lineEndPt, PointF point) {
    final float EPSILON = width;
    if (Math.abs(lineStaPt.x - lineEndPt.x) < EPSILON) {
        // We've a vertical line, thus check only the x-value of the point.
        return (Math.abs(point.x - lineStaPt.x) < EPSILON);
    } else {
        float m = (lineEndPt.y - lineStaPt.y) / (lineEndPt.x - lineStaPt.x);
        float b = lineStaPt.y - m * lineStaPt.x;
        return (Math.abs(point.y - (m * point.x + b)) < EPSILON);
    }
}

Here I used the answer found in this topic to know if the touch is in the line bound. You need to use the width of your line as epsilon to allow the user to click outside of the center (line) of it.

There is one last problem :

With this method, if you click outside of the drawn line, but still in its continuity, it will trigger the onTouch. To fix this problem, we will use the rect that I drew in blue.

public void touchUp(float x, float y) {
    mRectF.set(mStart.x, mStart.y, mEnd.x, mEnd.y);
    mRectF.sort();
    int left = (int) ((mRectF.left < mRectF.right ? (int) mRectF.left : (int) mRectF.right) - width / 2);
    int right = (int) ((mRectF.right > mRectF.left ? (int) mRectF.right : (int) mRectF.left) + width / 2);
    int top = (int) ((mRectF.top < mRectF.bottom ? (int) mRectF.top : (int) mRectF.bottom) - width / 2);
    int bottom = (int) ((mRectF.bottom > mRectF.top ? (int) mRectF.bottom : (int) mRectF.top) + width / 2);
    mRectF.set(left, top, right, bottom);
}

On the touchup of a drawObject, we create the rect, sort it (so the right is really on the right side of the rectangle), and then we calculate a slightly larger rect than the drawn line.

And here the method that I call to check the touch :

public boolean isInBounds(float x, float y) {
    if (mRectF.contains(x, y)) {
        return isPointOnLine(mStart, mEnd, new PointF(x, y));
    }

    return false;
}

Here is the full class that I made :

public class LabelObject implements DrawObject {
private static final String TAG = LabelObject.class.getSimpleName();
protected static final boolean DEBUG = true;
private PointF mStart, mEnd;
private Paint mPaint;
private RectF mRectF;
private float width = 60;

private static final int MIN_SIZE = 40;

public LabelObject() {
    mPaint = new Paint();
    mPaint.setStrokeWidth(width);
    mPaint.setColor(Color.RED);

    mStart = new PointF();
    mEnd = new PointF();
    mRectF = new RectF();
}

public void draw(Canvas canvas) {
    canvas.drawLine(mStart.x, mStart.y, mEnd.x, mEnd.y, mPaint);
}

public void touchStart(float x, float y) {
    mStart.set(x, y);
    mEnd.set(x, y);
}

public void touchMove(float x, float y) {
    mEnd.set(x, y);
}

public void touchUp(float x, float y) {
    mRectF.set(mStart.x, mStart.y, mEnd.x, mEnd.y);
    mRectF.sort();
    int left = (int) ((mRectF.left < mRectF.right ? (int) mRectF.left : (int) mRectF.right) - width / 2);
    int right = (int) ((mRectF.right > mRectF.left ? (int) mRectF.right : (int) mRectF.left) + width / 2);
    int top = (int) ((mRectF.top < mRectF.bottom ? (int) mRectF.top : (int) mRectF.bottom) - width / 2);
    int bottom = (int) ((mRectF.bottom > mRectF.top ? (int) mRectF.bottom : (int) mRectF.top) + width / 2);
    mRectF.set(left, top, right, bottom);
}

public boolean isPointOnLine(PointF lineStaPt, PointF lineEndPt, PointF point) {
    final float EPSILON = width;
    if (Math.abs(lineStaPt.x - lineEndPt.x) < EPSILON) {
        // We've a vertical line, thus check only the x-value of the point.
        return (Math.abs(point.x - lineStaPt.x) < EPSILON);
    } else {
        float m = (lineEndPt.y - lineStaPt.y) / (lineEndPt.x - lineStaPt.x);
        float b = lineStaPt.y - m * lineStaPt.x;
        return (Math.abs(point.y - (m * point.x + b)) < EPSILON);
    }
}

public boolean isInBounds(float x, float y) {
    if (mRectF.contains(x, y)) {
        return isPointOnLine(mStart, mEnd, new PointF(x, y));
    }

    return false;
}

public boolean isLargeEnough() {
    return Math.abs(mRectF.height()) > MIN_SIZE || Math.abs(mRectF.width()) > MIN_SIZE;
}
}
Community
  • 1
  • 1
NitroG42
  • 5,336
  • 2
  • 28
  • 32
0

But for line with different length the touch difference is large

For example touching near a small line produces 16 whereas touching near the large line with same distance from the line produces 143.

I'm using the below formula

Math.abs(touchY - (m * touchX + b).

Have you found any alternate solution to this

Michaël Azevedo
  • 3,874
  • 7
  • 31
  • 45
0

My recommendation is to calculate the distance d between click point (x0, y0) and the line. You may want to use the formula here (Line defined by two points). Then allow the distance d to be less than an epsilon (could be the line's width). The Formula