How to get touch of shape (arc) from android canvas?

I am creating a custom view.

It has 4 arcs.

I am drawing arcs using a shape RectF

, during onDraw()

:

// arcPaint is a Paint object initialized
// in the View constructor
arcPaint.setStrokeWidth(strokeWidth);
arcPaint.setColor(arcColor);

// Draw arcs, arc1, arc2, arc3 & arc4 are
// measured and initialized during onMeasure()
canvas.drawArc(arc1, startAngle, arc1Angle, false, arcPaint);
canvas.drawArc(arc2, startAngle, arc2Angle, false, arcPaint);
canvas.drawArc(arc3, startAngle, arc3Angle, false, arcPaint);
canvas.drawArc(arc4, startAngle, arc4Angle, false, arcPaint);

      

So the result is:

enter image description here

If I draw objects RectF

using canvas.drawRect()

with this code:

canvas.drawRect(arc1, arcPaint);
canvas.drawRect(arc2, arcPaint);
canvas.drawRect(arc3, arcPaint);
canvas.drawRect(arc4, arcPaint);

      

The result will be the following:

enter image description here

Comparing all, arcs and rectangles gives the following:

enter image description here

I know I can override the method onTouchEvent()

and get the X, Y coordinates, but I don't know how each coordinate relates to each shape drawn on the canvas.

What I want to do is detect when an arc, not a straight line, is touched in canvas

, how can I achieve this?

Edit:

Unlike the straight line, I don't want to detect when the user touches these areas (the corners of the rectangle):

enter image description here

+3


source to share


1 answer


It basically boils down to comparing the distances from the center point of that shape. Since the bounding boxes will always be square, one of these distances is substantially fixed, making it much easier.

The general methodology here is to calculate the distance from the center point to the tangency point and check if the difference between this distance and the radius of the arc is within half the stroke width anyway.

private static boolean isOnRing(MotionEvent event, RectF bounds, float strokeWidth) {
    // Figure the distance from center point to touch point.
    final float distance = distance(event.getX(), event.getY(),
                                    bounds.centerX(), bounds.centerY());

    // Assuming square bounds to figure the radius.
    final float radius = bounds.width() / 2f;

    // The Paint stroke is centered on the circumference,
    // so the tolerance is half its width.
    final float halfStrokeWidth = strokeWidth / 2f;

    // Compare the difference to the tolerance.
    return Math.abs(distance - radius) <= halfStrokeWidth;
}

private static float distance(float x1, float y1, float x2, float y2) {
    return (float) Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
}

      

The method is onTouchEvent()

isOnRing()

simply called with the MotionEvent

bounding arc RectF

and the stroke width Paint

. For example:

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            if (isOnRing(event, arc1, arcPaint.getStrokeWidth())) {
                // Hit.
            }
            break;
            ...
    }
    ...
}

      

This by itself will determine if the tangency point is anywhere on the full ring, described by the radius and stroke width. That is, it does not account for "gaps" in the arc. There are several processing methods, if necessary.

If you intend for the start and sweep angles to coincide with the x / y axes, the easiest method would probably be to check for signs of differences in the coordinates of the touch point and center point. Broadly speaking, if touch x minus center x is positive, then you are on the right side; if negative, you're on the left. Likewise, calculating for the top or bottom level will allow you to determine which quadrant the touch occurred in and ignore the event.



final float dx = event.getX() - arc1.centerX();
final float dy = event.getY() - arc1.centerY();

if (dx > 0) {
    // Right side
}
else {
    // Left side
}
...

      

However, if any angle doesn't have to be a coterminal with the axis, then the math is a little more complicated. For example:

private static boolean isInSweep(MotionEvent event, RectF bounds,
                                 float startAngle, float sweepAngle) {
    // Figure atan2 angle.
    final float at =
        (float) Math.toDegrees(Math.atan2(event.getY() - bounds.centerY(),
                                          event.getX() - bounds.centerX()));

    // Convert from atan2 to standard angle.
    final float angle = (at + 360) % 360;

    // Check if in sweep.
    return angle >= startAngle && angle <= startAngle + sweepAngle;
}

      

if

to onTouchEvent()

then change to include both tests.

if (isOnRing(event, arc1, arcPaint.getStrokeWidth()) &&
    isInSweep(event, arc1, startAngle, arc1Angle)) {
    // Hit.
}

      

These methods can be easily combined into one, but they are left separately for clarity.

+2


source







All Articles