Android Custom Control - Draggable Control Ring Control Bar

Keywords: Android github

Received such a demand the other day, originally thought it was very simple, but I didn't expect to find that it was still a bit cumbersome to achieve in the end. Here is a small summary.

Take a look at the following sample of this requirement:

Then take a look at the final result, which may be a problem with the gif recording software, there are some glimpses, just ignore them:

First of all, we need to analyze the core point, how to get the arc length corresponding to the sliding distance, see the picture:

p1 is the point your finger is pressing on, so it is obvious that to know the current progress arc value, you need the value of angle d.
Taking P as the center point, atan(b)=Math.atan((-p.y)/(-p.x));
So the value of angle d is Math.toDegrees(atan);
Then the value of angle b comes out, b=Math.toDegrees(atan) + mProgressOffest;
The circle in the graph can be divided into four quadrants. Similarly, the method of finding arc length in four quadrants can be obtained:

    /**
     * Update current progress in radians
     *
     * @param x Press x-coordinate point
     * @param y Press y coordinate point
     */
    private void updateCurrentAngle(float x, float y) {
        //Convert coordinates to corresponding angles
        float pointX = x - mCenterX;
        float pointY = y - mCenterY;
        float tan_x;//Processing post-processing x-values based on the quadrant of the left point
        float tan_y;//Processing y-values based on the quadrant of the left point
        double atan;//Arc Edge angle in Quadrant

        //01: First quadrant - upper right corner area
        if (pointX >= 0 &amp;&amp; pointY <= 0) {
            tan_x = pointX;
            tan_y = pointY * (-1);
            atan = Math.atan(tan_x / tan_y);//Finding arc edges
            mCurrentAngle = (int) Math.toDegrees(atan) + 90.f + mProgressOffest;
        }

        //02: Second quadrant - upper left corner area
        if (pointX <= 0 &amp;&amp; pointY <= 0) {
            tan_x = pointX * (-1);
            tan_y = pointY * (-1);
            atan = Math.atan(tan_y / tan_x);//Finding arc edges
            mCurrentAngle = (int) Math.toDegrees(atan) + mProgressOffest;
        }

        //03: Third quadrant - lower left corner area
        if (pointX <= 0 &amp;&amp; pointY >= 0) {
            tan_x = pointX * (-1);
            tan_y = pointY;
            atan = Math.atan(tan_x / tan_y);//Finding arc edges
            if ((int) Math.toDegrees(atan) >= (90.f - mProgressOffest)) {
                mCurrentAngle = (int) Math.toDegrees(atan) - (90.f - mProgressOffest);
            } else {
                mCurrentAngle = (int) Math.toDegrees(atan) + 270.f + mProgressOffest;
            }
        }

        //04: Fourth quadrant - lower right corner area
        if (pointX >= 0 &amp;&amp; pointY >= 0) {
            tan_x = pointX;
            tan_y = pointY;
            atan = Math.atan(tan_y / tan_x);//Finding arc edges
            mCurrentAngle = (int) Math.toDegrees(atan) + 180.f + mProgressOffest;
        }
    }

Get the area under your finger to avoid misjudgments:

    /**
     * Press to determine if the pressed point is within the circle
     *
     * @param x x Coordinate Points
     * @param y y Coordinate Points
     */
    private boolean isTouchArc(float x, float y) {
        double d = getTouchRadius(x, y);
        return d >= mMinValidateTouchArcRadius &amp;&amp; d <= mMaxValidateTouchArcRadius;
    }

    /**
     * Calculate the distance from a point to a circle
     *
     * @param x x Coordinate Points
     * @param y y Coordinate Points
     */
    private double getTouchRadius(float x, float y) {
        float cx = x - getWidth() / 2;
        float cy = y - getHeight() / 2;
        return Math.hypot(cx, cy);
    }

Draw bitmap;

 /**
     * Draw small dot bitmap
     *
     * @param canvas canvas
     */
    private void drawDragBitmap(Canvas canvas) {
        PointF progressPoint = ChartUtils.calcArcEndPointXY(mCenterX, mCenterY, mRadius,
                mCurrentAngle, 180.f - mProgressOffest);

        int left = (int) progressPoint.x - mDragBitmap.getWidth() / 2;
        int top = (int) progressPoint.y - mDragBitmap.getHeight() / 2;

        //        mBitmapRect = new Rect(left, top, left + mDragBitmap.getWidth(), top +
        //                mDragBitmap.getHeight());
        //
        //        canvas.drawBitmap(mDragBitmap,
        //                new Rect(0, 0, mDragBitmap.getWidth(), mDragBitmap.getHeight()),
        //                mBitmapRect, mBitmapPaint);
        //BitmapUtils scales directly using the scaling method in bitmap. You can either scale without Rect or limit the size of the bitmap by limiting Rect
        canvas.drawBitmap(mDragBitmap, left, top, mBitmapPaint);
    }

Override onTouchEvent events;

