Reputation: 3270
I have created an arc. I want to do certain things on different arcs when an arc is clicked. How do I know if an arc is touched or not? Can someone please provide some code for onTouch method to do such a calculation. Also please explain it a little bit.
package com.example.android.customviews;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
/**
* Limit Indicator is used to show any kind of limits such as Balance and Actual
* Amount of wallet present in an account. In order to use this in the XML
* Layout, please include the following: <br />
* <br />
*
* xmlns:custom="http://schemas.android.com/apk/res/com.example.android.customviews"
*
* <br /> <br />
*
* Following custom attributes are provided: <br />
* <br />
*
* custom:borderColor <br />
* custom:borderRadius <br />
* custom:outerCircleRadius <br />
* custom:text <br />
* custom:textSize <br />
* custom:innerCircleColor <br />
*
* @author Syed Ahmed Hussain
*/
public class LimitIndicator extends ViewGroup {
// ============================================================================================
// Variables Declaration
private int mInnerCircleColor;
private int mBorderColor;
private int mTextColor;
private float mTextSize;
private String mTitleText = "";
private float mHalfOfBorderWidth = 0.0f;
private float mOuterCircleRadius = 2.0f;
private float mBorderWidth = 30.0f;
private Paint mDialPaint, mTextPaint, mBorderPaint, mInnerCirclePaint;
private float mCenterX = 100.0f;
private float mCenterY = 100.0f;
private int mTotalProgressInDegrees;
private int mTotalProgress = -1;
// Start Angle should be 90 degrees to create a clockwise illusion.
private int mStartAngle = 270;
// This should be the one which provides us a percentage wise drawing
private int mSweepAngle = 1;
private RectF mBorderBounds = null;
// ============================================================================================
// Constructors
public LimitIndicator(Context pContext) {
super(pContext);
Log.d("LimitIndicator", "LimitIndicator(Context pContext) called");
initialize();
}
public LimitIndicator(Context pContext, AttributeSet pAttrs) {
super(pContext, pAttrs);
Log.d("LimitIndicator", "LimitIndicator(Context pContext, AttributeSet pAttrs) called");
TypedArray typedArray = pContext.obtainStyledAttributes(pAttrs, R.styleable.LimitIndicator, 0, 0);
try {
mOuterCircleRadius = typedArray.getDimension(R.styleable.LimitIndicator_outerCircleRadius, mOuterCircleRadius);
mTextColor = typedArray.getColor(R.styleable.LimitIndicator_textColor, Color.WHITE);
mTitleText = typedArray.getString(R.styleable.LimitIndicator_text);
mTextSize = typedArray.getDimension(R.styleable.LimitIndicator_textSize, 25);
mTotalProgress = typedArray.getInteger(R.styleable.LimitIndicator_numerator, mTotalProgress);
mBorderColor = typedArray.getColor(R.styleable.LimitIndicator_borderColor, Color.BLACK);
mBorderWidth = typedArray.getDimension(R.styleable.LimitIndicator_borderRadius, mBorderWidth);
mInnerCircleColor = typedArray.getColor(R.styleable.LimitIndicator_innerCircleColor, Color.GREEN);
} finally {
typedArray.recycle();
}
initialize();
}
// ============================================================================================
// Initialization
/**
* Initialize all elements
*/
private void initialize() {
// Set up the paint for the dial
mDialPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mDialPaint.setStyle(Paint.Style.FILL);
mDialPaint.setColor(Color.GRAY);
// Set up the paint for the label text
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setTypeface(Typeface.SANS_SERIF);
mTextPaint.setTextAlign(Align.CENTER);
mTextPaint.setColor(mTextColor);
mTextPaint.setTextSize(mTextSize);
// Set up the paint for the border
mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBorderPaint.setStyle(Paint.Style.STROKE);
mBorderPaint.setStrokeWidth(mBorderWidth);
mBorderPaint.setColor(mBorderColor);
mBorderPaint.setAntiAlias(true);
mBorderPaint.setDither(true);
mInnerCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mInnerCirclePaint.setStyle(Paint.Style.FILL);
mInnerCirclePaint.setColor(mInnerCircleColor);
// mBorderPaint.setStrokeJoin(Paint.Join.ROUND);
// mBorderPaint.setStrokeCap(Paint.Cap.ROUND);
mBorderBounds = new RectF(getLeft(), getTop(), getRight(), getBottom());
}
// ============================================================================================
// Drawing on surface
@Override
protected void onDraw(Canvas pCanvas) {
super.onDraw(pCanvas);
Log.d("LimitIndicator", "OnDraw called");
Log.d("Measured Spec Width", mCenterX + "");
Log.d("Measured Spec Height", mCenterY + "");
pCanvas.drawCircle(mCenterX, mCenterY, mOuterCircleRadius, mDialPaint);
pCanvas.drawCircle(mCenterX, mCenterY, (float) (mOuterCircleRadius - mBorderWidth + 1) , mInnerCirclePaint);
pCanvas.drawText(mTitleText, mCenterX, mCenterY + 5, mTextPaint);
pCanvas.drawArc(mBorderBounds, mStartAngle, mSweepAngle, false, mBorderPaint);
if (mSweepAngle < mTotalProgressInDegrees) {
mSweepAngle+=3;
mBorderPaint.setStrokeWidth(mBorderWidth++);
invalidate();
}
}
@Override
protected void onLayout(boolean pChanged, int pLeft, int pTop, int pRight, int pBottom) {
Log.d("LimitIndicator", "OnLayout called");
for (int i = 0; i < getChildCount(); i++) {
getChildAt(i).layout(0, 0, pRight, pBottom);
}
}
@Override
protected void onSizeChanged(int pW, int pH, int pOldw, int pOldh) {
super.onSizeChanged(pW, pH, pOldw, pOldh);
Log.d("LimitIndicator", "OnSizeChanged called");
float xPad = (getPaddingLeft() + getPaddingRight());
float yPad = (getPaddingTop() + getPaddingBottom());
// To draw Circle in the middle
mCenterX = (float) ((pW - xPad) * 0.5);
mCenterY = (float) ((pH - yPad) * 0.5);
// This (mBorderBounds.bottom needs to be fixed. Width &
// Height should be equal in order
// to create a perfect circle. Otherwise an
// Oval will be created! :P
// Bounds for creating an arc
mHalfOfBorderWidth = (float) (mBorderWidth * 0.5);
mBorderBounds.right = mCenterX + mOuterCircleRadius - mHalfOfBorderWidth;
mBorderBounds.left = mCenterX - mOuterCircleRadius + mHalfOfBorderWidth;
mBorderBounds.top = mCenterY - mOuterCircleRadius + mHalfOfBorderWidth;
mBorderBounds.bottom = mCenterY + mOuterCircleRadius - mHalfOfBorderWidth;
}
// =========================================================================================================
/**
* Start the progress/animation. Use this method to start the animated view.
*/
public void startProgress() {
if (mTotalProgress >= 0) {
float progressInDegrees = mTotalProgress;
mTotalProgressInDegrees = (int) (progressInDegrees/100 * 360);
invalidate();
}
}
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent pEvent) {
return super.dispatchPopulateAccessibilityEvent(pEvent);
}
// =========================================================================================================
// Getters && Setters!
/**
* @return the dialRadius
*/
public float getDialRadius() {
return mOuterCircleRadius;
}
/**
* @param pDialRadius
* the dialRadius to set
*/
public void setDialRadius(float pDialRadius) {
mOuterCircleRadius = pDialRadius;
}
/**
* @return the textSize
*/
public float getTextSize() {
return mTextSize;
}
/**
* @param pTextSize the textSize to set
*/
public void setTextSize(float pTextSize) {
mTextSize = pTextSize;
}
/**
* @return the textColor
*/
public int getTextColor() {
return mTextColor;
}
/**
* @param pTextColor the textColor to set
*/
public void setTextColor(int pTextColor) {
mTextColor = pTextColor;
}
/**
* @return the borderColor
*/
public int getBorderColor() {
return mBorderColor;
}
/**
* @param pBorderColor the borderColor to set
*/
public void setBorderColor(int pBorderColor) {
mBorderColor = pBorderColor;
}
/**
* @return the innerCircleColor
*/
public int getInnerCircleColor() {
return mInnerCircleColor;
}
/**
* @param pInnerCircleColor the innerCircleColor to set
*/
public void setInnerCircleColor(int pInnerCircleColor) {
mInnerCircleColor = pInnerCircleColor;
}
/**
* @return the titleText
*/
public String getTitleText() {
return mTitleText;
}
/**
* @param pTitleText the titleText to set
*/
public void setTitleText(String pTitleText) {
mTitleText = pTitleText;
}
/**
* @return the outerCircleRadius
*/
public float getOuterCircleRadius() {
return mOuterCircleRadius;
}
/**
* @param pOuterCircleRadius the outerCircleRadius to set
*/
public void setOuterCircleRadius(float pOuterCircleRadius) {
mOuterCircleRadius = pOuterCircleRadius;
}
/**
* @return the borderWidth
*/
public float getBorderWidth() {
return mBorderWidth;
}
/**
* @param pBorderWidth the borderWidth to set
*/
public void setBorderWidth(float pBorderWidth) {
mBorderWidth = pBorderWidth;
}
/**
* @return the totalProgress
*/
public int getTotalProgress() {
return mTotalProgress;
}
/**
* @param pTotalProgress the totalProgress to set
*/
public void setTotalProgress(int pTotalProgress) {
mTotalProgress = pTotalProgress;
}
}
EDIT
@Override
public boolean onTouchEvent(MotionEvent pEvent) {
double x = pEvent.getX();
double y = pEvent.getY();
double x1 = x - mCenterX;
double y1 = y - mCenterY;
double distance = Math.sqrt(x1*x1 + y1*y1);
RectF topBoundingRect = new RectF(mCenterX - mOuterCircleRadius, mCenterY - mOuterCircleRadius, mCenterX + mOuterCircleRadius, mCenterY);
if (Math.abs(distance - mOuterCircleRadius) <= MAX_TOUCH_TOLERANCE && topBoundingRect.contains((float) x, (float) y)) {
// the user is touching the arc. Which arc is tapped? How do I know that?
}
return true;
}
Upvotes: 0
Views: 847
Reputation: 373482
I won't post code for this, since I'm not completely comfortable working with Java UIs, but the math behind what you're describing shouldn't be too hard.
To make sure that I understand what you're doing: you have a circular arc defined by some center point (x0, y0), a radius r, a start angle θ0, and an end angle θ1. You then want to take a test point (x, y) and determine whether the user clicked on the circle.
This problem is a lot easier to solve if we translate everything back to the origin, since trigonometry is always easier relative to the origin. So let's let
x' = x - x0
y' = y - y0
Now that you have x' and y', we can determine how far away it is from the center of the circle by computing
dist = √(x'2 + y'2)
If this value isn't close to the radius r, then there's no way that the point clicked is anywhere near the arc. Since the arc mathematically is infinitely small, you probably want to set up some "tolerance" for when the user clicks on the arc. You could, for example, define some constant TOLERANCE
and then assume the user is clicking on the circumference of the circle if
|dist - r| ≤ TOLERANCE
Now, this assumes that the arc is just the border of the circle. If you are drawing a filled-in circular arc, you can instead just check whether
dist ≤ r + TOLERANCE
This checks whether the point is inside the circle at all.
Now, at this point you can check whether the point is in/inside the circle at all. The next question is whether they clicked on a part of the circle that's part of the arc. To do this, you can compute the angle θ at which the the point is relative to the center of the circle by using Math.atan2
and computing Math.atan2(y', x')
. This gives you back an angle θ (in radians). You can then check whether θ0 ≤ θ ≤ θ1, which will then tell you if they clicked on the part of the circle you care about.
In short:
Math.atan2
to get the angle θHope this helps!
Upvotes: 3