Rotate circle drawn on canvas to point marked by touch

user7999116 :

I'm trying to get my app to display a circle that the user can rotate to a certain degree by touching and dragging the circle. Say the maximum the circle can be rotated is 60 degrees, either way.

I've extended the View class to create my custom View. I've drawn a circle on my canvas with the drawCircle() method. To rotate it, I know I'd have to do a canvas.rotate(), but I'm not sure what to fix the center as. How do I measure this angle? What do I rotate about?

public class DrawingView extends View {

    Paint paint;
    Canvas canvas;
    int originalX = 100;
    int originalY = 50;
    int radius = 75;
    // ... some code ...
    // paint, canvas initialised in constructors

    @Override
    protected void onDraw(Canvas canvas){
        canvas.drawCircle(originalX, originalY, radius, paint);
        // some more code ...
    }

   @Override
   public boolean onTouchEvent(MotionEvent event){
       int x = (int)event.getX();         
       int y = (int)event.getY();
       case MotionEvent.ACTION_DOWN:
           // Detect if touch in my circle
       break;             
       case MotionEvent.ACTION_MOVE: 
          double tx = x - originalX;
          double ty = y - originalY;                         
          double tlength = Math.sqrt(tx*tx + ty*ty);
          double angle = Math.acos(radius/tlength);
          double degrees = (angle * 180)/Math.PI;
          System.out.println("Degrees " + (angle*180)/Math.PI);
       break;
   }
}

The code I've displayed doesn't seem to give me a correct value. Like, if I touch and drag all the way to the end of the screen, it gives me a value around 70 degrees. I don't think I'm calculating the angle right, but I have no idea how to either.


I followed the selected answer's way of finding the angle. I then used canvas.rotate() before drawing the circle.

Tatarize :

I'm not sure I have your idea of how this should work down right. But the geometry there is certainly weird. tlength is the radius from the circle center to the fingerpress and you're doing an acos on it. But, that's wrong. acos is which values gives you the cos of that value. In theory that'll be a cosine wave of different angles. Which might be interesting but doesn't sound like what you want. It sounds like you want to call distance from center of 0 to be rotate by -60 degrees and radius rotate by 0 degrees and 2*radius as rotate +60 degrees. So just do that. You linearly interpolate the distance (60° * (distance/radius)) - 60° or whatever.

Though really it seems from a UI standpoint you'd be better off just letting the user change the actual angle on the circle by moving their finger to that angle. Then throw those values in an atan2() and limit it if it's beyond the permitted bounds.


There's many ways to do this. You can calculate the angle based on finger presses. So you can use the known points like center point and current polar coord. So if you have a current angle at distance of r the point at the other end is cos(x)*r, sin(y)*r if you have x and y of the where you want that end point then the distance from the center is sqrt(dx*dx + dy*dy) and the angle it's at is atan2(dy, dx). So you can go from polar to Cartesian with a bit of trig. If you want to call the difference between the center and the radius an angle you can linearly interpolate that.


Here's a quick rendering of a view that does it like that. There are other UI choices that could be done instead like not doing the actual angle but doing the change of the angle, so you're moving the circle like it's a knob or something.

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class RotateCircleView extends View {
    static final double TAU = Math.PI * 2;
    Paint paint;
    double angle = 0;
    double radius = 0;
    double ix = Double.NaN, iy = Double.NaN;

    public RotateCircleView(Context context) {
        super(context);
        init();
    }
    public RotateCircleView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }
    public RotateCircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
    public void init() {
        paint = new Paint();
        paint.setAlpha(100);
        paint.setStrokeWidth(20);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        ix = w / 2.0;
        iy = h / 2.0;
        radius = Math.min(ix, iy) / 2.0;
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (Double.isNaN(ix)) return;
        super.onDraw(canvas);
        canvas.save();
        canvas.rotate((float) ((TAU * -angle) / 360.0), (float) ix, (float) iy);
        canvas.drawCircle((float) ix, (float) iy, (float) radius, paint);
        double limit = TAU / 6.0;
        if (angle > limit) angle = limit;
        if (angle < -limit) angle = -limit;
        double tx = ix + Math.cos(angle) * radius;
        double ty = iy + Math.sin(angle) * radius;
        canvas.drawLine((float) ix, (float) iy, (float) tx, (float) ty, paint);
        canvas.restore();
    }

    public boolean onTouchEvent(MotionEvent event) {
        double mx = event.getX();
        double my = event.getY();
        double dx = mx - ix;
        double dy = my - iy;
        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                //radius = Math.sqrt(dx * dx + dy * dy);
                angle = Math.atan2(dy, dx);
                invalidate();
                break;
        }
        return true;
    }
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=167166&siteId=1