以编程方式检测点是否在圆弧上?

时间:2013-10-25 17:45:21

标签: java android math android-canvas geometry

我创造了一个弧形。我想在点击弧时在不同的弧上做某些事情。我怎么知道弧是否被触摸?有人可以提供一些onTouch方法的代码来进行这样的计算。还请稍微解释一下。

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;
        }

    }

修改

@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;
    }

1 个答案:

答案 0 :(得分:3)

我不会为此发布代码,因为我不太习惯使用Java UI,但是你所描述的背后的数学应该不会太难。

为了确保我理解你在做什么:你有一个由某个中心点(x 0 ,y 0 )定义的圆弧,半径为r ,起始角θ 0 ,并且结束角θ 1 。然后,您需要测试点(x,y)并确定用户是否单击了圆圈。

如果我们将所有内容都转换回原点,这个问题就更容易解决,因为三角函数相对于原点总是更容易。那么让我们来吧

  

x'= x - x 0

     

y'= y - y 0

现在你有了x'和y',我们可以通过计算来判断它离圆心的距离

  

dist =√(x' 2 + y' 2

如果此值不接近半径r,则点击的点无法靠近弧的任何位置。由于圆弧在数学上是无限小的,因此您可能希望在用户单击圆弧时设置一些“公差”。例如,您可以定义一些常量TOLERANCE,然后假设用户点击圆周长

  

| dist - r | ≤容差

现在,假设弧只是圆的边界。如果您正在绘制填充圆弧,则可以只检查是否

  

dist≤r+ TOLERANCE

这会检查该点是否在圆圈内。

现在,此时您可以检查该点是否在圆圈内/内。接下来的问题是他们是否点击了圆弧的一部分。为此,您可以使用Math.atan2和计算Math.atan2(y', x')来计算该点相对于圆心的角度θ。这会让你回到一个角度θ(以弧度为单位)。然后你可以检查θ 0 ≤θ≤θ 1 ,然后它会告诉你他们是否点击了你关心的圆圈部分。

简而言之:

  • 从x,y,x 0 计算x'和y',y 0
  • 从x'和y'计算dist。
  • 使用上述数学确定它们是否击中圆/圆周。
  • 使用Math.atan2获取角度θ
  • 查看θ是否在您想要的范围内。

希望这有帮助!