package com.smartandroid.sa.view;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
public class WheelMenu extends ImageView {
private Bitmap imageOriginal, imageScaled; //variables for original and re-sized image
private Matrix matrix; //Matrix used to perform rotations
private int wheelHeight, wheelWidth; //height and width of the view
private int top; //the current top of the wheel (calculated in
// wheel divs)
private double totalRotation; //variable that counts the total rotation
// during a given rotation of the wheel by the
// user (from ACTION_DOWN to ACTION_UP)
private int divCount; //no of divisions in the wheel
private int divAngle; //angle of each division
private int selectedPosition; //the section currently selected by the user.
private boolean snapToCenterFlag = true; //variable that determines whether to snap the
// wheel to the center of a div or not
private Context context;
private WheelChangeListener wheelChangeListener;
public WheelMenu(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
//initializations
private void init(Context context) {
this.context = context;
this.setScaleType(ScaleType.MATRIX);
selectedPosition = 0;
// initialize the matrix only once
if (matrix == null) {
matrix = new Matrix();
} else {
matrix.reset();
}
//touch events listener
this.setOnTouchListener(new WheelTouchListener());
}
/**
* Add a new listener to observe user selection changes.
*
* @param wheelChangeListener
*/
public void setWheelChangeListener(WheelChangeListener wheelChangeListener) {
this.wheelChangeListener = wheelChangeListener;
}
/**
* Returns the position currently selected by the user.
*
* @return the currently selected position between 1 and divCount.
*/
public int getSelectedPosition() {
return selectedPosition;
}
/**
* Set no of divisions in the wheel menu.
*
* @param divCount no of divisions.
*/
public void setDivCount(int divCount) {
this.divCount = divCount;
divAngle = 360 / divCount;
totalRotation = -1 * (divAngle / 2);
}
/**
* Set the snap to center flag. If true, wheel will always snap to center of current section.
*
* @param snapToCenterFlag
*/
public void setSnapToCenterFlag(boolean snapToCenterFlag) {
this.snapToCenterFlag = snapToCenterFlag;
}
/**
* Set a different top position. Default top position is 0.
* Should be set after {#setDivCount(int) setDivCount} method and the value should be greater
* than 0 and lesser
* than divCount, otherwise the provided value will be ignored.
*
* @param newTopDiv
*/
public void setAlternateTopDiv(int newTopDiv) {
if (newTopDiv < 0 || newTopDiv >= divCount)
return;
else
top = newTopDiv;
selectedPosition = top;
}
/**
* Set the wheel image.
*
* @param drawableId the id of the drawable to be used as the wheel image.
*/
public void setWheelImage(int drawableId) {
imageOriginal = BitmapFactory.decodeResource(context.getResources(), drawableId);
}
/*
* We need this to get the dimensions of the view. Once we get those,
* We can scale the image to make sure it's proper,
* Initialize the matrix and align it with the views center.
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// method called multiple times but initialized just once
if (wheelHeight == 0 || wheelWidth == 0) {
wheelHeight = h;
wheelWidth = w;
// resize the image
Matrix resize = new Matrix();
resize.postScale((float) Math.min(wheelWidth, wheelHeight) / (float) imageOriginal
.getWidth(), (float) Math.min(wheelWidth,
wheelHeight) / (float) imageOriginal.getHeight());
imageScaled = Bitmap.createBitmap(imageOriginal, 0, 0, imageOriginal.getWidth(),
imageOriginal.getHeight(), resize, false);
// translate the matrix to the image view's center
float translateX = wheelWidth / 2 - imageScaled.getWidth() / 2;
float translateY = wheelHeight / 2 - imageScaled.getHeight() / 2;
matrix.postTranslate(translateX, translateY);
WheelMenu.this.setImageBitmap(imageScaled);
WheelMenu.this.setImageMatrix(matrix);
}
}
/**
* get the angle of a touch event.
*/
private double getAngle(double x, double y) {
x = x - (wheelWidth / 2d);
y = wheelHeight - y - (wheelHeight / 2d);
switch (getQuadrant(x, y)) {
case 1:
return Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI;
case 2:
return 180 - Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI;
case 3:
return 180 + (-1 * 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;
}
}
/**
* get the quadrant of the wheel which contains the touch point (x,y)
*
* @return quadrant 1,2,3 or 4
*/
private static int getQuadrant(double x, double y) {
if (x >= 0) {
return y >= 0 ? 1 : 4;
} else {
return y >= 0 ? 2 : 3;
}
}
/**
* rotate the wheel by the given angle
*
* @param degrees
*/
private void rotateWheel(float degrees) {
matrix.postRotate(degrees, wheelWidth / 2, wheelHeight / 2);
WheelMenu.this.setImageMatrix(matrix);
//add the rotation to the total rotation
totalRotation = totalRotation + degrees;
}
/**
* Interface to to observe user selection changes.
*/
public interface WheelChangeListener {
/**
* Called when user selects a new position in the wheel menu.
*
* @param selectedPosition the new position selected.
*/
public void onSelectionChange(int selectedPosition);
}
//listener for touch events on the wheel
private class WheelTouchListener implements View.OnTouchListener {
private double startAngle;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//get the start angle for the current move event
startAngle = getAngle(event.getX(), event.getY());
break;
case MotionEvent.ACTION_MOVE:
//get the current angle for the current move event
double currentAngle = getAngle(event.getX(), event.getY());
//rotate the wheel by the difference
rotateWheel((float) (startAngle - currentAngle));
//current angle becomes start angle for the next motion
startAngle = currentAngle;
break;
case MotionEvent.ACTION_UP:
//get the total angle rotated in 360 degrees
totalRotation = totalRotation % 360;
//represent total rotation in positive value
if (totalRotation < 0) {
totalRotation = 360 + totalRotation;
}
//calculate the no of divs the rotation has crossed
int no_of_divs_crossed = (int) ((totalRotation) / divAngle);
//calculate current top
top = (divCount + top - no_of_divs_crossed) % divCount;
//for next rotation, the initial total rotation will be the no of degrees
// inside the current top
totalRotation = totalRotation % divAngle;
//snapping to the top's center
if (snapToCenterFlag) {
//calculate the angle to be rotated to reach the top's center.
double leftover = divAngle / 2 - totalRotation;
rotateWheel((float) (leftover));
//re-initialize total rotation
totalRotation = divAngle / 2;
}
//set the currently selected option
if (top == 0) {
selectedPosition = divCount - 1;//loop around the array
} else {
selectedPosition = top - 1;
}
if (wheelChangeListener != null) {
wheelChangeListener.onSelectionChange(selectedPosition);
}
break;
}
return true;
}
}
}