this is my current code
Path path_eclipse = new Path();
float radius = (float) (Math.sqrt(Math.pow(r.stopX - r.startX, 2.0f) + Math.pow(r.stopY - r.startY, 2.0f)) / 2.0f);
path_eclipse.addCircle(r.startX, r.startY, radius, Path.Direction.CCW);
canvas.drawPath(path_eclipse, paint);
with this code I am getting as output:
But I want to draw circle like this:
UPDATED Source code: This source code worked in my case
[SOLVED]
[OnDraw]
#Override
protected void onDraw(Canvas canvas) {
Path path_eclipse = new Path();
float centerX = (r.startX + r.stopX) /2;
float centerY = (r.startY + r.stopY) /2;
float radius = (float)Math.sqrt((r.stopX - r.startX)*(r.stopX - r.startX)+(r.stopY - r.startY)*(r.stopY - r.startY));
path_eclipse.addCircle(centerX, centerY, radius/2, Path.Direction.CCW);
canvas.drawPath(path_eclipse,paint);
}
[OnTouchEvent]
#Override
public boolean onTouchEvent(MotionEvent event) {
float eventX = event.getX();
float eventY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = eventX;
startY = eventY;
return true;
case MotionEvent.ACTION_MOVE:
stopX = eventX;
stopY = eventY;
break;
case MotionEvent.ACTION_UP:
stopX = eventX;
stopY = eventY;
break;
default:
return false;
}
invalidate();
return true;
}
The first two paramateres of addCircle are the x and y coordinates of the center. Assuming A and B are the furthest distance from each other on the circle you want, then the center should a point equidistant to both, hence:
float centerX = (pointA.x + pointB.x) /2
float centerY = (pointA.y + pointB.y) /2
And your radius should be, the distance between A and B, thus:
float radius = (Math.sqrt(Math.pow(x2−x1, 2) + Math.pow(y2−y1, 2))) / 2
Mid points
int mx=(r.stopX + r.startX)/2;
int my= (r.stopy+r.startY)/2;
Radius
float radius = Math.sqrt(Math.pow(r.stopX - my, 2)
+ Math.pow(r.stopY - my, 2));
Edited
I have used code below
public class CustomView extends View {
private Paint paint;
private Circle circle;
private List<Point> points;
public final int CIRCLE_BETWEEN_TWO_POINTS = 1;
private int viewType;
{
paint = new Paint();
points = new ArrayList<>();
}
public CustomView(Context context) {
super(context);
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onDraw(Canvas canvas) {
switch (viewType) {
case CIRCLE_BETWEEN_TWO_POINTS:
drawView(canvas);
break;
}
}
private void drawView(Canvas canvas){
for(Point point:points){
drawCircle(canvas,new Circle(point.x,point.y,10),false);
}
drawCircle(canvas,circle,true);
}
private void drawCircle(Canvas canvas,Circle circle, boolean isStroke){
paint.reset();
paint.setAntiAlias(true);
if(isStroke){
paint.setStrokeWidth(5);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
}else {
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.FILL);
}
canvas.drawCircle(circle.getX(), circle.getY(), circle.getRadius(), paint);
}
public void drawCircleBetweenTwoPoints(int x1, int y1, int x2, int y2) {
viewType = CIRCLE_BETWEEN_TWO_POINTS;
points.clear();
points.add(new Point(x1, y1));
points.add(new Point(x2, y2));
int mx = (x1 + x2) / 2;
int my = (y1 + y2) / 2;
double radius = Math.sqrt(Math.pow(x1 - mx, 2)
+ Math.pow(y1 - my, 2));
circle=new Circle(mx,my,(int)radius);
invalidate();
}
}
And called method as
customView.drawCircleBetweenTwoPoints(500,200,100,600);
and its working for me
Click to see output
If you want to draw circle like this, you must know 3 points, then calculate center with radius. Because two points cannot uniquely determine a circle. You can find only whole line of possible centers.
On the other hand, if we consider that two points are exactly opposite. Then you should calculate center by midpoint formula:
M = [(x1 + x2 / 2), (y1 + y2 / 2)]
Next, you can do the same thing as at first case. First, calculate radius and then draw circle.
Assuming, that A and B are exactly opposite to each other, the line segment defined by them will pass through the centre of the circle (C), and the latter will cut AB in half.
Thus:
You calculate then distance of the two points, and divide by two. So you'll have the radius.
The centre of the circle will be exactly halfway between the two: x=(x1+x2)/2 and y=(y1+y2)/2.
If they happen to be just random points, you can use one of these techniques:
Link
Link
In this case however, your problem is slightly tricky, as two points and a radius will not determine one circle unambiguously: the problem will have two solutions.
Related
I am learning Custom Views and succeeded in creating three circle and lines between them. How could I make those circle's draggable.
First of all I want to know that I click on inside the circle using onTouch() and then update these circle position accordingly.
MyDrawingView
public class CustomDrawing extends View {
private static final String TAG = "CustomDrawing";
private Paint circlePaint;
private Paint linePaint;
private Paint textPaint;
private int centerX,centerY;
private float circleSize = 80;
public CustomDrawing(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
setFocusable(true);
setFocusableInTouchMode(true);
setupPaint();
}
private void setupPaint() {
circlePaint = new Paint();
circlePaint.setColor(Color.BLACK);
circlePaint.setAntiAlias(true);
circlePaint.setStrokeWidth(5);
circlePaint.setStyle(Paint.Style.STROKE);
circlePaint.setStrokeJoin(Paint.Join.ROUND);
circlePaint.setStrokeCap(Paint.Cap.ROUND);
linePaint = new Paint();
linePaint.setColor(Color.WHITE);
linePaint.setAntiAlias(true);
linePaint.setStrokeWidth((float) 1.5);;
textPaint = new Paint();
textPaint.setColor(Color.WHITE);
textPaint.setTextSize(60);
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setFakeBoldText(true);
}
#Override
protected void onDraw(Canvas canvas) {
///super.onDraw(canvas);
centerX = canvas.getWidth()/2;
centerY = canvas.getHeight()/2;
//Top Left Circle
canvas.drawCircle(circleSize, circleSize, 80, circlePaint);
canvas.drawText("LC",circleSize,getyPositionOfText(circleSize,textPaint),textPaint);
//Center Circle
circlePaint.setColor(Color.GREEN);
canvas.drawCircle(centerX, centerY, circleSize, circlePaint);
////int yPos = (int) ((canvas.getHeight() / 2) - ((textPaint.descent() + textPaint.ascent()) / 2)) ;
//((textPaint.descent() + textPaint.ascent()) / 2) is the distance from the baseline to the center.
canvas.drawText("CC",centerX,getyPositionOfText(canvas.getHeight()/2,textPaint),textPaint);
///canvas.drawText("CC",50,50,20,20,textPaint);
//Bottom Right Circle
circlePaint.setColor(Color.BLACK);
canvas.drawCircle(canvas.getWidth() - circleSize, canvas.getHeight() - circleSize, 80, circlePaint);
//Center to Left TOP and Center to Right TOP LINE
canvas.drawLine(centerX,centerY,circleSize,circleSize,linePaint);//center to top left
canvas.drawLine(centerX,centerY,canvas.getWidth() - circleSize,circleSize,linePaint);//center to top right
//Center to Left BOTTOM and Center to Right BOTTOM LINE
linePaint.setColor(Color.BLACK);
canvas.drawLine(centerX,centerY, circleSize,
canvas.getHeight() - circleSize,linePaint);// center to bottom left
canvas.drawLine(centerX,centerY,canvas.getWidth() - circleSize,
canvas.getHeight() - circleSize,linePaint);// center to bottom right
linePaint.setColor(Color.WHITE);
canvas.drawLine(centerX,centerY,circleSize,canvas.getHeight()/2,linePaint);
linePaint.setColor(Color.BLACK);
canvas.drawLine(centerX,centerY,canvas.getWidth() - circleSize,canvas.getHeight()/2,linePaint);
//Left top to left bottom
canvas.drawLine(circleSize,circleSize,circleSize,canvas.getHeight() - circleSize,linePaint);
//Right t top to Right bottom
canvas.drawLine(canvas.getWidth() - circleSize,circleSize,canvas.getWidth() - circleSize,canvas.getHeight() - circleSize,linePaint);
linePaint.setColor(Color.GREEN);
canvas.drawLine(circleSize,circleSize,canvas.getWidth()-circleSize,circleSize,linePaint);
canvas.drawLine(circleSize,canvas.getHeight() -circleSize,canvas.getWidth()-circleSize,canvas.getHeight() -circleSize,linePaint);
}
private int getyPositionOfText(float yPositionOfText,Paint mPaint){
return (int) ((yPositionOfText) - ((mPaint.descent() + mPaint.ascent()) / 2)) ;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float pointX = event.getX();
float pointY = event.getY();
// Checks for the event that occurs
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
return true;
case MotionEvent.ACTION_MOVE:
break;
default:
return false;
}
// Force a view to draw again
postInvalidate();
return true;
}
}
Also give suggestion to improve..
To make a View draggable I use the below code..
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
dX = v.getX() - event.getRawX();
dY = v.getY() - event.getRawY();
break;
case MotionEvent.ACTION_POINTER_UP:
break;
case MotionEvent.ACTION_MOVE:
v.animate()
.x(event.getRawX() + dX)
.y(event.getRawY() + dY)
.setDuration(0)
.start();
break;
}
invalidate();//reDraw
return true;
}
The above code working fine for View. How could I use it for animating(Dragging) Circle?
And in order to detect any position inside circle...
Math.sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)) < r
It seems that you might have issues with handling of multi-touch / drawing. There's some usefull tutorials about it on Android Developer site and on Android Blog.
Based on this I was able to create an example which I think quite similar to that You're trying to achieve (without complete circle drawing - circles get generated by single touch):
public class CirclesDrawingView extends View {
private static final String TAG = "CirclesDrawingView";
/** Main bitmap */
private Bitmap mBitmap = null;
private Rect mMeasuredRect;
/** Stores data about single circle */
private static class CircleArea {
int radius;
int centerX;
int centerY;
CircleArea(int centerX, int centerY, int radius) {
this.radius = radius;
this.centerX = centerX;
this.centerY = centerY;
}
#Override
public String toString() {
return "Circle[" + centerX + ", " + centerY + ", " + radius + "]";
}
}
/** Paint to draw circles */
private Paint mCirclePaint;
private final Random mRadiusGenerator = new Random();
// Radius limit in pixels
private final static int RADIUS_LIMIT = 100;
private static final int CIRCLES_LIMIT = 3;
/** All available circles */
private HashSet<CircleArea> mCircles = new HashSet<CircleArea>(CIRCLES_LIMIT);
private SparseArray<CircleArea> mCirclePointer = new SparseArray<CircleArea>(CIRCLES_LIMIT);
/**
* Default constructor
*
* #param ct {#link android.content.Context}
*/
public CirclesDrawingView(final Context ct) {
super(ct);
init(ct);
}
public CirclesDrawingView(final Context ct, final AttributeSet attrs) {
super(ct, attrs);
init(ct);
}
public CirclesDrawingView(final Context ct, final AttributeSet attrs, final int defStyle) {
super(ct, attrs, defStyle);
init(ct);
}
private void init(final Context ct) {
// Generate bitmap used for background
mBitmap = BitmapFactory.decodeResource(ct.getResources(), R.drawable.up_image);
mCirclePaint = new Paint();
mCirclePaint.setColor(Color.BLUE);
mCirclePaint.setStrokeWidth(40);
mCirclePaint.setStyle(Paint.Style.FILL);
}
#Override
public void onDraw(final Canvas canv) {
// background bitmap to cover all area
canv.drawBitmap(mBitmap, null, mMeasuredRect, null);
for (CircleArea circle : mCircles) {
canv.drawCircle(circle.centerX, circle.centerY, circle.radius, mCirclePaint);
}
}
#Override
public boolean onTouchEvent(final MotionEvent event) {
boolean handled = false;
CircleArea touchedCircle;
int xTouch;
int yTouch;
int pointerId;
int actionIndex = event.getActionIndex();
// get touch event coordinates and make transparent circle from it
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
// it's the first pointer, so clear all existing pointers data
clearCirclePointer();
xTouch = (int) event.getX(0);
yTouch = (int) event.getY(0);
// check if we've touched inside some circle
touchedCircle = obtainTouchedCircle(xTouch, yTouch);
touchedCircle.centerX = xTouch;
touchedCircle.centerY = yTouch;
mCirclePointer.put(event.getPointerId(0), touchedCircle);
invalidate();
handled = true;
break;
case MotionEvent.ACTION_POINTER_DOWN:
Log.w(TAG, "Pointer down");
// It secondary pointers, so obtain their ids and check circles
pointerId = event.getPointerId(actionIndex);
xTouch = (int) event.getX(actionIndex);
yTouch = (int) event.getY(actionIndex);
// check if we've touched inside some circle
touchedCircle = obtainTouchedCircle(xTouch, yTouch);
mCirclePointer.put(pointerId, touchedCircle);
touchedCircle.centerX = xTouch;
touchedCircle.centerY = yTouch;
invalidate();
handled = true;
break;
case MotionEvent.ACTION_MOVE:
final int pointerCount = event.getPointerCount();
Log.w(TAG, "Move");
for (actionIndex = 0; actionIndex < pointerCount; actionIndex++) {
// Some pointer has moved, search it by pointer id
pointerId = event.getPointerId(actionIndex);
xTouch = (int) event.getX(actionIndex);
yTouch = (int) event.getY(actionIndex);
touchedCircle = mCirclePointer.get(pointerId);
if (null != touchedCircle) {
touchedCircle.centerX = xTouch;
touchedCircle.centerY = yTouch;
}
}
invalidate();
handled = true;
break;
case MotionEvent.ACTION_UP:
clearCirclePointer();
invalidate();
handled = true;
break;
case MotionEvent.ACTION_POINTER_UP:
// not general pointer was up
pointerId = event.getPointerId(actionIndex);
mCirclePointer.remove(pointerId);
invalidate();
handled = true;
break;
case MotionEvent.ACTION_CANCEL:
handled = true;
break;
default:
// do nothing
break;
}
return super.onTouchEvent(event) || handled;
}
/**
* Clears all CircleArea - pointer id relations
*/
private void clearCirclePointer() {
Log.w(TAG, "clearCirclePointer");
mCirclePointer.clear();
}
/**
* Search and creates new (if needed) circle based on touch area
*
* #param xTouch int x of touch
* #param yTouch int y of touch
*
* #return obtained {#link CircleArea}
*/
private CircleArea obtainTouchedCircle(final int xTouch, final int yTouch) {
CircleArea touchedCircle = getTouchedCircle(xTouch, yTouch);
if (null == touchedCircle) {
touchedCircle = new CircleArea(xTouch, yTouch, mRadiusGenerator.nextInt(RADIUS_LIMIT) + RADIUS_LIMIT);
if (mCircles.size() == CIRCLES_LIMIT) {
Log.w(TAG, "Clear all circles, size is " + mCircles.size());
// remove first circle
mCircles.clear();
}
Log.w(TAG, "Added circle " + touchedCircle);
mCircles.add(touchedCircle);
}
return touchedCircle;
}
/**
* Determines touched circle
*
* #param xTouch int x touch coordinate
* #param yTouch int y touch coordinate
*
* #return {#link CircleArea} touched circle or null if no circle has been touched
*/
private CircleArea getTouchedCircle(final int xTouch, final int yTouch) {
CircleArea touched = null;
for (CircleArea circle : mCircles) {
if ((circle.centerX - xTouch) * (circle.centerX - xTouch) + (circle.centerY - yTouch) * (circle.centerY - yTouch) <= circle.radius * circle.radius) {
touched = circle;
break;
}
}
return touched;
}
#Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mMeasuredRect = new Rect(0, 0, getMeasuredWidth(), getMeasuredHeight());
}
}
Activity contains only setContentView(R.layout.main) there main.xml is the following:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:id="#+id/scroller">
<com.example.TestApp.CirclesDrawingView
android:layout_width="match_parent"
android:layout_height="match_parent" />
Guys I know How to draw circle in android..But what I need is using onTouch method to rotate that circle depending on the user hand movement on that circle. Please help.
public class MainActivity extends Activity {
public class SampleView extends View {
Paint mPaint = new Paint();
private Animation anim;
public SampleView(Context context) {
super(context);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setStrokeWidth(10);
mPaint.setColor(Color.RED);
}
private void createAnimation(Canvas canvas) {
anim = new RotateAnimation(0, 360, getWidth()/2, getHeight()/2);
anim.setRepeatMode(Animation.RESTART);
anim.setRepeatCount(Animation.INFINITE);
anim.setDuration(10000L);
startAnimation(anim);
}
protected void onDraw(Canvas canvas) {
int cx = getWidth()/2; // x-coordinate of center of the screen
int cy = getHeight()/2; // y-coordinate of the center of the screen
// Starts the animation to rotate the circle.
if (anim == null)
createAnimation(canvas);
canvas.drawCircle(cx, cy, 150, mPaint); // drawing the circle.
}
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new SampleView(this));
}
}
First, we have to determine the angle between old and new position of circle (as below figure).
cos a = uv / (|u|*|v|)
The simple equation may be
double tx = touch_x - center_x, ty = touch_y - center_y;
double t_length = Math.sqrt(tx*tx + ty*ty);
double angle = Math.acos(ty / t_length);
Then to rotate your Canvas. you can use the code
canvas.rotate(angle);
Or you can use another method as
public int touch_x,touch_y,cx,cy;
public float angle=0;
#Override
public boolean onTouchEvent(MotionEvent event) {
touch_x = (int)event.getX();
touch_y = (int)event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
}
//double tx = touch_x - cx, ty = touch_y - cy;
// double t_length = Math.sqrt(tx*tx + ty*ty);
//angle = (float) Math.acos(ty / t_length);
double dx = touch_x - cx;
// Minus to correct for coord re-mapping
double dy = -touch_y - cy;
double inRads = Math.atan2(dy,dx);
// We need to map to coord system when 0 degree is at 3 O'clock, 270 at 12 O'clock
if (inRads < 0)
inRads = Math.abs(inRads);
else
inRads = 2*Math.PI - inRads;
angle= (float) Math.toDegrees(inRads);
return false;
}
I am trying to make each slice of the pie a button. The pie is a bunch of vector drawables in an image view. I don't necessarily need the actual pie slices to be clicked. I was thinking of using Path to draw a transparent shape and place it on top and make that the button, but from what I understand, drawables aren't clickable.
I read one blog post that apparently used paths to make a custom shaped image view, and I know image views are clickable, but it seems like with the implementation in the blog post the image views are still rectangular, but the bitmaps the blogger was using in the example were just trimmed to a custom shape inside the image view. This is the post I'm referring to: http://www.androidhub4you.com/2014/10/android-custom-shape-imageview-rounded.html
Please explain this to me like a five year old. I'm relatively new to programming. Were it not for Android Studio's automatic everything, I would not be here.
Thank you.
You can just using drawArc and drawCircle to draw a radial menu, and using distance between touch point and center point and angle to detect which slice is currently being click. I wrote a Sample for you:
public class RadioButtons extends View {
//the number of slice
private int mSlices = 6;
//the angle of each slice
private int degreeStep = 360 / mSlices;
private int quarterDegreeMinus = -90;
private float mOuterRadius;
private float mInnerRadius;
//using radius square to prevent square root calculation
private float outerRadiusSquare;
private float innerRadiusSquare;
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private RectF mSliceOval = new RectF();
private static final double quarterCircle = Math.PI / 2;
private float innerRadiusRatio = 0.3F;
//color for your slice
private int[] colors = new int[]{Color.GREEN, Color.GRAY, Color.BLUE, Color.CYAN, Color.DKGRAY, Color.RED};
private int mCenterX;
private int mCenterY;
private OnSliceClickListener mOnSliceClickListener;
private int mTouchSlop;
private boolean mPressed;
private float mLatestDownX;
private float mLatestDownY;
public interface OnSliceClickListener{
void onSlickClick(int slicePosition);
}
public RadioButtons(Context context){
this(context, null);
}
public RadioButtons(Context context, AttributeSet attrs){
this(context, attrs, 0);
}
public RadioButtons(Context context, AttributeSet attrs, int defStyle){
super(context, attrs, defStyle);
ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
mTouchSlop = viewConfiguration.getScaledTouchSlop();
mPaint.setStrokeWidth(10);
}
public void setOnSliceClickListener(OnSliceClickListener onSliceClickListener){
mOnSliceClickListener = onSliceClickListener;
}
#Override
public void onSizeChanged(int w, int h, int oldw, int oldh){
mCenterX = w / 2;
mCenterY = h / 2;
mOuterRadius = mCenterX > mCenterY ? mCenterY : mCenterX;
mInnerRadius = mOuterRadius * innerRadiusRatio;
outerRadiusSquare = mOuterRadius * mOuterRadius;
innerRadiusSquare = mInnerRadius * mInnerRadius;
mSliceOval.left = mCenterX - mOuterRadius;
mSliceOval.right = mCenterX + mOuterRadius;
mSliceOval.top = mCenterY - mOuterRadius;
mSliceOval.bottom = mCenterY + mOuterRadius;
}
#Override
public boolean onTouchEvent(MotionEvent event){
float currX = event.getX();
float currY = event.getY();
switch(event.getActionMasked()){
case MotionEvent.ACTION_DOWN:
mLatestDownX = currX;
mLatestDownY = currY;
mPressed = true;
break;
case MotionEvent.ACTION_MOVE:
if(Math.abs(currX - mLatestDownX) > mTouchSlop || Math.abs(currY - mLatestDownY) > mTouchSlop) mPressed = false;
break;
case MotionEvent.ACTION_UP:
if(mPressed){
int dx = (int) currX - mCenterX;
int dy = (int) currY - mCenterY;
int distanceSquare = dx * dx + dy * dy;
//if the distance between touchpoint and centerpoint is smaller than outerRadius and longer than innerRadius, then we're in the clickable area
if(distanceSquare > innerRadiusSquare && distanceSquare < outerRadiusSquare){
//get the angle to detect which slice is currently being click
double angle = Math.atan2(dy, dx);
if(angle >= -quarterCircle && angle < 0){
angle += quarterCircle;
}else if(angle >= -Math.PI && angle < -quarterCircle){
angle += Math.PI + Math.PI + quarterCircle;
}else if(angle >= 0 && angle < Math.PI){
angle += quarterCircle;
}
double rawSliceIndex = angle / (Math.PI * 2) * mSlices;
if(mOnSliceClickListener != null){
mOnSliceClickListener.onSlickClick((int) rawSliceIndex);
}
}
}
break;
}
return true;
}
#Override
public void onDraw(Canvas canvas){
int startAngle = quarterDegreeMinus;
//draw slice
for(int i = 0; i < mSlices; i++){
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(colors[i % colors.length]);
canvas.drawArc(mSliceOval, startAngle, degreeStep, true, mPaint);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.WHITE);
canvas.drawArc(mSliceOval, startAngle, degreeStep, true, mPaint);
startAngle += degreeStep;
}
//draw center circle
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.BLACK);
canvas.drawCircle(mCenterX, mCenterY, mInnerRadius, mPaint);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.WHITE);
canvas.drawCircle(mCenterX, mCenterY, mInnerRadius, mPaint);
}
}
I am trying to make each slice of the pie a button. The pie is a bunch of vector drawables in an image view. I don't necessarily need the actual pie slices to be clicked. I was thinking of using Path to draw a transparent shape and place it on top and make that the button, but from what I understand, drawables aren't clickable.
I read one blog post that apparently used paths to make a custom shaped image view, and I know image views are clickable, but it seems like with the implementation in the blog post the image views are still rectangular, but the bitmaps the blogger was using in the example were just trimmed to a custom shape inside the image view. This is the post I'm referring to: http://www.androidhub4you.com/2014/10/android-custom-shape-imageview-rounded.html
Please explain this to me like a five year old. I'm relatively new to programming. Were it not for Android Studio's automatic everything, I would not be here.
Thank you.
You can just using drawArc and drawCircle to draw a radial menu, and using distance between touch point and center point and angle to detect which slice is currently being click. I wrote a Sample for you:
public class RadioButtons extends View {
//the number of slice
private int mSlices = 6;
//the angle of each slice
private int degreeStep = 360 / mSlices;
private int quarterDegreeMinus = -90;
private float mOuterRadius;
private float mInnerRadius;
//using radius square to prevent square root calculation
private float outerRadiusSquare;
private float innerRadiusSquare;
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private RectF mSliceOval = new RectF();
private static final double quarterCircle = Math.PI / 2;
private float innerRadiusRatio = 0.3F;
//color for your slice
private int[] colors = new int[]{Color.GREEN, Color.GRAY, Color.BLUE, Color.CYAN, Color.DKGRAY, Color.RED};
private int mCenterX;
private int mCenterY;
private OnSliceClickListener mOnSliceClickListener;
private int mTouchSlop;
private boolean mPressed;
private float mLatestDownX;
private float mLatestDownY;
public interface OnSliceClickListener{
void onSlickClick(int slicePosition);
}
public RadioButtons(Context context){
this(context, null);
}
public RadioButtons(Context context, AttributeSet attrs){
this(context, attrs, 0);
}
public RadioButtons(Context context, AttributeSet attrs, int defStyle){
super(context, attrs, defStyle);
ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
mTouchSlop = viewConfiguration.getScaledTouchSlop();
mPaint.setStrokeWidth(10);
}
public void setOnSliceClickListener(OnSliceClickListener onSliceClickListener){
mOnSliceClickListener = onSliceClickListener;
}
#Override
public void onSizeChanged(int w, int h, int oldw, int oldh){
mCenterX = w / 2;
mCenterY = h / 2;
mOuterRadius = mCenterX > mCenterY ? mCenterY : mCenterX;
mInnerRadius = mOuterRadius * innerRadiusRatio;
outerRadiusSquare = mOuterRadius * mOuterRadius;
innerRadiusSquare = mInnerRadius * mInnerRadius;
mSliceOval.left = mCenterX - mOuterRadius;
mSliceOval.right = mCenterX + mOuterRadius;
mSliceOval.top = mCenterY - mOuterRadius;
mSliceOval.bottom = mCenterY + mOuterRadius;
}
#Override
public boolean onTouchEvent(MotionEvent event){
float currX = event.getX();
float currY = event.getY();
switch(event.getActionMasked()){
case MotionEvent.ACTION_DOWN:
mLatestDownX = currX;
mLatestDownY = currY;
mPressed = true;
break;
case MotionEvent.ACTION_MOVE:
if(Math.abs(currX - mLatestDownX) > mTouchSlop || Math.abs(currY - mLatestDownY) > mTouchSlop) mPressed = false;
break;
case MotionEvent.ACTION_UP:
if(mPressed){
int dx = (int) currX - mCenterX;
int dy = (int) currY - mCenterY;
int distanceSquare = dx * dx + dy * dy;
//if the distance between touchpoint and centerpoint is smaller than outerRadius and longer than innerRadius, then we're in the clickable area
if(distanceSquare > innerRadiusSquare && distanceSquare < outerRadiusSquare){
//get the angle to detect which slice is currently being click
double angle = Math.atan2(dy, dx);
if(angle >= -quarterCircle && angle < 0){
angle += quarterCircle;
}else if(angle >= -Math.PI && angle < -quarterCircle){
angle += Math.PI + Math.PI + quarterCircle;
}else if(angle >= 0 && angle < Math.PI){
angle += quarterCircle;
}
double rawSliceIndex = angle / (Math.PI * 2) * mSlices;
if(mOnSliceClickListener != null){
mOnSliceClickListener.onSlickClick((int) rawSliceIndex);
}
}
}
break;
}
return true;
}
#Override
public void onDraw(Canvas canvas){
int startAngle = quarterDegreeMinus;
//draw slice
for(int i = 0; i < mSlices; i++){
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(colors[i % colors.length]);
canvas.drawArc(mSliceOval, startAngle, degreeStep, true, mPaint);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.WHITE);
canvas.drawArc(mSliceOval, startAngle, degreeStep, true, mPaint);
startAngle += degreeStep;
}
//draw center circle
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.BLACK);
canvas.drawCircle(mCenterX, mCenterY, mInnerRadius, mPaint);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.WHITE);
canvas.drawCircle(mCenterX, mCenterY, mInnerRadius, mPaint);
}
}
I have a image view (example: a world map) on top of which I have few location points. Location points are image buttons with just a square image and a text written below the square. I have a custom relative layout that handles touch to rotate the globe in a circular fashion.
Now, how to keep the text of the image button to remain parallel to x-axis or in other words, parallel to the width of the screen when I touch and rotate the globe? The image button rotates at an angle on touch and rotate of the globe.
Could anyone provide with the animation required to keep the image button text always parallel to the x-axis on touch and rotation of the background globe? A pointer I had was to move the image button in a small circular fashion (in a invisible circular path) so that the text remains always parallel to the width of the screen. A small drawback with this approach is that the button will move slightly in a circular fashion from its original point of location.
Can anyone help with the Android animation to move the image button on a small circular path on top of a image view?
My layout is as below
`
<com.mycompany.view.RotateRelativeLayout
android:id="#+id/test_relative_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#id/test_bottom_bar"
android:layout_marginTop="#dimen/test_margin_top"
android:layout_centerInParent="true">
<ImageView
android:id="#+id/test_globe"
android:layout_width="#dimen/test_layout_width"
android:layout_height="#dimen/test_layout_height" />
<FrameLayout
android:id="#+id/test_frame_layout"
android:layout_width="#dimen/test_layout_width"
android:layout_height="#dimen/test_layout_height">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageButton
android:id="#+id/test_button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#null"/>
<ImageButton
android:id="#+id/test_button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/test_button1"
android:background="#null"/>
</RelativeLayout>
</FrameLayout>
</com.mycompany.view.RotateRelativeLayout>
`
And the custom relative layout which I am using to touch and rotate the globe is as follows
`
public class RotateRelativeLayout extends RelativeLayout {
private float mCenterX, mCenterY;
private float direction = 0;
private float sX, sY;
private float startDirection = 0;
public RotateRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
private double angleBetween2Lines(float centerX, float centerY, float x1,
float y1, float x2, float y2) {
double angle1 = Math.atan2(y1 - centerY, x1 - centerX);
double angle2 = Math.atan2(y2 - centerY, x2 - centerX);
return angle1 - angle2;
}
private void touchStart(float x, float y) {
mCenterX = this.getWidth() / 2;
mCenterY = this.getHeight() / 2;
sX = x;
sY = y;
}
private void touchMove(float x, float y) {
// this calculates the angle the image rotate
float angle = (float) angleBetween2Lines(mCenterX, mCenterY, sX, sY, x, y);
direction = (float) Math.toDegrees(angle) * -1 + startDirection;
}
#Override
protected void dispatchDraw(Canvas canvas) {
canvas.save();
setRotation(direction);
super.dispatchDraw(canvas);
canvas.restore();
invalidate();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// record the start position of finger
touchStart(x, y);
break;
case MotionEvent.ACTION_MOVE:
// update image angle
touchMove(x, y);
break;
case MotionEvent.ACTION_UP:
startDirection = direction;
break;
}
return true;
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
onTouchEvent(event);
return super.onInterceptTouchEvent(event);
}
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
onTouchEvent(event);
return super.dispatchTouchEvent(event);
}
}
`
So, essentially my relative layout should respond to touch events and animate the image button. I have attempted here but this doesn't work
`
final RotateRelativeLayout rotateRelativeLayout = (RotateRelativeLayout) view.findViewById(R.id.test_relative_layout);
rotateRelativeLayout.setOnTouchListener(new View.OnTouchListener() {
private float mCenterX = rotateRelativeLayout.getWidth() / 2.0f;
private float mCenterY = rotateRelativeLayout.getHeight() / 2.0f;
private void animate(View view, double fromDegrees, double toDegrees, long duration) {
final RotateAnimation rotate = new RotateAnimation((float) fromDegrees, (float) toDegrees,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
rotate.setDuration(duration);
rotate.setFillEnabled(true);
rotate.setFillAfter(true);
rotate.setInterpolator(new LinearInterpolator());
rotate.setRepeatCount(1);
view.startAnimation(rotate);
}
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
float x = motionEvent.getX();
float y = motionEvent.getY();
final ImageButton imageButton = (ImageButton) view.findViewById(R.id.test_button1);
double angle1 = 0, angle2 = 0;
switch(motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
angle1 = Math.toDegrees(Math.atan2(y - mCenterY, x - mCenterX));
break;
case MotionEvent.ACTION_MOVE:
angle2 = Math.toDegrees(Math.atan2(y - mCenterY, x - mCenterX));
animate(imageButton, -angle1, -(angle2-angle1), 0);
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
});
`