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

前端 未结 10 2047
北恋
北恋 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:02

    I wrote this custom FrameLayout to detect circular movement around its center point. I'm using orientation of three points on a plane and the angle between them to determine when the user has made half a circle in one direction and then completes it in the same.

    public class CircularDialView extends FrameLayout implements OnTouchListener {
        private TextView counter;
        private int count = 50;
    
        private PointF startTouch;
        private PointF currentTouch;
        private PointF center;
        private boolean turning;
        private boolean switched = false;
    
        public enum RotationOrientation {
            CW, CCW, LINEAR;
        }
        private RotationOrientation lastRotatationDirection;
    
        public CircularDialView(Context context) {
            super(context);
            init();
        }
    
        public CircularDialView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        public CircularDialView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init();
        }
    
        private void init() {
            this.startTouch = new PointF();
            this.currentTouch = new PointF();
            this.center = new PointF();
            this.turning = false;
    
            this.setBackgroundResource(R.drawable.dial);
            this.counter = new TextView(getContext());
            this.counter.setTextSize(20);
            FrameLayout.LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
            params.gravity = Gravity.CENTER;
            addView(this.counter, params);
    
            updateCounter();
            this.setOnTouchListener(this);
        }
    
        private void updateCounter() {
            this.counter.setText(Integer.toString(count));
        }
    
        // need to keep the view square
        @Override
        public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
            super.onMeasure(widthMeasureSpec, widthMeasureSpec);
            center.set(getWidth()/2, getWidth()/2);
        }
    
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN: {
                    startTouch.set(event.getX(), event.getY());
                    turning = true;
                    return true; 
                }
                case MotionEvent.ACTION_MOVE: {
                    if(turning) {
                        currentTouch.set(event.getX(), event.getY());
                        RotationOrientation turningDirection = getOrientation(center, startTouch, currentTouch);
    
                        if (lastRotatationDirection != turningDirection) {
                            double angle = getRotationAngle(center, startTouch, currentTouch);
                            Log.d ("Angle", Double.toString(angle));
                            // the touch event has switched its orientation 
                            // and the current touch point is close to the start point
                            // a full cycle has been made
                            if (switched && angle < 10) {
                                if (turningDirection == RotationOrientation.CCW) {
                                    count--;
                                    updateCounter();
                                    switched = false;
                                }
                                else if (turningDirection == RotationOrientation.CW) {
                                    count++;
                                    updateCounter();
                                    switched = false;
                                }
                            }
                            // checking if the angle is big enough is needed to prevent
                            // the user from switching from the start point only
                            else if (!switched && angle > 170) {
                                switched = true;
                            }
                        }
    
                        lastRotatationDirection = turningDirection;
                        return true;
                    }
                }
    
                case MotionEvent.ACTION_CANCEL:
                case MotionEvent.ACTION_UP: {
                    turning  = false;
                    return true;
                }
            }
    
            return false;
        }
    
    
        // checks the orientation of three points on a plane
        private RotationOrientation getOrientation(PointF a, PointF b, PointF c){
            double face = a.x * b.y + b.x * c.y + c.x * a.y - (c.x * b.y + b.x * a.y + a.x * c.y);
            if (face > 0)
                 return RotationOrientation.CW;
            else if (face < 0)
                 return RotationOrientation.CCW;
            else return RotationOrientation.LINEAR;
        }
    
        // using dot product to calculate the angle between the vectors ab and ac
        public double getRotationAngle(PointF a, PointF b, PointF c){
            double len1 = dist (a, b);
            double len2 = dist (a, c);
            double product = (b.x - a.x) * (c.x - a.x) + (b.y - a.y) * (c.y - a.y);
    
            return Math.toDegrees(Math.acos(product / (len1 * len2)));
        }
    
        // calculates the distance between two points on a plane
        public double dist (PointF a, PointF b) {
            return Math.sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
        }
    }
    

提交回复
热议问题