@Override
    public boolean onTouchEvent(MotionEvent event) {
        //Get the coordinates of the click location
        float x = event.getX();
        float y = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (isTouchArc(x, y)) {
                    mTouchQuadrant = getTouchQuadrant(x, y);
                    mIsTouchOnArc = true;
                    updateCurrentAngle(x, y);
                    return true;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (mIsTouchOnArc) {
                    updateCurrentAngle(x, y);
                    if (mOnCirqueProgressChangeListener != null)
                        mOnCirqueProgressChangeListener.onChange(mMinProgress, mMaxProgress,
                                Integer.parseInt(mText.replace("℃", "")));
                }
                break;
            case MotionEvent.ACTION_UP:
                mIsTouchOnArc = false;
                mTouchQuadrant = 0;
                if (mOnCirqueProgressChangeListener != null)
                    mOnCirqueProgressChangeListener.onChangeEnd(mMinProgress, mMaxProgress,
                            Integer.parseInt(mText.replace("℃", "")));
                break;
        }

        invalidate();
        return true;
    }

The basic custom control is finished here.But!Right?Forget it!What?That's right. It's the upper and lower limits of the circle that make me feel so sore.
Since the current angle value ranges from 0 to 360 when the finger slides, it is not possible to simply limit the upper and lower limits.Without any judgment, you can slide freely at the starting point, as shown in the following figure:

Obviously, this is not possible, then after a twist of chicken and dog jumps, Jane (ou) Jane (xin) Jane (li) xue basically achieved the requirements, and finally updated the current Angle code as follows:

     /**
     * Update current progress in radians
     *
     * @param x Press x-coordinate point
     * @param y Press y coordinate point
     */
    private void updateCurrentAngle(float x, float y) {
        //Convert coordinates to corresponding angles
        float pointX = x - mCenterX;
        float pointY = y - mCenterY;
        float tan_x;//Processing post-processing x-values based on the quadrant of the left point
        float tan_y;//Processing y-values based on the quadrant of the left point
        double atan;//Arc Edge angle in Quadrant

        //01: First quadrant - upper right corner area
        //Ensure dragBitmap does not update currentAngle at peak because it slips to this quadrant
        if (pointX >= 0 &amp;&amp; pointY <= 0) {
            if (((mLastQuadrant == 3 &amp;&amp; mLastAngle == 359.f)
                    || (mLastQuadrant == 3 &amp;&amp; mLastAngle == 0.f))
                    &amp;&amp; mTouchQuadrant != 1)
                return;

            tan_x = pointX;
            tan_y = pointY * (-1);
            atan = Math.atan(tan_x / tan_y);//Finding arc edges
            mCurrentAngle = (int) Math.toDegrees(atan) + 90.f + mProgressOffest;
            mLastQuadrant = 1;
        }

        //02: Second quadrant - upper left corner area
        if (pointX <= 0 &amp;&amp; pointY <= 0) {
            if (((mLastQuadrant == 3 &amp;&amp; mLastAngle == 359.f)
                    || (mLastQuadrant == 3 &amp;&amp; mLastAngle == 0.f))
                    &amp;&amp; mTouchQuadrant != 2) {
                return;
            }

            tan_x = pointX * (-1);
            tan_y = pointY * (-1);
            atan = Math.atan(tan_y / tan_x);//Finding arc edges
            mCurrentAngle = (int) Math.toDegrees(atan) + mProgressOffest;
            mLastQuadrant = 2;
        }

        //03: Third quadrant - lower left corner area
        if (pointX <= 0 &amp;&amp; pointY >= 0) {
            tan_x = pointX * (-1);
            tan_y = pointY;
            atan = Math.atan(tan_x / tan_y);//Finding arc edges
            if ((int) Math.toDegrees(atan) >= (90.f - mProgressOffest)) {
                mCurrentAngle = (int) Math.toDegrees(atan) - (90.f - mProgressOffest);
                if (mLastAngle >= 270.f) {
                    mCurrentAngle = 359.f;
                }
            } else {
                mCurrentAngle = (int) Math.toDegrees(atan) + 270.f + mProgressOffest;
                if (mLastAngle <= 90.f) {
                    mCurrentAngle = 0.f;
                }
            }
            mLastQuadrant = 3;
        }

        //04: Fourth quadrant - lower right corner area
        //Ensure dragBitmap does not update currentAngle at peak because it slips to this quadrant
        if (pointX >= 0 &amp;&amp; pointY >= 0) {
            if (((mLastQuadrant == 3 &amp;&amp; mLastAngle == 359.f)
                    || (mLastQuadrant == 3 &amp;&amp; mLastAngle == 0.f))
                    &amp;&amp; mTouchQuadrant != 4)
                return;

            tan_x = pointX;
            tan_y = pointY;
            atan = Math.atan(tan_y / tan_x);//Finding arc edges
            mCurrentAngle = (int) Math.toDegrees(atan) + 180.f + mProgressOffest;
            mLastQuadrant = 4;
        }
        mLastAngle = mCurrentAngle;
    }

In fact, it was really a simple custom control before it was implemented, and the result was unexpected because the last bit of code was tossing around for half a day.Although this code looks really painful at the end, there is no good way to think about it for the time being, let's do it first.

Finally, attach the complete code:
https://github.com/Horrarndoo/CirqueControlView

Free access to android Development Architecture information (including Fultter, Advanced UI, Performance Optimization, Architect Course, NDK, Kotlin, ReactNative+Weex) and Topic Summary for first-line Internet companies about android interviews can be added [Android Development Architecture]

Posted by MStaniszczak on Mon, 06 May 2019 08:25:39 -0700