I need to create a series of concentric ellipses (rings), and need to place user icons on the circumference of these ellipses. See the image below.
Till now I've drawn 3 elliptical concentric circles on canvas & placed user icons. I need user icons to be draggable across the rings.
Please suggest ways to implement this.
Since it appears you're already placing icons on the circumference of the rings, I assume you know how to do the math [but see edit] to determine the points along the circumference and are asking about the drag-and-drop.
You'll probably want to implement the icon movement using a drag-and-drop approach. Assuming that you keep the rings as a single image, then you will only have a single drop destination. You will then need to analyze the drop point mathematically [see edit] (by ascertaining its pixel color) to determine which ring the icon was dropped into. If you create separate views for the rings, then each one can be its own drop point. (You'll probably need to eventually work out how to redistribute the icons within each ring, but that's a different question.)
Here's some code that shows a minimal way to handle the drag-and-drop using a single image icon on a single view group (where you'd display your rings image).
package com.example.dragexample;
import android.app.Activity;
import android.content.ClipData;
import android.os.Bundle;
import android.util.Log;
import android.view.DragEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.DragShadowBuilder;
import android.view.View.OnDragListener;
import android.view.View.OnTouchListener;
import android.widget.ImageView;
public class MainActivity extends Activity {
static final String TAG = "DragActivity";
ImageView icon = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.rings).setOnDragListener(new OnDragListener() {
@Override
public boolean onDrag(View vw, DragEvent event) {
if (event.getAction() == DragEvent.ACTION_DROP) {
// Drop the icon and redisplay it:
icon.setX(event.getX());
icon.setY(event.getY());
icon.setVisibility(View.VISIBLE);
// Analyze the drop point mathematically (or perhaps get its pixel color)
// to determine which ring the icon has been dragged into and then take
// appropriate action.
int destRing = determineDestinationRing(event.getX(), event.getY());
}
return true;
}
});
icon = (ImageView) findViewById(R.id.icon);
icon.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View vw, MotionEvent event) {
Log.v(TAG, "Touch event " + event.getAction());
if (event.getActionMasked() == MotionEvent.ACTION_MOVE) {
Log.v(TAG, "Starting drag");
// Set up clip data (empty) and drag shadow objects and start dragging:
ClipData cd = ClipData.newPlainText("", "");
DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(vw);
vw.startDrag(cd, shadowBuilder, vw, 0);
vw.setVisibility(View.INVISIBLE);
}
return true;
}
});
}
public void resetImage(View vw) {
Log.v(TAG, "Resetting image position");
icon.setX(0f);
icon.setY(0f);
icon.setVisibility(View.VISIBLE);
}
}
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/rings"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:onClick="resetImage" >
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher" />
</FrameLayout>
To mathematically determine which ring the icon is dropped into, you could use something like the following, which loops through an implementation of the standard equation for an ellipse using hard-coded sizes for the axes. Note that a zero will be returned if an icon is dropped within the innermost 'me' ring. Also, this approach will be a bit more challenging in practice due to the fact that the on-screen ring sizes will likely be adjusted when the layout is rendered. In that case, the final sizes of the axes will need to be determined at run time.
// Axis values must be ascending order; ring 0 is 'me';
float a[] = {50, 100, 150, 200};
float b[] = {100, 200, 300, 400};
public int determineDestinationRing(float x, float y) {
// Check for inclusion within each ring:
for (int i = 0; i < a.length; i++) {
if (((x * x) / (a[i] * a[i]) + (y * y) / (b[i] * b[i])) <= 1)
return i;
}
return -1;
}
To solve the problem, you need to use the equation of ellipse:
(x/a)2 + (y/a)2 = 1 ,
where:
x,y are coordinates of any point on ellipse circumference
a,b are radius on x and y axis respectively.
When the user drags and drops the icon, if the center co-ordinates of the icon intersect with the ellipse circumference, then above formula should hold good. In that case you can place the icon on the ellipse. Since you have 3 ellipses, you will have to do this check couple of times, once each for the other 2 ellipses.
public class CustomView extends View {
// Other methods
public boolean onTouchEvent(MotionEvent e) {
int index = e.getActionIndex();
float x = e.getX(index);
float y = e.getY(index);
int a = this.getWidth()/2;
int b = this.getHeight()/2;
// x-a instead of x and y-b instead of y, to get the offset from centre of ellipse.
double result = Math.pow(((x-a)/a), 2) + Math.pow(((y-b)/b), 2);
Log.v(TAG, "(" + (x-a) + "/" + a + ")2 + (" + (y-b) + "/" + b + ")2 = " + result);
return true;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With