Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
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).
MainActivity.java
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);
}
}
activity_main.xml
<?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>
Edit 1:
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;
}
}
I would do it like this. First of all we need to detect which icon is touched on TOUCH_DOWN event. This can be simply done by comparing touch point and icons coordinates. Once the closest icon is found, we should also know which ellipse this icon belongs to, meaning we know both the horizontal and the vertical radiuses of this ellipse. We also know the coordinates of the center of the ellipses.
Then, here is the idea of what we are going to do. We will calculate an angle between the line drawn through the touch point and the center of the ellipses and the horizontal line drawn through the center.
o <- touch point
/
/ \ <- angle
center-> o----- <- horizontal line
Now, after knowing the angle, we will use equation of a circle adjusted to ellipse, to recalculate new icon's coordinates to stick our icon exactly to that ellipse. Let's go to the algorithm.
To detect touch angle, we will use atan2 function.
double atan2Angle = Math.atan2(touchY - centerY, touchX - centerX);
This will give us following values depending on the angle.
-π
-2*π -0
o <- center
2*π 0
π
To be able to use this angle in equation of a circle we need to convert it into more traditional presentation like the one below.
270°
180° o 0°/360°
90°
It can be done like this.
float angleFactor = (float) (atan2Angle / 2 * Math.PI);
if (angleFactor < 0) {
angleFactor += 1f;
}
float touchAngle = angleFactor * 360f;
if (angle < 0) {
angle += 360f;
}
Now, after we've got our touch angle in degree, we can calculate new coordinates for our icon using equation of an ellipse.
double touchAngleRad = Math.toRadians(touchAngle);
float iconX = centerX + (float) (radiusX * Math.cos(touchAngleRad));
float iconY = centerY + (float) (radiusY * Math.sin(touchAngleRad));
// centerX, centerY - coordinates of the center of ellipses
// radiusX, radiusY - horizontal and vertical radiuses of the ellipse, to which
// the touched icon belongs
// iconX, iconY - new coordinates of the icon lying on that ellipse
If you recalculate icon position according to this algorithm on every TOUCH_MOVE and invalidate the view, then your icon will move across its ellipse.
The code can be further optimized by using radians instead of degrees, but I thought degrees are better suitable for explanation. Hope this help. Post your code, if you experience any issues with the implementation.
Related
I am developing a Gdx game but I have got stuck on some part and I will explain briefly about it:
I have 9 balls organized on 3*3 and I need to detect the ball that I'm touching as shown in the image below:
enter image description here
and I typed this code:
for (int i : listBalls) {
touchPoint = new Vector3(Gdx.input.getX(), Gdx.input.getY(), 0);
rectangle = new Rectangle(sprite[i].getX(), sprite[i].getY(), spriteSize, spriteSize);
if (rectangle.contains(touchPoint.x, touchPoint.y)) {
Gdx.app.log("Test", "Touched dragged " + String.valueOf(i));
pointer = i;
}
}
In case of touching any ball of the above row, it detects the opposite ball in the bottom row. For example in the above image, if I touch ball no 2 on the top it will point to ball no 8, and same if touching any of the bottom balls.
In case of touching any ball of the middle ball, it gives the right on.
I hope I could explain clearly my issue. Please help.
As explained here: LibGDX input y-axis flipped the coordinate system of the input is inverted on the y-axis, try substracting the screen height of your device or camera
int y = screenHeight - Gdx.input.getY();
Keep in mind that using a Camera and unprojecting the input coordinates is the most recommended way to go about detecting input in LibGDX. Example:
#Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
Vector3 unprojected = camera.unproject(new Vector3(screenX, screenY,0));
}
You don't even have to invert the y-coordinate, unproject() does this automatically for you. To use the correct x and y coordinates you then could use:
float x = unprojected.x;
float y = unprojected.y;
I'm doing a simple android animation using my self-customized VIEW. I have two circles drawn on the onDraw() method of the class extends to View class. The one circle is moving upon dragging using MotionEvent while the other one is static on a certain position. If the moving circle touches any point of a static circle, the color of the moving circle will change to the color of the static circle.
For example
int_circle_radius= 50;
int circle1_x = 0;
int circle1_y = 0;
int circle2_x = 200;
int circle2_y = 200;
let's assume that the moving circle which is the circle 1 was drag and drop to a certain point of the circle 2.
I tried using the below formula but the circle 1's color only change if it really goes to the exact location of the circle 2.
if (circle1_x == circle1_x && circle1_y == circle2_y){
paint.setColor(Color.RED);
}
I know that the problem here is a circle has many points from it's radius, but how can I trigger a specific action if the a circle touches any of his point to another circle? Thanks.
You can simply calculate the distance between the centers of the two circles. If the distance is less than two times the radius, the circles are intersecting. Calculating that is easy. You can not expect to get the exact MotionEvent where the circles distance equals the double radius, so you have to check for a distance that is less or equal:
int deltaX = circle1_x - circle2_x;
int deltaY = circle1_y - circle2_y;
if(Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)) <= 2 * circle_radius) {
paint.setColor(Color.RED);
}
Background
I'm developing an app for Android that plots data as a line graph using AndroidPlot. Because of the nature of the data, it's important that it be pannable and zoomable. I'm using AndroidPlot's sample code on bitbucket for panning and zooming, modified to allow panning and zooming in both X and Y directions.
Everything works as desired except that there are no X and Y axis lines. It is very disorienting to look at the data without them. The grid helps, but there's no guarantee that grid lines will actually fall on the axis.
To remedy this I have tried adding two series, one that falls on just the X axis and the other on the Y. The problem with this is that if one zooms out too far the axis simply end, and it becomes apparent that I have applied a 'hack'.
Question
Is it possible to add X and Y axis lines to AndroidPlot? Or will my sad hack have to do?
EDIT
Added tags
I figured it out. It wasn't trivial, took a joint effort with a collaborator, and sucked up many hours of our time.
Starting with the sample mentioned in my question, I had to extend XYPlot (which I called GraphView) and override the onPreInit method. Note that I have two PointF's, minXY and maxXY, that are defined in my overridden XYPlot and manipulated when I zoom or scroll.
#Override
protected void onPreInit() {
super.onPreInit();
final Paint axisPaint = new Paint();
axisPaint.setColor(getResources().getColor(R.color.MY_AXIS_COLOR));
axisPaint.setStrokeWidth(3); //or whatever stroke width you want
XYGraphWidget oldWidget = getGraphWidget();
XYGraphWidget widget = new XYGraphWidget(getLayoutManager(),
this,
new SizeMetrics(
oldWidget.getHeightMetric(),
oldWidget.getWidthMetric())) {
//We now override XYGraphWidget methods
RectF mGridRect;
#Override
protected void doOnDraw(Canvas canvas, RectF widgetRect)
throws PlotRenderException {
//In order to draw the x axis, we must obtain gridRect. I believe this is the only
//way to do so as the more convenient routes have private rather than protected access.
mGridRect = new RectF(widgetRect.left + ((isRangeAxisLeft())?getRangeLabelWidth():1),
widgetRect.top + ((isDomainAxisBottom())?1:getDomainLabelWidth()),
widgetRect.right - ((isRangeAxisLeft())?1:getRangeLabelWidth()),
widgetRect.bottom - ((isDomainAxisBottom())?getDomainLabelWidth():1));
super.doOnDraw(canvas, widgetRect);
}
#Override
protected void drawGrid(Canvas canvas) {
super.drawGrid(canvas);
if(mGridRect == null) return;
//minXY and maxXY are PointF's defined elsewhere. See my comment in the answer.
if(minXY.y <= 0 && maxXY.y >= 0) { //Draw the x axis
RectF paddedGridRect = getGridRect();
//Note: GraphView.this is the extended XYPlot instance.
XYStep rangeStep = XYStepCalculator.getStep(GraphView.this, XYAxisType.RANGE,
paddedGridRect, getCalculatedMinY().doubleValue(),
getCalculatedMaxY().doubleValue());
double rangeOriginF = paddedGridRect.bottom;
float yPix = (float) (rangeOriginF + getRangeOrigin().doubleValue() * rangeStep.getStepPix() /
rangeStep.getStepVal());
//Keep things consistent with drawing y axis even though drawRangeTick is public
//drawRangeTick(canvas, yPix, 0, getRangeLabelPaint(), axisPaint, true);
canvas.drawLine(mGridRect.left, yPix, mGridRect.right, yPix, axisPaint);
}
if(minXY.x <= 0 && maxXY.x >= 0) { //Draw the y axis
RectF paddedGridRect = getGridRect();
XYStep domianStep = XYStepCalculator.getStep(GraphView.this, XYAxisType.DOMAIN,
paddedGridRect, getCalculatedMinX().doubleValue(),
getCalculatedMaxX().doubleValue());
double domainOriginF = paddedGridRect.left;
float xPix = (float) (domainOriginF - getDomainOrigin().doubleValue() * domianStep.getStepPix() /
domianStep.getStepVal());
//Unfortunately, drawDomainTick has private access in XYGraphWidget
canvas.drawLine(xPix, mGridRect.top, xPix, mGridRect.bottom, axisPaint);
}
}
};
widget.setBackgroundPaint(oldWidget.getBackgroundPaint());
widget.setMarginTop(oldWidget.getMarginTop());
widget.setMarginRight(oldWidget.getMarginRight());
widget.setPositionMetrics(oldWidget.getPositionMetrics());
getLayoutManager().remove(oldWidget);
getLayoutManager().addToTop(widget);
setGraphWidget(widget);
//More customizations can go here
}
And that was that. I sure wish this was built into AndroidPlot; it'll be nasty trying to fix this when it breaks in an AndroidPlot update...
I am trying to gain some more familiarity with the Android SurfaceView class, and in doing so am attempting to create a simple application that allows a user to move a Bitmap around the screen. The troublesome part of this implementation is that I am also including the functionality that the user may drag the image again after it has been placed. In order to do this, I am mapping the bitmap to a simple set of coordinates that define the Bitmap's current location. The region I am mapping the image to, however, does not match up with the image.
The Problem
After placing an image on the SurfaceView using canvas.drawBitmap(), and recording the coordinates of the placed image, the mapping system that I have set up misinterprets the Bitmap's coordinates somehow and does not display correctly. As you can see in this image, I have simply used canvas.drawLine() to draw lines representing the space of my touch region, and the image is always off and to the right:
The Code
Here, I shall provide the relevant code excerpts to help answer my question.
CustomSurface.java
This method encapsulates the drawing of the objects onto the canvas. The comments clarify each element:
public void onDraw(Canvas c){
//Simple black paint
Paint paint = new Paint();
//Draw a white background
c.drawColor(Color.WHITE);
//Draw the bitmap at the coordinates
c.drawBitmap(g.getResource(), g.getCenterX(), g.getCenterY(), null);
//Draws the actual surface that is receiving touch input
c.drawLine(g.left, g.top, g.right, g.top, paint);
c.drawLine(g.right, g.top, g.right, g.bottom, paint);
c.drawLine(g.right, g.bottom, g.left, g.bottom, paint);
c.drawLine(g.left, g.bottom, g.left, g.top, paint);
}
This method encapsulates how I capture touch events:
public boolean onTouchEvent(MotionEvent e){
switch(e.getAction()){
case MotionEvent.ACTION_DOWN:{
if(g.contains((int) e.getX(), (int) e.getY()))
item_selected = true;
break;
}
case MotionEvent.ACTION_MOVE:{
if(item_selected)
g.move((int) e.getX(), (int) e.getY());
break;
}
case MotionEvent.ACTION_UP:{
item_selected = false;
break;
}
default:{
//Do nothing
break;
}
}
return true;
}
Graphic.java
This method is used to construct the Graphic:
//Initializes the graphic assuming the coordinate is in the upper left corner
public Graphic(Bitmap image, int start_x, int start_y){
resource = image;
left = start_x;
top = start_y;
right = start_x + image.getWidth();
bottom = start_y + image.getHeight();
}
This method detects if a user is clicking inside the image:
public boolean contains(int x, int y){
if(x >= left && x <= right){
if(y >= top && y <= bottom){
return true;
}
}
return false;
}
This method is used to move the graphic:
public void move(int x, int y){
left = x;
top = y;
right = x + resource.getWidth();
bottom = y + resource.getHeight();
}
I also have 2 methods that determine the center of the region (used for redrawing):
public int getCenterX(){
return (right - left) / 2 + left;
}
public int getCenterY(){
return (bottom - top) / 2 + top;
}
Any help would be greatly appreciated, I feel as though many other StackOverflow users could really benefit from a solution to this issue.
There's a very nice and thorough explanation of touch/multitouch/gestures on Android Developers blog, that includes free and open source code example at google code.
Please, take a look. If you don't need gestures -- just skip that part, read about touch events only.
This issue ended up being much simpler than I had thought, and after some tweaking I realized that this was an issue of image width compensation.
This line in the above code is where the error stems from:
c.drawBitmap(g.getResource(), g.getCenterX(), g.getCenterY(), null);
As you can tell, I manipulated the coordinates from within the Graphic class to produce the center of the bitmap, and then called canvas.drawBitmap() assuming that it would draw from the center outward.
Obviously, this would not work because the canvas always drops from the top left of an image downwards and to the right, so the solution was simple.
The Solution
Create the touch region with regards to the touch location, but draw it relative to a distance equal to the image width subtracted from the center location in the x and y directions. I basically changed the architecture of the Graphic class to implement a getDrawX() and getDrawY() method that would return the modified x and y coordinates of where it should be drawn in order to have the center_x and center_y values (determined in the constructor) actually appear to be at the center of the region.
It all comes down to the fact that in an attempt to compensate for the way the canvas draws bitmaps, I unfortunately incorporated some bad behaviors and in the end had to handle the offset in a completely different way.
I've drawn 5 bitmaps from .png files on a canvas - a head, a body and two arms and legs.
How can I detect which of these has been touched on an OnTouch? And, more specifically, can I detect if the OnTouch was within the actual shape of the body part touched?
What I mean is, obviously, the .pngs themselves are rectangular, but does Android know, or can I tell it, to ignore the transparency within the image?
You could get the colour of pixel touched and compare it to the colour of pixel on the background at those co-ords.
EDIT: ok, ignore that, you can't get the colour of a pixel on the canvas, so instead, get the x,y of the touch, check if any of the body part images have been touched, if so, take the x,y of the image from the touch x,y, then get the pixel of the image, which should be transparent or colour.
public boolean onTouchEvent(MotionEvent event)
{
int x = (int) event.getX();
int y = (int) event.getY();
int offsetx, offsety;
for(int i = 0;i<NUM_OF_BODY_PARTS;i++)
{
if(bodyPartRect[i].intersects(x,y,x+1,y+1))
{
offsetx = x - bodyPartRect[i].left;
offsety = y - bodyPartRect[i].top;
if(bodyPartBMP[i].getPixel(offsetx,offsety) == TRANSPARENT)
{
//whatever
}
}
}
}