I need to animate the drawing of a line graph. I'll receive an array of values (probably between 0 and 10) and that has to go on a graph over time. I want the line graph to be drawn, and the drawing should be visible; animated.
I've looked at Anders Ericsson's tutorial here: http://www.jayway.com/2012/08/29/creating-custom-android-views-part-3-animating-your-custom-views-smoothly/#comment-105813
The code here: https://github.com/andersericsson/ViewTutorialPart3
But I can't seem to swing it to work my way.
Is there another solution out there?
The solution I ended up using involved some algebra in the class where I extended View.
The function gets the array of scores (values between 0 and 10) from the activity and uses the values two-by-two as start and end points to a line (note that the end point of one line is the start point of the next line).
It calculates the length of the line and how many segments will be in the line; also how far to move in the x direction and how for to move in the y direction.
Then it calculates the x and y values of the end of the first segment in the line and draws that segment.
Then the xDirection and yDirection is added to the x and y points respectively and the line is drawn again, which now includes the first and second segment of the line. This is done for every segment in the line, and then the final line from point A to point B is drawn.
But it's not complete - the entire for loop in the setData function should be placed in a recursive function, because postInvalidateDelayed() doesn't pause the for loop from executing.
However, nothing is drawn on the canvas at all, hence the link to my other question currently on SO: Why are no lines drawn on my (custom View) canvas?
But for this question, I think that the solution I ended up using is probably not so bad. Comments?
public void setData(float[] scorePoints, float max, int totalScores){
Log.d(TAG, "Get stuff from activity");
scores = scorePoints;
numberOfScores = totalScores;
Log.d(TAG, "Number of scores = " + numberOfScores);
maxScore = max;
Log.d(TAG, "Max score = " + maxScore);
segmentToDraw = (float) 10;
//get the width of the area to draw
width = Math.abs(getWidth() - getPaddingLeft() - getPaddingRight());
//get the height of the area to draw
height = getHeight() - getPaddingTop() - getPaddingBottom();
Log.d(TAG, "Drawable area in view = " + width + " x " + height);
/*
* Now we start filling an array of points.
* The onDraw function will drawLines of groupings of four points in a given array.
*
* For the first segment, we'll draw from the x and y of the first point (which will be in the 1st and 2nd spots in our array)
* to the x and y of the first segment (which will be in the 3rd and 4th spots in our array).
* And then the 3rd and 4th spots in our array will be replaced by a new x and y
* marking the end of the second segment to be drawn from our first point.
*
* This will happen until the x and y is not smaller than the x and y of the final point of the line, any more.
* Then the 3rd and 4th spots in our array will be replaced by the x and y of the final point of the line.
*
* If there are more points to draw, the 5th and 6th spots in our array will also be created and filled with
* the x and y of the final point of the line because it'll be the first two values (x and y) for drawing the next line.
*
* So, yes, there will be duplicates in our array of points to draw, but a grouping of four spots will be used to draw one line,
* and the end point of the first line is the starting point of the second line, so we need it twice.
*/
points.add(getXPos(scores[0]));
points.add(getYPos(scores[0]));
points.add((float) 0);
points.add((float) 0);
x = points.get(0);
y = points.get(1);
startPoint = scores[0];
endPoint = scores[1];
for(int i=0; i<scores.length-1; i++){
String thePoints = "";
if(i>0){
startPoint = scores[i];
endPoint = scores[i+1];
x = points.get(i*4);
y = points.get((i*4) + 1);
}
startPointX = getXPos(startPoint);
startPointY = getYPos(startPoint);
endPointX = getXPos(endPoint);
endPointY = getYPos(endPoint);
distanceOfLine = (float) Math.sqrt(Math.pow((endPointX - startPointX), 2) + Math.pow((endPointY - startPointY), 2));
Log.d(TAG, "Distance of line = " + distanceOfLine);
//get number of segments in line
numberOfSegmentsInLine = (int) (distanceOfLine/segmentToDraw);
Log.d(TAG, "Number of segments in line = " + numberOfSegmentsInLine);
//get distance to move in Y direction
yDirection = (float) ((endPointY - startPointY)/ (float) numberOfSegmentsInLine);
Log.d(TAG, "Move " + yDirection + " in Y direction");
//get distance to move in X direction
xDirection = (float) ((endPointX - startPointX)/ (float) numberOfSegmentsInLine);
Log.d(TAG, "Move " + xDirection + " in X direction");
//loop for each segment
for(int j=0; j<numberOfSegmentsInLine; j++){
x += xDirection;
y += yDirection;
points.set(points.size()-2, Float.valueOf(x));
points.set(points.size()-1, Float.valueOf(y));
Log.d(TAG, "Line : " + (i+1) + " Segment : " + j);
Log.d(TAG, "X = "+ (x+xDirection) + " Y = " + (y+yDirection));
Log.d(TAG, "Call invalidate now!");
//invalidate();
//postInvalidateDelayed(delayMilliseconds);
}
//draw final line
points.set(points.size()-2, endPointX);
points.set(points.size()-1, endPointY);
invalidate();
//postInvalidateDelayed(delayMilliseconds);
if(i<scores.length-2){
points.add(endPointX);
points.add(endPointY);
points.add((float) 0);
points.add((float) 0);
}
for(int k =0; k<points.size(); k++){
thePoints = thePoints + " : " + points.get(k);
}
Log.d(TAG, "Points array = " + thePoints);
}
}
#Override
public void onDraw(Canvas canvas){
//setWillNotDraw(true);
Log.d(TAG, "DRAW DAMNIT!!!");
Log.d(TAG, "Width = " + (int) width + " Height = " + (int)height);
paint = new Paint();
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(4);
paint.setColor(Color.RED);
//paint.setAntiAlias(true);
//paint.setShadowLayer(4, 2, 2, 0x81000000);
Bitmap bitmap = Bitmap.createBitmap((int)width, (int)height, Bitmap.Config.ARGB_8888);
canvas = new Canvas(bitmap);
String drawPointsGo = "";
float[] drawPoints = new float[points.size()];
for(int i=0; i<points.size(); i++){
Float f = points.get(i);
drawPoints[i] = (float) (f != null ? f : 0.0);
drawPointsGo = drawPointsGo + " : " + drawPoints[i];
}
Log.d(TAG, "Draw Points: " + drawPointsGo);
canvas.drawLines(drawPoints, paint);
}
Try looking into CountDownTimer or Runnable so that onTick() or when it's time to post you can update your line by drawing a few more pixels. This would let you draw one line after the other but would give the effect of animation.
Here is the general direction to try:
Make sure you are comfortable with Canvas drawing.
Then you will want to create a new View (subclass it) and override the onDraw method.
This method should only draw the graph partially, based on the elapsed time since the start of the animation.
Related
I 'd like someone to advise me a way to find the coordinates of a point on a sprite in libgdx .
As you can see from the image I have set the sprite with the point of origin on the red dot , and I can not change it.
I would leave the red dot as the source and find the coordinates of the green on the same sprite .
Thanks for your help.
EDIT
#Override
public void create () {
img = new Texture("rocket.png");
font = new BitmapFont();
font.setColor(Color.BLUE);
MyInputProcessor inputProcessor = new MyInputProcessor();
Gdx.input.setInputProcessor(inputProcessor);
float w = Gdx.graphics.getWidth();
float h = Gdx.graphics.getHeight();
sprite = new Sprite(img);
spacesprite = new Sprite(new Texture(Gdx.files.internal("space.jpg")));
spacesprite.setPosition(0,0);
spacesprite.setSize(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
point = new Sprite(new Texture(Gdx.files.internal("point.png")));
batch = new SpriteBatch();
}
#Override
public void render () {
sprite.setPosition(Gdx.graphics.getWidth() / 2 - sprite.getWidth()/2, Gdx.graphics.getHeight() / 2 - sprite.getHeight()/2);
sprite.setOrigin(sprite.getWidth()/2, sprite.getHeight()/2);
point.setPosition(sprite.getX() + sprite.getWidth()/2 - point.getWidth()/2, sprite.getY() + sprite.getHeight()/2);
point.setOrigin(point.getWidth()/2, 0);
if(Gdx.input.isButtonPressed(Input.Buttons.LEFT)){
//sprite.setPosition(Gdx.input.getX() - sprite.getWidth()/2, Gdx.graphics.getHeight() - Gdx.input.getY() - sprite.getHeight()/2);
if(Gdx.input.getX() < Gdx.graphics.getWidth() / 2)
{
//System.out.println("x: " + Gdx.input.getX() + " - y: " + Gdx.input.getY());
sprite.setRotation(rotation++);
point.setRotation(rotation++);
System.out.println("Sprite: X" + sprite.getX() + " - Y:" + sprite.getY());
}
else
{
//System.out.println("x: " + Gdx.input.getX() + " - y: " + Gdx.input.getY());
sprite.setRotation(rotation--);
point.setRotation(rotation--);
System.out.println("Sprite: X" + sprite.getX() + " - Y:" + sprite.getY());
}
}
batch.begin();
spacesprite.draw(batch);
sprite.draw(batch);
point.draw(batch);
batch.end();
}
someone can adapt the code , when I rotate I'd get the position , but they are insecure about my implementation .
if you're looking for a middle point on the rectangular sprite, try something like this:
float x = obj.getOriginX() + obj.getHeight();
float y = obj.getOriginY() + obj.getWidth() / 2;
Either use Gonio, matrix or quaternions to calculate the rotated coordinates.
For a few simple calculations I'd use gonio, something like:
float angle_rad = sprite.getRotation() / 180.0f * PI;
float rotated_x = Math.sin(angle_rad) * y + Math.cos(angle_rad) * x;
float rotated_y = Math.sin(angle_rad) * x + Math.cos(angle_rad) * y;
If you have to do this more often look into matrices or quaternions, matrices are a little easier though: http://en.wikipedia.org/wiki/Rotation_matrix
I'm in a tremendous bind with a last minute request on a consulting project I'm working on.
Essentially here is what I am trying to accomplish:
I have a surfaceview that draws a series of randomly sized circles. Each circle can have a radius from 50-100.
The x,y values are randomly generated along with a random radius
Each circle is created as an object representing that circle (x, y coord's and radius) and it is added to a list.
Once they are all created they are drawn.
The problem is I want to make sure none of these circles overlap.
I'm struggling a bit. This seems like it's shouldn't be all that difficult but it is for me unfortunately.
Here's my code so far (I know it's not close...be kind):
x = 100 + (int) (Math.random() * (mCanvasWidth - 200));
y = 100 + (int) (Math.random() * (mCanvasHeight - 200));
radius = 50 + (int) (Math.random() * 99);
color[0] = (float) (Math.random() * 360);
color[1] = 1;
color[2] = 1;
String radVal = String.valueOf(radius);
circle circ = new circle(x, y, radius, Color.HSVToColor(128, color), radVal);
boolean addit = true;
for (dot d : Dots) {
int leftSide = d.get_x() - radius;
int rightSide = d.get_x() + radius;
int xBoundary = x + radius;
int yBoundary = y + radius;
int exist_xLeft = d.get_x() - d.get_radius();
int exist_xRight = d.get_x() + d.get_radius();
int exist_yTop = d.get_y() - d.get_radius();
int exist_yBottom = d.get_y() + d.get_radius();
if ((xBoundary > exist_xLeft) && (xBoundary < exist_xRight))
{
if (yBoundary > (exist_yTop) && (yBoundary < exist_yBottom)) {
addit = false;
break;
}
}
}
if (addit)
circles.add(mdot);
if (circles.size() >= 5)
running = false;
Then it iterates the circles list and draws them to the canvas.
Any suggestions on where I'm failing in the collision detection?
You can detect if 2 circles are colliding like this:
Given:
centerpoints cx1,cy1 & cx2,cy2
and given radii r1 & r2,
Then you can determine if the 2 circles are colliding:
areColliding=((cx2-cx1)*(cx2-cx1)+(cy2-cy1)*(cy2-cy1))<((r1+r2)*(r1+r2));
I'd like to calculate a point on a quadratic curve. To use it with the canvas element of HTML5.
When I use the quadraticCurveTo() function in JavaScript, I have a source point, a target point and a control point.
How can I calculate a point on the created quadratic curve at let's say t=0.5 with "only" knowing this three points?
Use the quadratic Bézier formula, found, for instance, on the Wikipedia page for Bézier Curves:
In pseudo-code, that's
t = 0.5; // given example value
x = (1 - t) * (1 - t) * p[0].x + 2 * (1 - t) * t * p[1].x + t * t * p[2].x;
y = (1 - t) * (1 - t) * p[0].y + 2 * (1 - t) * t * p[1].y + t * t * p[2].y;
p[0] is the start point, p[1] is the control point, and p[2] is the end point. t is the parameter, which goes from 0 to 1.
In case somebody needs the cubic form:
//B(t) = (1-t)**3 p0 + 3(1 - t)**2 t P1 + 3(1-t)t**2 P2 + t**3 P3
x = (1-t)*(1-t)*(1-t)*p0x + 3*(1-t)*(1-t)*t*p1x + 3*(1-t)*t*t*p2x + t*t*t*p3x;
y = (1-t)*(1-t)*(1-t)*p0y + 3*(1-t)*(1-t)*t*p1y + 3*(1-t)*t*t*p2y + t*t*t*p3y;
I created this demo :
// x = a * (1-t)³ + b * 3 * (1-t)²t + c * 3 * (1-t)t² + d * t³
//------------------------------------------------------------
// x = a - 3at + 3at² - at³
// + 3bt - 6bt² + 3bt³
// + 3ct² - 3ct³
// + dt³
//--------------------------------
// x = - at³ + 3bt³ - 3ct³ + dt³
// + 3at² - 6bt² + 3ct²
// - 3at + 3bt
// + a
//--------------------------------
// 0 = t³ (-a+3b-3c+d) + => A
// t² (3a-6b+3c) + => B
// t (-3a+3b) + => c
// a - x => D
//--------------------------------
var A = d - 3*c + 3*b - a,
B = 3*c - 6*b + 3*a,
C = 3*b - 3*a,
D = a-x;
// So we need to solve At³ + Bt² + Ct + D = 0
Full example here
may help someone.
I edited talkhabis answer (cubic curve) so the curve is displayed with the right coordinates. (Couldn't comment)
The Y-coordinates needed to be changed (-p[].y+150). (A new variable for that might be a nicer and more efficient solution, but you get the idea)
// Apply points to SVG and create the curve and controllers :
var path = document.getElementById('path'),
ctrl1 = document.getElementById('ctrl1'),
ctrl2 = document.getElementById('ctrl2'),
D = 'M ' + p0.x + ' ' + (-p0.y+150) +
'C ' + c0.x + ' ' + (-c0.y+150) +', ' + c1.x + ' ' + (-c1.y+150) + ', ' + p1.x + ' ' + (-p1.y+150);
path.setAttribute('d',D);
ctrl1.setAttribute('d','M'+p0.x+','+(-p0.y+150)+'L'+c0.x+','+(-c0.y+150));
ctrl2.setAttribute('d','M'+p1.x+','+(-p1.y+150)+'L'+c1.x+','+(-c1.y+150));
// Lets test the "Bezier Function"
var t = 0, point = document.getElementById('point');
setInterval(function(){
var p = Bezier(p0,c0,c1,p1,t);
point.setAttribute('cx',p.x);
point.setAttribute('cy',-p.y+150);
t += 0.01;
if(t>=1) t=0;
},50);
// OK ... Now tring to get "y" on cruve based on mouse "x" :
var svg = document.getElementById('svg'),
point2 = document.getElementById('point2');
svg.onmousemove = function(e){
var x = (e.pageX - 50)/2,
y = (e.pageY - 50)/2;
// "-50" because of "50px margin" on the left side
// and "/2" because the svg width is 300 units and 600 px => 300 = 600/2
// Get the x,y by mouse x
var p = YBX(p0,c0,c1,p1,x);
point2.setAttribute('cx',p.x);
point2.setAttribute('cy',-p.y+150);
}
http://jsfiddle.net/u214gco8/1/
I also created some C-Code to test the results for the cubic curve. Just enter the X and Y coordinates in the main function.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
void bezierCurve(int x[] , int y[])
{
double xu = 0.0 , yu = 0.0 , u = 0.0 ;
int i = 0 ;
for(u = 0.0 ; u <= 1.0 ; u += 0.05)
{
xu = pow(1-u,3)*x[0]+3*u*pow(1-u,2)*x[1]+3*pow(u,2)*(1-u)*x[2]
+pow(u,3)*x[3];
yu = pow(1-u,3)*y[0]+3*u*pow(1-u,2)*y[1]+3*pow(u,2)*(1-u)*y[2]
+pow(u,3)*y[3];
printf("X: %i Y: %i \n" , (int)xu , (int)yu) ;
}
}
int main(void) {
int x[] = {0,75,50,300};
int y[] = {0,2,140,100};
bezierCurve(x,y);
return 0;
}
https://ideone.com/glLXcB
Just a note: If you are using the usual formulas presented here then don't expect t = 0.5 to return the point at half of the curve's length.. In most cases it won't.
More on this here under "§23 — Tracing a curve at fixed distance intervals" and here.
here's my sample from the libgdx android game that I want to create. Nothing special yet, cause I'm just starting my adventure with Android games.
Here is a couple of questions I would like to get answered
So far I am using the Gdx.input.isTouched function and check if that matches the bunny coordinates, I do that for each bunny and at the end I would like to have 11 of them and that's just too much to write. Is there a way to check if the Image class from import com.badlogic.gdx.scenes.scene2d.ui.Image; was touched? Or else does the Actor class has that feature?
Another thing is that when I instantiate the bunny class and I click the bunny, all of them are changing to "scared". It's because the Scene2d.Image texture has to be static. Is there a way to change that?
if(Gdx.input.isTouched()){
x = Gdx.input.getX();
y = Gdx.graphics.getHeight() - Gdx.input.getY();
// **** Show Coordinates **** \\
if (x < 420)
font.draw(batch, "x:" + x +
"\n y: " + y, x, y);
else
font.draw(batch, "x:" + x +
" y: " + y, x-65, y);
// **** End Show Coordinates **** \\
//if krolik (bunny) is touched add highscore and change texture to scared
if (x >= krolik.pos.x && y >= krolik.pos.y
&& x <= krolik.pos.x + 64 && y <= krolik.pos.y + 64)
{
krolik.scared();
highscore+=100;
}
else if (x >= krolik2.pos.x && y >= krolik2.pos.y
&& x <= krolik2.pos.x + 64 && y <= krolik2.pos.y + 64)
{
krolik2.scared();
highscore+=100;
}
}
else{
krolik.normal();
}
// **** Show Highscore **** \\
font.draw(batch, "Highscore: " + highscore, 350, 300);
batch.draw(krolik.getTexture(), krolik.pos.x, krolik.pos.y);
batch.draw(krolik2.getTexture(), krolik2.pos.x, krolik2.pos.y);
batch.end();
Why is it that you are using Image but not drawing with Stage? Image and other Actors are for using Stage which can take care of your touch events and drawing very easily.
Create them like this:
for (int i = 0; i < KROLIKS_COUNT; i++) {
final Image krolik = new Image(nonScaredTextureRegion);
krolik.setClickListener(new ClickListener() {
#Override
public void click(Actor actor, float x, float y) {
krolik.setRegion(scaredTextureRegion);
highscore+=100;
}
});
krolik.x = startPosX;
krolik.y = startPosY;
stage.addActor(krolik);
}
set the stage as the input processor:
Gdx.input.setInputProcessor(stage);
then in render, simply:
stage.act(Gdx.graphics.getDeltaTime());
stage.draw();
I've been working on Android for a while and would like to know if it is possible to retrieve the position of a button in android.
My target is to get the X & Y coordinates and print them on the LOGCAT.
Some example to show me how would be appreciated.
Thanks
Sure, you can get these, make sure the views are drawn atleast once before you try to get the positions. You could try to get the positions in onResume() and try these functions
view.getLocationInWindow()
or
view.getLocationOnScreen()
or if you need something relative to the parent, use
view.getLeft(), view.getTop()
Links to API definitions:
getLocationInWindow
getLocationOnScreen
getLeft
getTop
Like Azlam said you can use View.getLocationInWindow() to get the coordinates x,y.
Here is an example:
Button button = (Button) findViewById(R.id.yourButtonId);
Point point = getPointOfView(button);
Log.d(TAG, "view point x,y (" + point.x + ", " + point.y + ")");
private Point getPointOfView(View view) {
int[] location = new int[2];
view.getLocationInWindow(location);
return new Point(location[0], location[1]);
}
Bonus - To get the center point of the given view:
Point centerPoint = getCenterPointOfView(button);
Log.d(TAG, "view center point x,y (" + centerPoint.x + ", " + centerPoint.y + ")");
private Point getCenterPointOfView(View view) {
int[] location = new int[2];
view.getLocationInWindow(location);
int x = location[0] + view.getWidth() / 2;
int y = location[1] + view.getHeight() / 2;
return new Point(x, y);
}
I hope the example can still be useful to someone.
buttonObj.getX();
buttonObj.getY();