How would I implement a swipe-based circular control like this?

前端 未结 10 2029
北恋
北恋 2021-01-30 23:52

I am working on an Android application, and I have a TextView where I display a price (for example 50$).

I would like to have a circular control similar to this picture:

10条回答
  •  清酒与你
    2021-01-31 00:12

    DialView Class :

    public abstract class DialView extends View {
    
        private float centerX;
        private float centerY;
        private float minCircle;
        private float maxCircle;
        private float stepAngle;
    
        public DialView(Context context) {
            super(context);
            stepAngle = 1;
            setOnTouchListener(new OnTouchListener() {
                private float startAngle;
                private boolean isDragging;
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    float touchX = event.getX();
                    float touchY = event.getY();
                    switch (event.getActionMasked()) {
                    case MotionEvent.ACTION_DOWN:
                        startAngle = touchAngle(touchX, touchY);
                        isDragging = isInDiscArea(touchX, touchY);
                        break;
                    case MotionEvent.ACTION_MOVE:
                        if (isDragging) {
                            float touchAngle = touchAngle(touchX, touchY);
                            float deltaAngle = (360 + touchAngle - startAngle + 180) % 360 - 180;
                            if (Math.abs(deltaAngle) > stepAngle) {
                                int offset = (int) deltaAngle / (int) stepAngle;
                                startAngle = touchAngle;
                                onRotate(offset);
                            }
                        }
                        break;
                    case MotionEvent.ACTION_UP:
                    case MotionEvent.ACTION_CANCEL:
                        isDragging = false;
                        break;
                    }
                    return true;
                }
            });
        }
    
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            centerX = getMeasuredWidth() / 2f;
            centerY = getMeasuredHeight() / 2f;
            super.onLayout(changed, l, t, r, b);
        }
    
        @SuppressLint("DrawAllocation")
        @Override
        protected void onDraw(Canvas canvas) {
            float radius = Math.min(getMeasuredWidth(), getMeasuredHeight()) / 2f;
            Paint paint = new Paint();
            paint.setDither(true);
            paint.setAntiAlias(true);
            paint.setStyle(Style.FILL);
            paint.setColor(0xFFFFFFFF);
            paint.setXfermode(null);
            LinearGradient linearGradient = new LinearGradient(
                radius, 0, radius, radius, 0xFFFFFFFF, 0xFFEAEAEA, Shader.TileMode.CLAMP);
            paint.setShader(linearGradient);
            canvas.drawCircle(centerX, centerY, maxCircle * radius, paint);
            paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
            canvas.drawCircle(centerX, centerY, minCircle * radius, paint);
            paint.setXfermode(null);
            paint.setShader(null);
            paint.setColor(0x15000000);
            for (int i = 0, n =  360 / (int) stepAngle; i < n; i++) {
                double rad = Math.toRadians((int) stepAngle * i);
                int startX = (int) (centerX + minCircle * radius * Math.cos(rad));
                int startY = (int) (centerY + minCircle * radius * Math.sin(rad));
                int stopX = (int) (centerX + maxCircle * radius * Math.cos(rad));
                int stopY = (int) (centerY + maxCircle * radius * Math.sin(rad));
                canvas.drawLine(startX, startY, stopX, stopY, paint);
            }
            super.onDraw(canvas);
        }
    
        /**
         * Define the step angle in degrees for which the
         * dial will call {@link #onRotate(int)} event
         * @param angle : angle between each position
         */
        public void setStepAngle(float angle) {
            stepAngle = Math.abs(angle % 360);
        }
    
        /**
         * Define the draggable disc area with relative circle radius
         * based on min(width, height) dimension (0 = center, 1 = border)
         * @param radius1 : internal or external circle radius
         * @param radius2 : internal or external circle radius
         */
        public void setDiscArea(float radius1, float radius2) {
            radius1 = Math.max(0, Math.min(1, radius1));
            radius2 = Math.max(0, Math.min(1, radius2));
            minCircle = Math.min(radius1, radius2);
            maxCircle = Math.max(radius1, radius2);
        }
    
        /**
         * Check if touch event is located in disc area
         * @param touchX : X position of the finger in this view
         * @param touchY : Y position of the finger in this view
         */
        private boolean isInDiscArea(float touchX, float touchY) {
            float dX2 = (float) Math.pow(centerX - touchX, 2);
            float dY2 = (float) Math.pow(centerY - touchY, 2);
            float distToCenter = (float) Math.sqrt(dX2 + dY2);
            float baseDist = Math.min(centerX, centerY);
            float minDistToCenter = minCircle * baseDist;
            float maxDistToCenter = maxCircle * baseDist;
            return distToCenter >= minDistToCenter && distToCenter <= maxDistToCenter;
        }
    
        /**
         * Compute a touch angle in degrees from center
         * North = 0, East = 90, West = -90, South = +/-180
         * @param touchX : X position of the finger in this view
         * @param touchY : Y position of the finger in this view
         * @return angle
         */
        private float touchAngle(float touchX, float touchY) {
            float dX = touchX - centerX;
            float dY = centerY - touchY;
            return (float) (270 - Math.toDegrees(Math.atan2(dY, dX))) % 360 - 180;
        }
    
        protected abstract void onRotate(int offset);
    
    }
    

    Use it :

    public class DialActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle state) {
            setContentView(new RelativeLayout(this) {
                private int value = 0;
                private TextView textView;
                {
                    addView(new DialView(getContext()) {
                        {
                            // a step every 20°
                            setStepAngle(20f);
                            // area from 30% to 90%
                            setDiscArea(.30f, .90f);
                        }
                        @Override
                        protected void onRotate(int offset) {
                            textView.setText(String.valueOf(value += offset));
                        }
                    }, new RelativeLayout.LayoutParams(0, 0) {
                        {
                            width = MATCH_PARENT;
                            height = MATCH_PARENT;
                            addRule(RelativeLayout.CENTER_IN_PARENT);
                        }
                    });
                    addView(textView = new TextView(getContext()) {
                        {
                            setText(Integer.toString(value));
                            setTextColor(Color.WHITE);
                            setTextSize(30);
                        }
                    }, new RelativeLayout.LayoutParams(0, 0) {
                        {
                            width = WRAP_CONTENT;
                            height = WRAP_CONTENT;
                            addRule(RelativeLayout.CENTER_IN_PARENT);
                        }
                    });
                }
            });
            super.onCreate(state);
        }
    
    }
    

    Result :

    result

提交回复
热议问题