0

我创建了一个弧。单击弧时,我想在不同的弧上做某些事情。我如何知道是否触摸了电弧?有人可以为 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;
    }
4

1 回答 1

3

我不会为此发布代码,因为我对使用 Java UI 并不完全满意,但是你所描述的背后的数学应该不会太难。

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

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

x' = x - x 0

y' = y - y 0

现在你有了 x' 和 y',我们可以通过计算确定它离圆心有多远

距离 = √(x' 2 + y' 2 )

如果该值不接近半径 r,则单击的点不可能位于圆弧附近。由于弧线在数学上无限小,因此您可能希望为用户单击弧线时设置一些“容差”。例如,您可以定义一些常数TOLERANCE,然后假设用户正在单击圆的圆周,如果

|距离 - r| ≤ 公差

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

距离 ≤ r + 公差

这将检查该点是否在圆内。

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

简而言之:

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

希望这可以帮助!

于 2013-10-25T20:52:35.690 回答