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.
Related
Im really confuse that how can i draw professional brushes in android, im drawing circle using path when user moves its finger on screen but when user move its finger slow the number of circle increase and when user move finger fast the number of circle is very less, suppose user moves it finger very fast ther will be only 6 7 circle on that path but if user moves it finger slowly ther will be 30/40 or more circle on the path, which seems very buggy, is this is possible that moveing finger fast stores less points? but if i talk about line , the line on canvas draw prefectly while user moves it finger fast or slow, im sharing my code below
private void DrawCircleBrush(List<PointF> points) {
PointF p1 = points.get(0);
PointF p2 = points.get(1);
Path path = new Path();
path.moveTo(p1.x, p1.y);
for (int i = 1; i < points.size(); i++) {
int rc = (int) (20 +(this.paintStrokeWidth/5));
path.addCircle(p1.x, p1.y, (float) rc, Path.Direction.CCW);
}
this.invalidate();
}
I call DrawCircleBrush Fucnion on action_move like this
path.reset();
points.add(new PointF(x, y));
DrawCircleBrush(points);
You can see the difference of fast moving and slow moving finger in attached picture.
What i want to Achive you can see in this photo, as the brush draw same in this app when i move finger fast or slow,
Ok At last i find solution.
this is how im getting all the points , note that this a theorem called Bresenham's line algorithm and its only works with integer,
this is how im getting all the point , move finger fast or slow point will always be same :D
//x0,y0 , is the starting point and x1,y1 are current points
public List<PointF> findLine( int x0, int y0, int x1, int y1)
{
List<PointF> line = new ArrayList<PointF>();
int dx = Math.abs(x1 - x0);
int dy = Math.abs(y1 - y0);
int sx = x0 < x1 ? 1 : -1;
int sy = y0 < y1 ? 1 : -1;
int err = dx-dy;
int e2;
while (true)
{
line.add(new PointF(x0,y0));
if (x0 == x1 && y0 == y1)
break;
e2 = 2 * err;
if (e2 > -dy)
{
err = err - dy;
x0 = x0 + sx;
}
if (e2 < dx)
{
err = err + dx;
y0 = y0 + sy;
}
}
return line;
}
How im using this function for my brush,
//radius of circle
int rc = (int) (20 +(this.paintStrokeWidth/5));
//getting the points of line
List<PointF> pointFC =findLine((int)this.startX,(int) this.startY,(int) x,
(int) y);
//setting the index of first point
int p1 = 0;
//will check if change occur
boolean change = false;
for(int l=1; l<pointFC.size(); l++){
//getting distance between two pints
float d = distanceBetween(pointFC.get(p1),pointFC.get(l));
if(d>rc){
// we will add this point for draw
//point is a list of PointF //declared universally
points.add(new PointF(pointFC.get(l).x,pointFC.get(l).y));
we will change the index of last point
p1 = l-1;
change = true;
}
}
if(points.size() >0){
path.reset();
DrawCircleBrush(points);
}
if(change){
we will cahnge the starts points, //set them as last drawn points
this.startX = points.get(points.size()-1).x;
this.startY = points.get(points.size()-1).y;
}
//Distance betwenn points
private float distanceBetween(PointF point1,PointF point2) {
return (float) Math.sqrt(Math.pow(point2.x - point1.x, 2) +
Math.pow(point2.y - point1.y, 2));
}
//this is how im drawing my circle brush
private void DrawCircleBrush(List<PointF> points) {
Path path = this.getCurrentPath();
path.moveTo(points.get(0).x, points.get(0).y);
for (int i = 1; i < points.size(); i++) {
PointF pf = points.get(i);
int rc = (int) (20 +(this.paintStrokeWidth/5));
path.addCircle(pf.x, pf.y, (float) rc, Path.Direction.CCW);
}
}
Result: brush is same even move finger fast or slow
Check the "colored_pixels" from here
I am working on android studio and I want to create the effect of throwing an object, in this case a circle drawn on the canvas. But I'm having problems. Can someone guide me?
I take the "X" and the "Y" value where the user touch but how can i make that the circle move in that direction?
Thanks
Game class:
public class Game extends SurfaceView implements View.OnTouchListener {
Paint paint;
int x, y, radius = 100, speedX = 5, speedY = 5, touchY, touchX;
boolean move = false;
boolean one_time = true;
public Game(Context context, AttributeSet attrs) {
super(context, attrs);
this.setOnTouchListener(this);
setFocusable(true);
paint = new Paint();
}
public void onDraw(Canvas canvas){
paint.setColor(Color.WHITE);
canvas.drawRect(0,0,canvas.getWidth(),canvas.getHeight(),paint);
if (one_time == true){
x = canvas.getWidth()/2;
y = canvas.getHeight()/2;
one_time = false;
}
paint.setColor(Color.BLACK);
canvas.drawCircle(x, y, radius, paint);
if (move == true){
if (x >= canvas.getWidth() - radius) {
speedX = -5;
}
if (x <= radius) {
speedX = 5;
}
if (y >= canvas.getHeight() - radius) {
speedY = -5;
}
if (y <= radius) {
speedY = 5;
}
x = x + speedX;
y = y + speedY;
}
invalidate();
}
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction()) {
case (MotionEvent.ACTION_DOWN):
touchX = (int) motionEvent.getX();
touchY = (int) motionEvent.getY();
move = true;
return true;
default:
return super.onTouchEvent(motionEvent);
}
}
}
Gonna need to be an x+=speedX or y+=speedY in there somewhere so that the x or y value changes causing the ball to move. If your trying to implement some physics in there ur gonna need some maths.
You need a way to change the (x, y) values at a certain interval. You can do this with the Timer class, but then you get into some difficult multithreading code. I suggest looking at using OpenGL ES or a higher level library such as LibGDX. Both of these provide an event loop which allow you to update objects that will be drawn.
Basically you want to move the circle to the place the user touched the screen, using some type of constant speed?
If all you want is to move a circle the Timer can do it - or you could use something like: https://github.com/MasayukiSuda/FPSAnimator
Finally, are you always looking for a linear straight line constant speed movement? Then your math is fine. If your looking for something with gravity etc. then you can reference this: https://developer.android.com/guide/topics/graphics/physics-based-animation.html and consider this: https://github.com/google/liquidfun
This will move the circle exactly to the point the user touched:
theta = atan2(touchY - y,touchX - x)
speedX = cos(theta)
speedY = sin(theta)
x += speedX
y += speedY
What happens once the circle reaches the point depends on how the calculation is implemented. If you want the circle to continue on its path and travel the direction infinitely, you must only calculate the velocity of x and y once. That way the same x and y velocity will always be used to update the position of the circle.
If the velocities are recalculated every time the position of the circle is to be updated, the circle will continuously move to the point even once it has technically reached it and must manually be stopped.
The velocities can be increased by multiplying them by a value greater than 1. To maintain the correct direction, the value should be the same for both velocities.
Example:
theta = atan2(touchY - y,touchX - x)
speedX = maxSpeed*cos(theta)
speedY = maxSpeed*sin(theta)
x += speedX
y += speedY
The animation I am working on achieving is shown below in Figure 1.
1)I have a list containing points on a 2D plane(left) that I am working on animating.
2)A touch is made at location x, and an invisible circle is drawn (middle). I want all of the points contained in that circle to move away from the center (x).
3) A final result example (right)
I am planning on applying this in a way that I can supply any list of points, touch location, numFrames, and force applied on each point per frame. An array of length numFrames is returned, each array item being an already animated list.
Since I plan on implementing this on screen touch, it is possible for another touch to happen while the first touch animation is already in progress. How can I handle these collisions?
Figure 1:
here's my 2 cents, you need a list of points, these points should have a position, a velocity and a method to update the position. something like this
public class Point {
public float x, y;
public boolean isMoving;
private float dx,dy;
public Point(float x, float y){
this.x = x;
this.y = y;
}
public boolean move(float maxWidth, float maxHeight){
if(!isMoving) return false;
// update the position
x += dx;
y += dy;
dx /= FRICTION;
dy /= FRICTION;
// bounce...?
if(x < 0 || x > maxWidth){
dx *= -1;
}
if(y < 0 || y > maxHeight){
dy *= -1;
}
if(Math.abs(dx) < MOVEMENT_THRESHOLD && Math.abs(dy) < MOVEMENT_THRESHOLD){
isMoving = false;
}
return isMoving;
}
}
on every touch event you apply a force to every point within the radius and set their velocity
for(Point point : mPoints){
float distance = point.distance(x,y);
if(distance > mTouchRange) continue;
float force = (float) Math.pow(1 - (distance / mTouchRange), 2) * mForceFactor;
float angle = point.angle(x,y);
point.dx -= Math.cos(angle) * force;
point.dy -= Math.sin(angle) * force;
point.isMoving = true;
}
then you need an animation that call move on every frame and eventully stops when there are no moving points
you can find the full example here
Im using below code to draw line on bitmap canvas while finger touch move... here i posted partial code and it is working fine..
As shown in below image, the black and white bitmap erased on touch drag.. I made canvas transparent so the parent layout background(color image) is getting visible.
I want to know , how much area is erased(like 50% or 60% of bitmap ).. is there any way to find that?
//Erasing paint
mDrawPaint = new Paint();
mDrawPaint.setAntiAlias(true);
mDrawPaint.setDither(true);
mDrawPaint.setStyle(Paint.Style.STROKE);
mDrawPaint.setStrokeJoin(Paint.Join.ROUND);
mDrawPaint.setStrokeCap(Paint.Cap.ROUND);
mDrawPaint.setStrokeWidth(50);
mDrawPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
BlurMaskFilter mBlur = new BlurMaskFilter(10, BlurMaskFilter.Blur.NORMAL);
mDrawPaint.setMaskFilter(mBlur);
private void doDraw(Canvas c) {
c.drawBitmap(mBitmap, 0, 0,null );
}
private float mX, mY;
private static final float TOUCH_TOLERANCE = 1;
void touch_start(float x, float y) {
mPath.reset();
mPath.moveTo(x, y);
mX = x;
mY = y;
}
void touch_move(float x, float y) {
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
mPath.quadTo(mX, mY, (x + mX)/2, (y + mY)/2);
mX = x;
mY = y;
}
canvas.drawPath(mPath, mDrawPaint ); //Erasing Black and white image
}
void touch_up() {
mPath.lineTo(mX, mY);
// commit the path to our offscreen
mCanvas.drawPath(mPath, mDrawPaint);
// kill this so we don't double draw
mPath.reset();
}
Try to use Monte Carlo method to estimate percentage of transparent area. I think it is a fastest and easiest way to do this. Take about 50 (depends on accuracy you need) random pixels on your transparency mask and check their color. Then calc ans = TransparentPixelsCount/TestPixelCount.
It is very hard to calculate square of user's drawings using path coordinates. And it's quite long to iterate over all pixels. So, IMHO Monte Carlo is your choise.
To get an exact (and slow) answer, you need to inspect every pixel and count the number are transparent and divide by the total number of pixels. If your requirements allow for some estimation, it is probably best to sample the image.
You could downsize the image and run and the above procedure on the smaller image. That has the disadvantage that the scaling operation might be going through all the pixels making it slow. I would recommend a grid sampling, it is similar to downsizing, but skips over pixels. Basically, we evenly space x sample points on a grid over the image. Then count the number of sample points that are transparent. The estimate of transparent percentage is the total transparent samples/number of transparent samples. You can get reasonable accuracy (usually within 5%) with a small number, say 100, samples. Here is a code function that implements this method -- bm is the Bitmap and scale is the number of samples per axis, so setting scale = 10 gives 100 total samples (10x10 sampling grid over the image).
static public float percentTransparent(Bitmap bm, int scale) {
final int width = bm.getWidth();
final int height = bm.getHeight();
// size of sample rectangles
final int xStep = width/scale;
final int yStep = height/scale;
// center of the first rectangle
final int xInit = xStep/2;
final int yInit = yStep/2;
// center of the last rectangle
final int xEnd = width - xStep/2;
final int yEnd = height - yStep/2;
int totalTransparent = 0;
for(int x = xInit; x <= xEnd; x += xStep) {
for(int y = yInit; y <= yEnd; y += yStep) {
if (bm.getPixel(x, y) == Color.TRANSPARENT) {
totalTransparent++;
}
}
}
return ((float)totalTransparent)/(scale * scale);
}
For reference, the slow method that would give you the results by counting every pixel is below. It can be used for reference on testing the above estimator.
static public float percentTransparent(Bitmap bm) {
final int width = bm.getWidth();
final int height = bm.getHeight();
int totalTransparent = 0;
for(int x = 0; x < width; x++) {
for(int y = 0; y < height; y++) {
if (bm.getPixel(x, y) == Color.TRANSPARENT) {
totalTransparent++;
}
}
}
return ((float)totalTransparent)/(width * height);
}
A different approach on this: you can calculate the size of each path using ComputeBounds. Then it should be simple to compare this with the size of your view and decide the % of the drawing.
Jus you need to keep in mind that the path can be drawn over itself, so you need to be careful and handle that in the calculation.
Store all point x and y value in two different sorted sets, one for x value of point and other for y value of point.
The final value of your bound will be point(min_x,min_y) and point(max_x,max_y).
You need to detect the points lying inside the drawn polygon.
Here is the functions which takes array that contains all the drawn point, and second parameter are the points itself i.e. x ,y.
// Return true if the dot { x,y } is within any of the polygons in the list
function pointInPolygons( polygons, dot )
for (i=1, [polygons count] i++)
{
if (pointInPolygon( polygons[i], dot ))
return true
}
return false
end
// Returns true if the dot { x,y } is within the polygon
//defined by points table { {x,y},- --{x,y},{x,y},... }
function pointInPolygon( points, dot )
local i, j = #points, #points
local oddNodes = false
for i=1, #points do
if ((points[i].y < dot.y and points[j].y>=dot.y
or points[j].y< dot.y and points[i].y>=dot.y) and (points[i].x<=dot.x
or points[j].x<=dot.x)) then
if (points[i].x+(dot.y-points[i].y)/(points[j].y-points[i].y)*(points[j].x-points[i].x)<dot.x) then
oddNodes = not oddNodes
end
end
j = i
end
return oddNodes
end
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.