Hoa
Hoa

Reputation: 89

Create rotation wheel in android?

I need to implement a mini game as Roulette that user can press play to rotate and give user a random result.

May you give me any suggestion how to make a layout like following image? Thank you very much.

enter image description here

Upvotes: 4

Views: 10202

Answers (2)

OhhhThatVarun
OhhhThatVarun

Reputation: 4321

Here is the dialer image which I needed to rotate. Every section is divided by 45-degree angle

I needed to rotate this dialer by the user and here is the code for it. This code is heavily inspired by link and before reading my code you can read the explanation there.

currentAngle is the variable for keeping an eye on total angle dialer has been rotated. So for every rotation after 360 degree turns it will subtract 360 degrees from it so angle will always be in between 0-360 degree. You can check it in rotateDialer function. In onSingleTapConfirmed function, we are sending the coordinate of click(i.e x and y) then changing it to the angle saving it in clickedAngle. The dialer has 8 section divided by the 45-degree angle. And every section of the dialer has defined angle range. So it will subtract the currentAngle from every angle range(i.e 0-45,0-90..etc) so every section range is being changed real-time. E.g let the current angle of dialer is 20 degree in + direction so the angle range of the first section is now changed to (0-20) to (45-20) that is -20 to 25 degree.

import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.support.constraint.ConstraintLayout;
import android.support.constraint.Guideline;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.ImageView;

public class MainActivityNew extends AppCompatActivity {

private static Bitmap imageOriginal, imageScaled;
private static Matrix matrix;

private ImageView dialer;
private int dialerHeight, dialerWidth;

private GestureDetector detector;

// needed for detecting the inversed rotations
private boolean[] quadrantTouched;

private boolean allowRotating;

ImageView menu_circle;
Guideline menuWheelGuidline;

float currentAngle; //The current angle of the dialer
double clickedAngle; //The angle which user has clicked

@SuppressLint("ClickableViewAccessibility")
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main_new);
    setUpMenuWheel();

    // load the image only once
    if (imageOriginal == null) {
        imageOriginal = BitmapFactory.decodeResource(getResources(), R.drawable.menu_wheel);
    }
    // initialize the matrix only once
    if (matrix == null) {
        matrix = new Matrix();
    } else {
        // not needed, you can also post the matrix immediately to restore the old state
        matrix.reset();
    }
    detector = new GestureDetector(this, new MyGestureDetector());
    // there is no 0th quadrant, to keep it simple the first value gets ignored
    quadrantTouched = new boolean[] { false, false, false, false, false };
    allowRotating = true;
    dialer = findViewById(R.id.menu_wheel);
    dialer.setOnTouchListener(new MyOnTouchListener());
    dialer.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

        @Override
        public void onGlobalLayout() {
            // method called more than once, but the values only need to be initialized one time
            if (dialerHeight == 0 || dialerWidth == 0) {
                dialerHeight = dialer.getHeight();
                dialerWidth = dialer.getWidth();

                // resize
                Matrix resize = new Matrix();
                resize.postScale((float)Math.min(dialerWidth, dialerHeight) / (float)imageOriginal.getWidth(), (float)Math.min(dialerWidth, dialerHeight) / (float)imageOriginal.getHeight());
                imageScaled = Bitmap.createBitmap(imageOriginal, 0, 0, imageOriginal.getWidth(), imageOriginal.getHeight(), resize, false);

                // translate to the image view's center
                float translateX = dialerWidth / 2 - imageScaled.getWidth() / 2;
                float translateY = dialerHeight / 2 - imageScaled.getHeight() / 2;
                matrix.postTranslate(translateX, translateY);

                dialer.setImageBitmap(imageScaled);
                dialer.setImageMatrix(matrix);
            }
        }
    });
}

/**
 * Rotate the dialer.
 *
 * @param degrees The degrees, the dialer should get rotated.
 */
private void rotateDialer(float degrees) {
    if(Math.abs(currentAngle)/360>1){
        if(currentAngle>0){
            currentAngle =currentAngle-360+degrees;
        }else{
            currentAngle +=360+degrees;
        }
    }else{
        currentAngle=currentAngle+degrees;
    }
    matrix.postRotate(degrees, dialerWidth / 2, dialerHeight / 2);
    dialer.setImageMatrix(matrix);
    Log.d("Angle Sum",""+currentAngle);
}

/**
 * @return The angle of the unit circle with the image view's center
 */
private double getAngle(double xTouch, double yTouch) {

    double x = xTouch - (dialerWidth / 2d);
    double y = dialerHeight - yTouch - (dialerHeight / 2d);

    switch (getQuadrant(x, y)) {
        case 1:
            return Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI;
        case 2:
        case 3:
            return 180 - (Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI);
        case 4:
            return 360 + Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI;
        default:
            return 0;// ignore, does not happen
    }
}

/**
 * @return The selected quadrant.
 */
private static int getQuadrant(double x, double y) {
    if (x >= 0) {
        return y >= 0 ? 1 : 4;
    } else {
        return y >= 0 ? 2 : 3;
    }
}

/**
 * Simple implementation of an {@link View.OnTouchListener} for registering the dialer's touch events.
 */
private class MyOnTouchListener implements View.OnTouchListener {

    private double startAngle;

