In this Example, a pre-defined function is drawn to an interactive chart. This is the code used in the Example:
/**
* The simple math function Y = fun(X) to draw on the chart.
* #param x The X value
* #return The Y value
*/
protected static float fun(float x) {
return (float) Math.pow(x, 3) - x / 4;
}
....
private void drawDataSeriesUnclipped(Canvas canvas) {
mSeriesLinesBuffer[0] = mContentRect.left;
mSeriesLinesBuffer[1] = getDrawY(fun(mCurrentViewport.left));
mSeriesLinesBuffer[2] = mSeriesLinesBuffer[0];
mSeriesLinesBuffer[3] = mSeriesLinesBuffer[1];
float x;
for (int i = 1; i <= DRAW_STEPS; i++) {
mSeriesLinesBuffer[i * 4 + 0] = mSeriesLinesBuffer[(i - 1) * 4 + 2];
mSeriesLinesBuffer[i * 4 + 1] = mSeriesLinesBuffer[(i - 1) * 4 + 3];
x = (mCurrentViewport.left + (mCurrentViewport.width() / DRAW_STEPS * i));
mSeriesLinesBuffer[i * 4 + 2] = getDrawX(x);
mSeriesLinesBuffer[i * 4 + 3] = getDrawY(fun(x));
}
canvas.drawLines(mSeriesLinesBuffer, mDataPaint);
}
I now want to modify this code, so that not a pre-defined function gets drawn, but them path the user draws with his finger on the screen.
(I know that I could easily draw a Path following the user's finger and I have tried it before. Although the path gets drawn correctly, it doesn't behave the way I want it to so I have to try this method).
To draw this Path, I tried to save the points the user touched:
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
firstTouchX = event.getX();
firstTouchY = event.getY();
case MotionEvent.ACTION_MOVE:
lastTouchX = event.getX();
lastTouchY = event.getY();
case MotionEvent.ACTION_UP:
lastTouchX = event.getX();
lastTouchY = event.getY();
}
Point lastTouch = new Point();
lastTouch.set((int)lastTouchX, (int)lastTouchY);
pointsList.add(lastTouch);
Then I changed fun, to return the correct y-value of a point if the x-value of it is passed:
protected static float fun(float x) {
float currX = 0;
float currY = 0;
for (int i = 0; i < pointsList.size(); i++) {
Point curr = pointsList.get(i);
currX = curr.x;
currY = curr.y;
}
if (currX == x) {
return currY;
}
I know this is a very long and complicated question, but I really don't know what else to do and I've tried everything but couldn't manage to find a solution, so I would be really grateful if someone could take the time and take a look at my problem.
To summarize my Problem:
- I want to draw a line, following the touch of the users Finger
- I need to do it without a path, but with the method described above
Related
I'm new to Android, and I'm trying to get the hang of multi touch input. I've begun with a simple app that allows the user to create rectangles on a Canvas by dragging and releasing with one finger, which I have working. To expand upon that, I now want a user to be able to rotate the rectangle they are drawing using a second finger, which is where my problems begin. As it stands, adding a second finger will cause multiple rectangles to rotate, instead of just the current one, but they will revert to their default orientation as soon as the second finger is released.
I've been working at it for a while, and I think my core problem is that I'm mishandling the multiple MotionEvents that come with two (or more fingers). Logging statements I left to display the coordinates on the screen for each event stay tied to the first finger touching the screen, instead of switching to the second. I've tried multiple configurations of accessing and changing the event pointer ID, and still no luck. If anyone could provide some guidance in the right direction, I would be extremely grateful.
My code is as follows:
public class BoxDrawingView extends View {
private static final String TAG = "BoxDrawingView";
private static final int INVALID_POINTER_ID = -1;
private int mActivePointerId = INVALID_POINTER_ID;
private Box mCurrentBox;
private List<Box> mBoxen = new ArrayList<>();
private Float mLastTouchX;
private Float mLastTouchY;
...
#Override
public boolean onTouchEvent(MotionEvent event) {
switch(MotionEventCompat.getActionMasked(event)) {
case MotionEvent.ACTION_DOWN:
mActivePointerId = MotionEventCompat.getPointerId(event, 0);
current = new PointF(MotionEventCompat.getX(event, mActivePointerId),
MotionEventCompat.getY(event, mActivePointerId));
action = "ACTION_DOWN";
// Reset drawing state
mCurrentBox = new Box(current);
mBoxen.add(mCurrentBox);
mLastTouchX = MotionEventCompat.getX(event, MotionEventCompat.getPointerId(event, 0));
mLastTouchY = MotionEventCompat.getY(event, MotionEventCompat.getPointerId(event, 0));
break;
case MotionEvent.ACTION_POINTER_DOWN:
action = "ACTION_POINTER_DOWN";
mActivePointerId = MotionEventCompat.getPointerId(event, 0);
mLastTouchX = MotionEventCompat.getX(event, MotionEventCompat.getPointerId(event, 0));
mLastTouchY = MotionEventCompat.getY(event, MotionEventCompat.getPointerId(event, 0));
break;
case MotionEvent.ACTION_MOVE:
action = "ACTION_MOVE";
current = new PointF(MotionEventCompat.getX(event, mActivePointerId),
MotionEventCompat.getY(event, mActivePointerId));
if (mCurrentBox != null) {
mCurrentBox.setCurrent(current);
invalidate();
}
if(MotionEventCompat.getPointerCount(event) > 1) {
int pointerIndex = MotionEventCompat.findPointerIndex(event, mActivePointerId);
float currX = MotionEventCompat.getX(event, pointerIndex);
float currY = MotionEventCompat.getY(event, pointerIndex);
if(mLastTouchX < currX) {
// simplified: only use x coordinates for rotation for now.
// +X for clockwise, -X for counter clockwise
Log.d(TAG, "Clockwise");
mRotationAngle = 30;
}
else if (mLastTouchX > getX()) {
Log.d(TAG, "Counter clockwise");
mRotationAngle = -30;
}
}
break;
case MotionEvent.ACTION_UP:
action = "ACTION_UP";
mCurrentBox = null;
mLastTouchX = null;
mLastTouchY = null;
mActivePointerId = INVALID_POINTER_ID;
break;
case MotionEvent.ACTION_POINTER_UP:
action = "ACTION_POINTER_UP";
int pointerIndex = event.getActionIndex();
int pointerId = event.getPointerId(pointerIndex);
if(pointerId == mActivePointerId){
mActivePointerId = INVALID_POINTER_ID;
}
break;
case MotionEvent.ACTION_CANCEL:
action = "ACTION_CANCEL";
mCurrentBox = null;
mActivePointerId = INVALID_POINTER_ID;
break;
}
return true;
}
#Override
protected void onDraw(Canvas canvas){
// Fill the background
canvas.drawPaint(mBackgroundPaint);
for(Box box : mBoxen) {
// Box is a custom object. Origin is the origin point,
// Current is the point of the opposite diagonal corner
float left = Math.min(box.getOrigin().x, box.getCurrent().x);
float right = Math.max(box.getOrigin().x, box.getCurrent().x);
float top = Math.min(box.getOrigin().y, box.getCurrent().y);
float bottom = Math.max(box.getOrigin().y, box.getCurrent().y);
if(mRotationAngle != 0) {
canvas.save();
canvas.rotate(mRotationAngle);
canvas.drawRect(left, top, right, bottom, mBoxPaint);
canvas.rotate(-mRotationAngle);
canvas.restore();
mRotationAngle = 0;
} else {
canvas.drawRect(left, top, right, bottom, mBoxPaint);
}
}
}
}
There are several ways to draw things, not just in android, but in Java as well. The thing is that you are trying to draw the rectangles by rotating the Canvas. That's a way, but in my personal experience I think that is only a good choice if you want to rotate the whole picture. If not, that may get a little tricky because you need to place a rotation axis, which it seems you are not using, so Android will asume that you want to rotate from the left top corner or the center of the view (I don't remember).
If you are opting for that choice, you may try to do it like this:
Matrix matrix = new Matrix();
matrix.setRotate(angle, rectangleCenterX, rectangleCenterY);
canvas.setMatrix(matrix);
But I recommend you to try a different approach. Do the rotation directly on the rectangle that you are moving, by calculating the axes of the polygon. This you can do it using Java Math operations:
public void formShape(int cx[], int cy[], double scale) {
double xGap = (width / 2) * Math.cos(angle) * scale;
double yGap = (width / 2) * Math.sin(angle) * scale;
cx[0] = (int) (x * scale + xGap);
cy[0] = (int) (y * scale + yGap);
cx[1] = (int) (x * scale - xGap);
cy[1] = (int) (y * scale - yGap);
cx[2] = (int) (x * scale - xGap - length * Math.cos(radians) * scale);
cy[2] = (int) (y * scale - yGap - length * Math.sin(radians) * scale);
cx[3] = (int) (x * scale + xGap - length * Math.cos(radians) * scale);
cy[3] = (int) (y * scale + yGap - length * Math.sin(radians) * scale);
}
So (x,y) is the center of your rectangle and with, height tell you how big is it. In the formShape(int[], int[], double) method cx and cy are going to be used to draw your shape and scale is the value to use if you want to do zoom in or zoom out later, if not just use scale = 1;
Now for drawing your rectangles, this is how you do it:
Paint paint = new Paint();
paint.setColor(Color.GRAY);
paint.setStyle(Style.FILL);
int[] cx = new int[4];
int[] cy = new int[4];
Box box = yourBoxHere;
box.formShape(cx, cy, 1);
Path path = new Path();
path.reset(); // only needed when reusing this path for a new build
path.moveTo(cx[0], cy[0]); // used for first point
path.lineTo(cx[1], cy[1]);
path.lineTo(cx[2], cy[2]);
path.lineTo(cx[3], cy[3]);
path.lineTo(cx[0], cy[0]); // repeat the first point
canvas.drawPath(wallpath, paint);
For multitouch rotation listener you should override 2 methods in your Activity or View:
#Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getId() == MotionEvent.ACTION_UP)
this.points = null;
}
}
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
if(event.getPointerCount() >= 2) {
float newPoints[][] = new float[][] {
{event.getX(0), event.getY(0)},
{event.getX(1), event.getY(1)}
};
double angle = angleBetweenTwoPoints(newPoints[0][0], newPoints[0][1], newPoints[1][0], newPoints[1][1]);
if(points != null) {
double difference = angle - initialAngle;
if(Math.abs(difference) > rotationSensibility) {
listener.onGestureListener(GestureListener.ROTATION, Math.toDegrees(difference));
this.initialAngle = angle;
}
} else {
this.initialAngle = angle;
}
this.points = newPoints;
}
}
public static double angleBetweenTwoPoints(double xHead, double yHead, double xTail, double yTail) {
if(xHead == xTail) {
if(yHead > yTail)
return Math.PI/2;
else
return (Math.PI*3)/2;
} else if(yHead == yTail) {
if(xHead > xTail)
return 0;
else
return Math.PI;
} else if(xHead > xTail) {
if(yHead > yTail)
return Math.atan((yHead-yTail)/(xHead-xTail));
else
return Math.PI*2 - Math.atan((yTail-yHead)/(xHead-xTail));
} else {
if(yHead > yTail)
return Math.PI - Math.atan((yHead-yTail)/(xTail-xHead));
else
return Math.PI + Math.atan((yTail-yHead)/(xTail-xHead));
}
}
Sorry, but this answer is getting long, if you have further questions about any of those operations and you want to change the approach of your solution, please ask again and tell me in the comments.
I hope this was helpful.
I am trying to implement zooming on a canvas which should focus on a pivot point. Zooming works fine, but afterwards the user should be able to select elements on the canvas. The problem is, that my translation values seem to be incorrect, because they have a different offset, than the ones where I don't zoom to the pivot point (zoom without pivot point and dragging works fine).
I used some code from this example.
The relevant code is:
class DragView extends View {
private static float MIN_ZOOM = 0.2f;
private static float MAX_ZOOM = 2f;
// These constants specify the mode that we're in
private static int NONE = 0;
private int mode = NONE;
private static int DRAG = 1;
private static int ZOOM = 2;
public ArrayList<ProcessElement> elements;
// Visualization
private boolean checkDisplay = false;
private float displayWidth;
private float displayHeight;
// These two variables keep track of the X and Y coordinate of the finger when it first
// touches the screen
private float startX = 0f;
private float startY = 0f;
// These two variables keep track of the amount we need to translate the canvas along the X
//and the Y coordinate
// Also the offset from initial 0,0
private float translateX = 0f;
private float translateY = 0f;
private float lastGestureX = 0;
private float lastGestureY = 0;
private float scaleFactor = 1.f;
private ScaleGestureDetector detector;
...
private void sharedConstructor() {
elements = new ArrayList<ProcessElement>();
flowElements = new ArrayList<ProcessFlow>();
detector = new ScaleGestureDetector(getContext(), new ScaleListener());
}
/**
* checked once to get the measured screen height/width
* #param hasWindowFocus
*/
#Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (!checkDisplay) {
displayHeight = getMeasuredHeight();
displayWidth = getMeasuredWidth();
checkDisplay = true;
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
ProcessBaseElement lastElement = null;
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mode = DRAG;
// Check if an Element has been touched.
// Need to use the absolute Position that's why we take the offset into consideration
touchedElement = isElementTouched(((translateX * -1) + event.getX()) / scaleFactor, (translateY * -1 + event.getY()) / scaleFactor);
//We assign the current X and Y coordinate of the finger to startX and startY minus the previously translated
//amount for each coordinates This works even when we are translating the first time because the initial
//values for these two variables is zero.
startX = event.getX() - translateX;
startY = event.getY() - translateY;
}
// if an element has been touched -> no need to take offset into consideration, because there's no dragging possible
else {
startX = event.getX();
startY = event.getY();
}
break;
case MotionEvent.ACTION_MOVE:
if (mode != ZOOM) {
if (touchedElement == null) {
translateX = event.getX() - startX;
translateY = event.getY() - startY;
} else {
startX = event.getX();
startY = event.getY();
}
}
if(detector.isInProgress()) {
lastGestureX = detector.getFocusX();
lastGestureY = detector.getFocusY();
}
break;
case MotionEvent.ACTION_UP:
mode = NONE;
break;
case MotionEvent.ACTION_POINTER_DOWN:
mode = ZOOM;
break;
case MotionEvent.ACTION_POINTER_UP:
break;
}
detector.onTouchEvent(event);
invalidate();
return true;
}
private ProcessBaseElement isElementTouched(float x, float y) {
for (int i = elements.size() - 1; i >= 0; i--) {
if (elements.get(i).isTouched(x, y))
return elements.get(i);
}
return null;
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
if(detector.isInProgress()) {
canvas.scale(scaleFactor,scaleFactor,detector.getFocusX(),detector.getFocusY());
} else
canvas.scale(scaleFactor, scaleFactor,lastGestureX,lastGestureY); // zoom
// canvas.scale(scaleFactor,scaleFactor);
//We need to divide by the scale factor here, otherwise we end up with excessive panning based on our zoom level
//because the translation amount also gets scaled according to how much we've zoomed into the canvas.
canvas.translate(translateX / scaleFactor, translateY / scaleFactor);
drawContent(canvas);
canvas.restore();
}
/**
* scales the canvas
*/
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
scaleFactor *= detector.getScaleFactor();
scaleFactor = Math.max(MIN_ZOOM, Math.min(scaleFactor, MAX_ZOOM));
return true;
}
}
}
Elements are saved with their absolute position on the canvas (with dragging in mind). I suspect that I don't take the new offset from the pivot point to translateX and translateY in consideration, but I can't figure out where and how I should do this.
Any help would be appreciated.
Okay, so you're basically trying to figure out where a certain screen X/Y coordinate corresponds to, after the view has been scaled (s) around a certain pivot point {Px, Py}.
So, let's try to break it down.
For the sake of argument, lets assume that Px & Py = 0, and that s = 2. This means the view was zoomed by a factor of 2, around the top left corner of the view.
In this case, the screen coordinate {0, 0} corresponds to {0, 0} in the view, because that point is the only point which hasn't changed. Generally speaking, if the screen coordinate is equal to the pivot point, then there is no change.
What happens if the user clicks on some other point, lets say {2, 3}? In this case, what was once {2, 3} has now moved by a factor of 2 from the pivot point (which is {0, 0}), and so the corresponding position is {4, 6}.
All this is easy when the pivot point is {0, 0}, but what happens when it's not?
Well, lets look at another case - the pivot point is now the bottom right corner of the view (Width = w, Height = h - {w, h}). Again, if the user clicks at the same position, then the corresponding position is also {w, h}, but lets say the user clicks on some other position, for example {w - 2, h - 3}? The same logic occurs here: The translated position is {w - 4, h - 6}.
To generalize, what we're trying to do is convert the screen coordinates to the translated coordinate. We need to perform the same action on this X/Y coordinate we received that we performed on every pixel in the zoomed view.
Step 1 - we'd like to translate the X/Y position according to the pivot point:
X = X - Px
Y = Y - Py
Step 2 - Then we scale X & Y:
X = X * s
Y = Y * s
Step 3 - Then we translate back:
X = X + Px
Y = Y + Py
If we apply this to the last example I gave (I will only demonstrate for X):
Original value: X = w - 2, Px = w
Step 1: X <-- X - Px = w - 2 - w = -2
Step 2: X <-- X * s = -2 * 2 = -4
Step 3: X <-- X + Px = -4 + w = w - 4
Once you apply this to any X/Y you receive which is relevant prior to the zoom, the point will be translated so that it is relative to the zoomed state.
Hope this helps.
I want the user to be able to drag the edges of a square around the canvas. With my current solution it works but has glitches, sometimes an edge cannot be selected. Is there a clean way to tell if a line has been clicked (e.g. passes through a coordinate)? This is how I'm currently testing:
// check edge pressed, edge is the line between to
// coords e.g. (i) & (i = 1)
for (int i = 0; i < coords.size(); i++) {
p1 = coords.get(i);
if ((i + 1) > (coords.size() - 1)) p2 = coords.get(0);
else p2 = coords.get(i + 1);
// is this the line pressed
if (p1.x <= event.getX() + 5 && event.getX() - 5 <= p2.x && p1.y <= event.getY() + 5 && event.getY() - 5 <= p2.y) {
// points found, set to non temp
// variable for use in ACTION_MOVE
point1 = p1;
point2 = p2;
break;
} else if (p1.x >= event.getX() + 5 && event.getX() - 5 >= p2.x && p1.y >= event.getY() + 5 && event.getY() - 5 >= p2.y) {
// points found, set to non temp
// variable for use in ACTION_MOVE
point1 = p1;
point2 = p2;
break;
}
}
The code bellow //is this the line pressed is the most important and also most likely the issue. The +5 and -5 are used to give the use a larger area to click on.
Here is the whole on click event:
public void EditEdge() {
//TODO this works like shit
// Detect the two coordinates along the edge pressed and drag
// them
scene.setOnTouchListener(new View.OnTouchListener() {
private int startX;
private int startY;
private Point point1 = new Point(0, 0);
private Point point2 = new Point(0, 0);
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = (int) event.getX();
startY = (int) event.getY();
Point p1;
Point p2;
// check edge pressed, edge is the line between to
// coords e.g. (i) & (i = 1)
for (int i = 0; i < coords.size(); i++) {
p1 = coords.get(i);
if ((i + 1) > (coords.size() - 1)) p2 = coords.get(0);
else p2 = coords.get(i + 1);
// is this the line pressed
if (p1.x <= event.getX() + 5 && event.getX() - 5 <= p2.x && p1.y <= event.getY() + 5 && event.getY() - 5 <= p2.y) {
// points found, set to non temp
// variable for use in ACTION_MOVE
point1 = p1;
point2 = p2;
break;
} else if (p1.x >= event.getX() + 5 && event.getX() - 5 >= p2.x && p1.y >= event.getY() + 5 && event.getY() - 5 >= p2.y) {
// points found, set to non temp
// variable for use in ACTION_MOVE
point1 = p1;
point2 = p2;
break;
}
}
break;
case MotionEvent.ACTION_UP:
point1 = new Point(0, 0);
point2 = new Point(0, 0);
// scene.setOnTouchListener(scene.editModeOnTouchListener);
break;
case MotionEvent.ACTION_MOVE:
for (Point p: new Point[] {
point1, point2
}) {
int modX = (int)(p.x + (event.getX() - startX));
int modY = (int)(p.y + (event.getY() - startY));
p.set(modX, modY);
}
SetCoords(coords);
startX = (int) event.getX();
startY = (int) event.getY();
break;
default:
return false;
}
return true;
}
});
}
So is there a easier way to tell is a line is clicked/ passes through a point or is that not the issue?
Thanks
use the line equation y = mx + b to find out if the point is on a line
float EPSILON = 0.001f;
public boolean isPointOnLine(Point linePointA, Point linePointB, Point point) {
float m = (linePointB.y - linePointA.y) / (linePointB.x - linePointA.x);
float b = linePointA.y - m * linePointA.x;
return Math.abs(point.y - (m*point.x+b)) < EPSILON);
}
Great piece of code by #tyczj !
I added a use-case to handle vertical lines, which gives me the following code fragment:
public boolean isPointOnLine(PointF lineStaPt, PointF lineEndPt, PointF point) {
final float EPSILON = 0.001f;
if (Math.abs(staPt.x - endPt.x) < EPSILON) {
// We've a vertical line, thus check only the x-value of the point.
return (Math.abs(point.x - lineStaPt.x) < EPSILON);
} else {
float m = (lineEndPt.y - lineStaPt.y) / (lineEndPt.x - lineStaPt.x);
float b = lineStaPt.y - m * lineStaPt.x;
return (Math.abs(point.y - (m * point.x + b)) < EPSILON);
}
}
Also a piece of code to check if a point lies on a line-segment:
public boolean isPointOnLineSegment(PointF staPt, PointF endPt, PointF point) {
final float EPSILON = 0.001f;
if (isPointOnLine(staPt, endPt, point)) {
// Create lineSegment bounding-box.
RectF lb = new RectF(staPt.x, staPt.y, endPt.x, endPt.y);
// Extend bounds with epsilon.
RectF bounds = new RectF(lb.left - EPSILON, lb.top - EPSILON, lb.right + EPSILON, lb.bottom + EPSILON);
// Check if point is contained within lineSegment-bounds.
return bounds.contains(point.x, point.y);
}
return false;
}
You could define 8 Rect to check against - the 4 sides and 4 corners (so you can move 2 edges at once). The lines of the edge should have a width for the touchable area.
Define a Point centred on your touch event, there are then methods for checking if a rect contains a point.
I need an advice how to achieve the following functionality under Android:
I need an image that represents something like a graph (from discrete math), with vertices and edges, where I can click every vertice or edge and fire a different action.
Please advise me how to achieve this (maybe with imagebuttons) or another approach to represent this functionality.
I was bored, so I coded up this crude example...
It assumes straight edges between points.
public class App extends Activity
{
PlotView plot;
#Override
public void onCreate(Bundle sis)
{
super.onCreate(sis);
plot = new PlotView(this);
setContentView(plot);
}
public class PlotView extends View
{
Paint paint1 = new Paint();
Paint paint2 = new Paint();
Point[] points = new Point[10];
public PlotView(Context context)
{
super(context);
paint1.setColor(Color.RED);
paint2.setColor(Color.BLUE);
for (int i = 0; i < points.length; i++)
{
points[i] = new Point();
points[i].x = (float) (Math.random() * 320);
points[i].y = (float) (Math.random() * 480);
}
Arrays.sort(points);
}
#Override
protected void onDraw(Canvas canvas)
{
canvas.drawColor(Color.WHITE);
for (int i = 0; i < points.length; i++)
{
if (i < points.length - 1)
{
canvas.drawLine(points[i].x, points[i].y, points[i + 1].x, points[i + 1].y, paint2);
}
canvas.drawCircle(points[i].x, points[i].y, 5, paint1);
}
super.onDraw(canvas);
}
#Override
public boolean onTouchEvent(MotionEvent event)
{
switch(event.getAction())
{
case MotionEvent.ACTION_DOWN:
{
float x = event.getX();
float y = event.getY();
int hitPoint = -1;
int closestLeft = -1;
int closestRight = -1;
for (int i = 0; i < points.length; i++)
{
float dx = x - points[i].x;
float dy = y - points[i].y;
if(i < points.length - 1)
{
if(points[i].x < x && x < points[i + 1].x)
{
closestLeft = i;
closestRight = i + 1;
}
}
if (Math.abs(dx) <= 16.0f && Math.abs(dy) <= 16.0f)
{
hitPoint = i;
break;
}
}
if (hitPoint != -1)
{
Toast.makeText(getContext(), "Hit Point: " + hitPoint, Toast.LENGTH_SHORT).show();
}
else
if(closestLeft != -1 && closestRight != -1)
{
float dx = points[closestLeft].x - points[closestRight].x;
float dy = points[closestLeft].y - points[closestRight].y;
final float u = ((x - points[closestLeft].x) * dx + (y - points[closestLeft].y) * dy) / (dx * dx + dy * dy);
float px = points[closestLeft].x + u * dx;
float py = points[closestLeft].y + u * dy;
if (Math.abs(x - px) <= 16.0f && Math.abs(y - py) <= 16.0f)
{
Toast.makeText(getContext(), "Hit Line Between: " + closestLeft + " & " + closestRight, Toast.LENGTH_SHORT).show();
}
}
}
}
return super.onTouchEvent(event);
}
public class Point implements Comparable<Point>
{
float x;
float y;
#Override
public int compareTo(Point other)
{
if (x < other.x) return -1;
if (x > other.x) return 1;
return 0;
}
}
}
}
I can imagine how to do this with SurfaceView:
create a Vertex class, which among other things, has an x,y coordinate representing where to draw the vertex. If your vertex was a png image of a circle, then the top-left x,y coordinates of the image are stored in the Vertex class.
Have all your verticies in a List, and iterate through and draw each vertex.
the edges are more complicated since they might criss-cross or curve around.
assuming they are straight lines, then you can have a Edge class that contains the starting x,y and ending x,y coordinates.
you can iterate through a List of Edges and draw the lines accordingly
In order to detect when a user clicks on them, you should override the onTouch method and check the event.rawX() and event.rawY() values to see if they match up to a Vertex or Edge class.
for a Vertex class, you can check if x <= event.rawX <= x + image_width and y <= event.rawY <= y + image_height
for an Edge, you can check if the event.rawX, event.rawY coordinates are found in the line formed by the two sets of coordinates you stored in the Edge class.
I've used a similar method to draw a set of nodes in a game. I'm not so sure how to do the edges though - the method I outline would only work if they were straight and do not criss-cross.
I am sure there is a better way to do this using openGL, but I have not used openGL before.
Hopefully you can get some ideas out of this.
I think you might be best off with a SurfaceView:
http://developer.android.com/reference/android/view/SurfaceView.html
And handling the onTouchEvent() as a whole for the surface, and mapping that to underlying entities in the image. If you're calculating the drawing the graph as you go should be easy to also create a map of tapable areas and grabbing the X and Y of the touch event to figure out if it corresponds to an element in the image.
If you literally have an image, as an already processed PNG for example, you would need some way to also carry in the touch event areas. Depends where that image comes in from.
According to android help, "drawing to a View, is your best choice when you want to draw simple graphics that do not need to change dynamically and are not part of a performance-intensive game." This is the right way to go when making a snake or a chess game, for instance. So I don't see a point in suggesting using a SurfaceView for this, it will just overcomplicate things.
For clickable areas you override public boolean onTouchEvent(MotionEvent event) where you manage x and y coordinates of the click for identifying the clicked area.
I am trying to write the logic for an animation sequence and can't seem to get the thing right. What I want to happen is: if the user clicks on the screen, the method takes in the touchEvent coordinates, and then changes the movement variables of a sprite so that the sprite travels to where the user touched the screen. I have my "launch" event setup like this.
public void launch(float eventX, float eventY) {
//get the touch event coords and then move the banana to them.
fire = true;
//the x and y variables for the sprite
getX();
getY();
//the target x and y variables
targetX = eventX;
targetY = eventY;
//the total distance the two variable have to "travel"
distanceX = x - targetX;
distanceY = y - targetY;
//variables to update the movement
moveX = distanceX;
moveY = distanceY;
}
Then, I thought I was supposed to put the movement variables in the update method like this:
public void update(long gameTime) {
if(gameTime > frameTicker + framePeriod) {
frameTicker = gameTime;
currentFrame++;
if(currentFrame >= frameNbr){
currentFrame = 0;
}
}
this.sourceRect.left = currentFrame * spriteWidth;
this.sourceRect.right = this.sourceRect.left + spriteWidth;
if(fire == true){
x = (int) moveX;
y = (int) moveY;
}
If the user clicks as it is, the animation shows up like it's supposed to, but then instantaneously goes to the top left corner of the screen or what I have come to understand is (0,0) on a coordinate system. I can't figure out how to slow it down so that it moves at a reasonable space and goes where it is supposed to.
You could put the whole animation in your launch() function if you want.
For instance, at the end of the function something like:
float incrementX = distanceX / 100;
float incrementY = distanceY / 100;
float spriteX = getX();
float spriteY = getY();
bool xDone = false;
bool yDone = false;
while(!(xDone && yDone)) {
if (distanceX <= spriteX) {
spriteX += incrementX; // update the sprite's x coordinate as well
}
if (distanceY <= spriteY) {
spriteY += incrementY; // update the sprite's y coordinate as well
}
try{ Thread.sleep(10) } catch(Exception e) {}
}
That code relies on the sprite starting at a lower x and y than the event; if that's not the case it needs to be modified.