    @Override
    public boolean onTouch(View v, MotionEvent event) {

        switch (event.getAction()) {

            case MotionEvent.ACTION_DOWN:
                // reset the touched quadrants
                for (int i = 0; i < quadrantTouched.length; i++) {
                    quadrantTouched[i] = false;
                }
                allowRotating = false;
                startAngle = getAngle(event.getX(), event.getY());
                break;
            case MotionEvent.ACTION_MOVE:
                double currentAngle = getAngle(event.getX(), event.getY());
                rotateDialer((float) (startAngle - currentAngle));
                startAngle = currentAngle;
                break;
            case MotionEvent.ACTION_UP:
                allowRotating = true;
                break;
        }
        // set the touched quadrant to true
        quadrantTouched[getQuadrant(event.getX() - (dialerWidth / 2), dialerHeight - event.getY() - (dialerHeight / 2))] = true;
        detector.onTouchEvent(event);
        return true;
    }
}

/**
 * Simple implementation of a {@link GestureDetector.SimpleOnGestureListener} for detecting a fling event.
 */
private class MyGestureDetector extends GestureDetector.SimpleOnGestureListener {

    @Override
    public boolean onSingleTapConfirmed(MotionEvent e) {
        iconClicked(e.getX(),e.getY());
        return super.onSingleTapConfirmed(e);
    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {

        // get the quadrant of the start and the end of the fling
        int q1 = getQuadrant(e1.getX() - (dialerWidth / 2), dialerHeight - e1.getY() - (dialerHeight / 2));
        int q2 = getQuadrant(e2.getX() - (dialerWidth / 2), dialerHeight - e2.getY() - (dialerHeight / 2));

        // the inversed rotations
        if ((q1 == 2 && q2 == 2 && Math.abs(velocityX) < Math.abs(velocityY))
                || (q1 == 3 && q2 == 3)
                || (q1 == 1 && q2 == 3)
                || (q1 == 4 && q2 == 4 && Math.abs(velocityX) > Math.abs(velocityY))
                || ((q1 == 2 && q2 == 3) || (q1 == 3 && q2 == 2))
                || ((q1 == 3 && q2 == 4) || (q1 == 4 && q2 == 3))
                || (q1 == 2 && q2 == 4 && quadrantTouched[3])
                || (q1 == 4 && q2 == 2 && quadrantTouched[3])) {

            dialer.post(new FlingRunnable(-1 * (velocityX + velocityY)));
        } else {
            // the normal rotation
            dialer.post(new FlingRunnable(velocityX + velocityY));
        }
        return true;
    }
}
/**
 * A {@link Runnable} for animating the the dialer's fling.
 */
private class FlingRunnable implements Runnable {

    private float velocity;
    float totalAngle;

    public FlingRunnable(float velocity) {
        this.velocity = velocity;
    }

    @Override
    public void run() {
        if (Math.abs(velocity) > 5 && allowRotating) {
            rotateDialer(velocity / 75);
            totalAngle=totalAngle+velocity/75;
            velocity /= 1.0666F;
            // post this instance again
            dialer.post(this);
        }
    }
}

//To position the menu wheel
public void setUpMenuWheel(){
    DisplayMetrics displayMetrics = new DisplayMetrics();
    getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
    int width = displayMetrics.widthPixels;
    int height = displayMetrics.heightPixels;
    menu_circle = findViewById(R.id.menu_wheel);
    menu_circle.getLayoutParams().height=width;
    menuWheelGuidline = findViewById(R.id.menuWheelGuidline);
    ConstraintLayout.LayoutParams lp = (ConstraintLayout.LayoutParams) menuWheelGuidline.getLayoutParams();
    lp.guideBegin = height-(width/2);
    menuWheelGuidline.setLayoutParams(lp);
}

public void iconClicked(double x,double y){
    clickedAngle=getAngle(x,y);
    Log.d("Clicked angle",""+(getAngle(x,y)));
    if(clickedAngle>0-currentAngle && clickedAngle<45-currentAngle){
        Log.e("Icon 1","Clicked");
    }if(clickedAngle>45-currentAngle && clickedAngle<90-currentAngle){
        Log.e("Icon 2","Clicked");
    }if(clickedAngle>90-currentAngle && clickedAngle<135-currentAngle){
        Log.e("Icon 3","Clicked");
    }if(clickedAngle>135-currentAngle && clickedAngle<180-currentAngle){
        Log.e("Icon 4","Clicked");
    }if(clickedAngle>180-currentAngle && clickedAngle<225-currentAngle){
        Log.e("Icon 5","Clicked");
    }if(clickedAngle>225-currentAngle && clickedAngle<270-currentAngle){
        Log.e("Icon 6","Clicked");
    }if(clickedAngle>270-currentAngle && clickedAngle<315-currentAngle){
        Log.e("Icon 7","Clicked");
    }if(clickedAngle>315-currentAngle && clickedAngle<359-currentAngle){
        Log.e("Icon 8","Clicked");
    }
}
}

Upvotes: 3

Aditya Vyas-Lakhan
Aditya Vyas-Lakhan

Reputation: 13555

I suggest that you can use Wheel Menu

<com.anupcowkur.wheelmenu.WheelMenu
android:id="@+id/wheelMenu"
android:layout_width="300dp"
android:layout_height="300dp" />

https://github.com/anupcowkur/Android-Wheel-Menu

https://github.com/LukeDeighton/WheelView

https://github.com/hongyangAndroid/Android-CircleMenu

enter image description here

Upvotes: 6

Related Questions