I'm writing an android program and i just ran into a problem.
My program serves to create graphs, run some specific algorithms on them (Dijsktra, BelmanFord, etc.), save the graphs to SD Card and loading them back.
The problem: If i save a little bigger, more complex graph i get a stackoverflow error..
Serialization:
public void createExternalStoragePublicGraph(String filename) {
File dir = Environment.getExternalStoragePublicDirectory("grapher");
try {
if (!dir.exists()) {
dir.mkdirs();
}
File file = new File(dir, filename);
FileOutputStream fileOutputStream = new FileOutputStream(file);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(graphDrawerView.getGraph());
objectOutputStream.close();
} catch (IOException e) {
Log.w("ExternalStorage", "Error Writing" + filename, e);
}
}
Deserialization:
public Graph loadExternalStoragePublicGraph(String filename) {
File file = new File(Environment.getExternalStoragePublicDirectory("grapher"), filename);
Graph graph = null;
try {
FileInputStream fint = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(fint);
graph = (Graph) ois.readObject();
ois.close();
} catch (Exception e) {
Log.e("Deserialization error:", e.getMessage());
e.printStackTrace();
}
return graph;
}
Graph class:
package com.cslqaai.grapher;
import android.util.Log;
import java.io.Serializable;
import java.util.LinkedList;
public class Graph implements Serializable {
private String name;
private boolean directed = false;
private boolean weighted = false;
private LinkedList<Vertex> vertexes = new LinkedList<Vertex>();
private LinkedList<Edge> edges = new LinkedList<Edge>();
// --------------------------------------------------------------------------------------------------------------------------------------
public Graph(boolean weighted, boolean directed) {
this.directed = directed;
this.weighted = weighted;
}
// --------------------------------------------------------------------------------------------------------------------------------------
public void add(AbstractGraphObject ago) {
if (ago instanceof Vertex) {
this.vertexes.add((Vertex) ago);
} else if (ago instanceof Edge) {
this.edges.add((Edge) ago);
} else {
}
}
// --------------------------------------------------------------------------------------------------------------------------------------
public boolean isWeighted() {
return this.weighted;
}
// --------------------------------------------------------------------------------------------------------------------------------------
public boolean isDirected() {
return this.directed;
}
// --------------------------------------------------------------------------------------------------------------------------------------\
public LinkedList<Edge> getEdges() {
return this.edges;
}
// --------------------------------------------------------------------------------------------------------------------------------------
public LinkedList<Vertex> getVertexes() {
return this.vertexes;
}
// --------------------------------------------------------------------------------------------------------------------------------------
public void reset() {
for (int i = 0; i < this.edges.size(); i++) {
this.edges.get(i).setColorToDefault();
}
}
// --------------------------------------------------------------------------------------------------------------------------------------
void remove(AbstractGraphObject ago) {
if (ago instanceof Vertex) {
Log.d("Graph", "Remove Vertex from graph");
this.vertexes.remove((Vertex) ago);
} else if (ago instanceof Edge) {
Log.d("Graph", "Remove Edge to graph");
this.edges.remove((Edge) ago);
}
}
// --------------------------------------------------------------------------------------------------------------------------------------
public void setWeighted(boolean weighted) {
this.weighted = weighted;
}
// --------------------------------------------------------------------------------------------------------------------------------------
public void setDirected(boolean directed) {
this.directed = directed;
}
// --------------------------------------------------------------------------------------------------------------------------------------
public String getName() {
return this.name;
}
// --------------------------------------------------------------------------------------------------------------------------------------
public void setName(String name) {
this.name = name;
}
}
// ----------------------------------------------------------------------------------------------------------------------------------------
Vertex class:
package com.cslqaai.grapher;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
public class Vertex extends AbstractGraphObject {
public static final int RADIUS_SIZE = 20;
public static final int SURROUNDING_RADIUS_SIZE = 30;
private static Layer defaultVertexLayer = AbstractGraphObject.defaultLayer;
private static int no = 0;
private static Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private static Paint paintColored = new Paint(Paint.ANTI_ALIAS_FLAG);
private static Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private static Paint textBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final int id;
private String name = null;
private Coord coord;
private Coord cachedCoord;
private ArrayList<Edge> edges = new ArrayList<Edge>();
private boolean colored = false;
// --------------------------------------------------------------------------------------------------------------------------------------
static {
Vertex.paint.setColor(0xFF0FF5F5);
Vertex.paintColored.setColor(Color.RED);
Vertex.textPaint.setStyle(Paint.Style.FILL);
Vertex.textPaint.setColor(Color.BLACK);
Vertex.textPaint.setTextAlign(Paint.Align.CENTER);
Vertex.textPaint.setTextSize(20);
Vertex.textPaint.setTypeface(Typeface.create("Helvetica", Typeface.BOLD));
Vertex.textBgPaint.setStyle(Paint.Style.FILL);
Vertex.textBgPaint.setColor(0xFF0FF5F5);
}
// --------------------------------------------------------------------------------------------------------------------------------------
public Vertex(Coord coord) {
super(Vertex.defaultVertexLayer);
this.id = Vertex.no++;
this.coord = coord;
this.recalculate();
}
// --------------------------------------------------------------------------------------------------------------------------------------
public Vertex(int x, int y) {
this(new Coord(x, y));
}
// --------------------------------------------------------------------------------------------------------------------------------------
#Override
public void recalculate() {
this.cachedCoord = new Coord(Math.round((Vertex.baseX + this.coord.getX()) * Vertex.scaleFactor), Math.round((Vertex.baseY + this.coord.getY()) * Vertex.scaleFactor));
this.onScreen = this.cachedCoord.getX() + Vertex.RADIUS_SIZE > 0 && this.cachedCoord.getY() + Vertex.RADIUS_SIZE > 0
&& this.cachedCoord.getX() - Vertex.RADIUS_SIZE < Vertex.screenWidth && this.cachedCoord.getY() - Vertex.RADIUS_SIZE < this.cachedCoord.getY();
}
// --------------------------------------------------------------------------------------------------------------------------------------
public Coord getCoord() {
return this.coord;
}
// --------------------------------------------------------------------------------------------------------------------------------------
public Coord getCachedCoord() {
return this.cachedCoord;
}
// --------------------------------------------------------------------------------------------------------------------------------------
public int getRadiusSize() {
return Vertex.RADIUS_SIZE;
}
// --------------------------------------------------------------------------------------------------------------------------------------
#Override
public void draw(Canvas canvas) {
this.recalculate();
if (!this.onScreen) {
return;
}
canvas.drawCircle(this.cachedCoord.getX(), this.cachedCoord.getY(), Vertex.RADIUS_SIZE * Vertex.scaleFactor, Vertex.paint);
if (this.name != null) {
float width = Vertex.textPaint.measureText(this.name) + 10;
float height = Vertex.textPaint.getTextSize() + 5;
canvas.drawRect(this.cachedCoord.getX() - width / 2, this.cachedCoord.getY() - height / 2, this.cachedCoord.getX() + width / 2, this.cachedCoord.getY() + height / 2, Vertex.textBgPaint);
canvas.drawText(this.name, this.cachedCoord.getX(), this.cachedCoord.getY() + height * 0.25f, Vertex.textPaint);
}
}
// --------------------------------------------------------------------------------------------------------------------------------------
private boolean searchingCoordOn(float radius, float pX, float pY) {
return this.onScreen && ((Math.pow(pX - this.cachedCoord.getX(), 2) + Math.pow(pY - this.cachedCoord.getY(), 2)) < (Math.pow(radius * Vertex.scaleFactor, 2)));
}
// --------------------------------------------------------------------------------------------------------------------------------------
public boolean isOnCoord(float pX, float pY) {
return this.searchingCoordOn(Vertex.RADIUS_SIZE, pX, pY);
}
// --------------------------------------------------------------------------------------------------------------------------------------
public boolean isInCoordSurroundings(float pX, float pY) {
return this.searchingCoordOn(Vertex.SURROUNDING_RADIUS_SIZE, pX, pY);
}
// --------------------------------------------------------------------------------------------------------------------------------------
public void addEdge(Edge edge) {
if (!this.edges.contains(edge)) {
this.edges.add(edge);
}
}
// --------------------------------------------------------------------------------------------------------------------------------------
public ArrayList<Edge> getEdges() {
return this.edges;
}
// --------------------------------------------------------------------------------------------------------------------------------------
public void removeEdge(Edge edge) {
if (this.edges.contains(edge)) {
this.edges.remove(edge);
}
}
// --------------------------------------------------------------------------------------------------------------------------------------
public boolean hasEdge(Edge edge) {
return this.edges.contains(edge);
}
// --------------------------------------------------------------------------------------------------------------------------------------
public static void setDefaultLayer(Layer layer) {
Vertex.defaultVertexLayer = layer;
}
// --------------------------------------------------------------------------------------------------------------------------------------
public static Layer getDefaultLayer() {
return Vertex.defaultVertexLayer;
}
// --------------------------------------------------------------------------------------------------------------------------------------
public static void remove(Vertex vertex) {
Iterator<Edge> edges = vertex.getEdges().iterator();
while (edges.hasNext()) {
Edge e = edges.next();
edges.remove();
Edge.remove(e);
}
Vertex.defaultVertexLayer.remove(vertex);
if (Vertex.graph != null) {
Vertex.graph.remove(vertex);
}
}
// --------------------------------------------------------------------------------------------------------------------------------------
public boolean hasName() {
return this.name != null;
}
// --------------------------------------------------------------------------------------------------------------------------------------
public void setName(String name) {
this.name = name;
}
// --------------------------------------------------------------------------------------------------------------------------------------
public String getName() {
return this.name;
}
// --------------------------------------------------------------------------------------------------------------------------------------
public int getId() {
return this.id;
}
// --------------------------------------------------------------------------------------------------------------------------------------
}
Edge class:
package com.cslqaai.grapher;
import android.graphics.*;
import java.util.ArrayList;
import java.util.LinkedList;
public class Edge extends AbstractGraphObject {
public static final float STROKE_WIDTH = 5f;
public static final float SENSOR_WIDTH = 50f;
public static final float SURROUNDING_SENSOR_WIDTH = 15f;
public static final float TRIANGLE_SIZE = 8f;
private static Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private static Paint paintColored = new Paint(Paint.ANTI_ALIAS_FLAG);
private static Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private static Paint textBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private static Layer defaultEdgeLayer = AbstractGraphObject.defaultLayer;
private static int no = 0;
private final int id;
private int weight = 1;
private Coord cachedSourceCoord;
private Coord cachedTargetCoord;
private Vertex sourceVertex;
private Vertex targetVertex;
private Coord weightCoord;
private boolean colored = false;
// --------------------------------------------------------------------------------------------------------------------------------------
static {
Edge.paint.setColor(0xFFFFFFFF);
Edge.paint.setStrokeWidth(Edge.STROKE_WIDTH * Edge.scaleFactor);
Edge.paint.setStrokeCap(Paint.Cap.ROUND);
Edge.paint.setStyle(Paint.Style.FILL_AND_STROKE);
Edge.paintColored.setColor(0xFFFF0000);
Edge.paintColored.setStrokeWidth(Edge.STROKE_WIDTH * Edge.scaleFactor);
Edge.paintColored.setStrokeCap(Paint.Cap.ROUND);
Edge.paintColored.setStyle(Paint.Style.FILL_AND_STROKE);
Edge.textPaint.setStyle(Paint.Style.FILL);
Edge.textPaint.setColor(Color.BLACK);
Edge.textPaint.setTextAlign(Paint.Align.CENTER);
Edge.textPaint.setTextSize(20);
Edge.textPaint.setTypeface(Typeface.create("Helvetica", Typeface.BOLD));
Edge.textBgPaint.setStyle(Paint.Style.FILL);
Edge.textBgPaint.setColor(Color.WHITE);
}
// --------------------------------------------------------------------------------------------------------------------------------------
public Edge(Vertex sourceVertex, Vertex targetVertex) {
super(Edge.defaultEdgeLayer);
this.id = Edge.no++;
this.sourceVertex = sourceVertex;
this.targetVertex = targetVertex;
this.sourceVertex.addEdge(this);
this.targetVertex.addEdge(this);
this.recalculate();
}
// --------------------------------------------------------------------------------------------------------------------------------------
public void recalculate() {
this.cachedSourceCoord = new Coord(Math.round((Edge.baseX + this.sourceVertex.getCoord().getX()) * Edge.scaleFactor), Math.round((Edge.baseY + this.sourceVertex.getCoord().getY()) * Edge.scaleFactor));
this.cachedTargetCoord = new Coord(Math.round((Edge.baseX + this.targetVertex.getCoord().getX()) * Edge.scaleFactor), Math.round((Edge.baseY + this.targetVertex.getCoord().getY()) * Edge.scaleFactor));
Line line = new Line(this.cachedSourceCoord, this.cachedTargetCoord);
this.weightCoord = line.getMiddle();
this.onScreen = Edge.screenBottomLine.hasIntersection(line)
|| Edge.screenLeftLine.hasIntersection(line)
|| Edge.screenRightLine.hasIntersection(line)
|| Edge.screenTopLine.hasIntersection(line)
|| this.cachedSourceCoord.getX() > 0 && this.cachedSourceCoord.getX() < Edge.screenWidth && this.cachedSourceCoord.getY() > 0 && this.cachedSourceCoord.getY() < Edge.screenHeight
|| this.cachedTargetCoord.getX() > 0 && this.cachedTargetCoord.getX() < Edge.screenWidth && this.cachedTargetCoord.getY() > 0 && this.cachedTargetCoord.getY() < Edge.screenHeight;
}
// --------------------------------------------------------------------------------------------------------------------------------------
#Override
public void draw(Canvas canvas) {
this.recalculate();
if (!this.onScreen) {
return;
}
canvas.drawLine(this.cachedSourceCoord.getX(), this.cachedSourceCoord.getY(), this.cachedTargetCoord.getX(), this.cachedTargetCoord.getY(), this.colored ? Edge.paintColored : Edge.paint);
if (Edge.graph != null && Edge.graph.isDirected()) {
Line line = new Line(this.cachedSourceCoord, this.cachedTargetCoord);
Coord v = line.getVector();
float t = (float) ((Vertex.RADIUS_SIZE + 5) / Math.sqrt(Math.pow(v.getX(), 2) + Math.pow(v.getY(), 2)));
Coord t1 = new Coord((int) (this.cachedTargetCoord.getX() - t * v.getX()), (int) (this.cachedTargetCoord.getY() - t * v.getY()));
if (!line.isOnLine(t1)) {
t1 = new Coord((int) (this.cachedTargetCoord.getX() + t * v.getX()), (int) (this.cachedTargetCoord.getY() + t * v.getY()));
}
t = (float) ((Vertex.RADIUS_SIZE + 5 + Edge.TRIANGLE_SIZE) / Math.sqrt(Math.pow(v.getX(), 2) + Math.pow(v.getY(), 2)));
Coord p = new Coord((int) (this.cachedTargetCoord.getX() - t * v.getX()), (int) (this.cachedTargetCoord.getY() - t * v.getY()));
if (!line.isOnLine(p)) {
p = new Coord((int) (this.cachedTargetCoord.getX() + t * v.getX()), (int) (this.cachedTargetCoord.getY() + t * v.getY()));
}
v = line.getNormalVector().getVector();
t = (float) ((Edge.TRIANGLE_SIZE) / Math.sqrt(Math.pow(v.getX(), 2) + Math.pow(v.getY(), 2)));
Coord t2 = new Coord((int) (p.getX() - t * v.getX()), (int) (p.getY() - t * v.getY()));
Coord t3 = new Coord((int) (p.getX() + t * v.getX()), (int) (p.getY() + t * v.getY()));
Path path = new Path();
path.setFillType(Path.FillType.EVEN_ODD);
path.moveTo(t1.getX(), t1.getY());
path.lineTo(t2.getX(), t2.getY());
path.lineTo(t3.getX(), t3.getY());
path.lineTo(t1.getX(), t1.getY());
path.close();
canvas.drawPath(path, Edge.paint);
}
if (Edge.graph != null && Edge.graph.isWeighted()) {
float width = Edge.textPaint.measureText(Integer.toString(this.weight)) + 10;
float height = Edge.textPaint.getTextSize() + 5;
canvas.drawRect(this.weightCoord.getX() - width / 2, this.weightCoord.getY() - height / 2, this.weightCoord.getX() + width / 2, this.weightCoord.getY() + height / 2, Edge.textBgPaint);
canvas.drawText(Integer.toString(this.weight), this.weightCoord.getX(), this.weightCoord.getY() + height * 0.25f, Edge.textPaint);
}
}
// --------------------------------------------------------------------------------------------------------------------------------------
private boolean searchingCoordOn(float distance, float pX, float pY) {
Coord p = new Coord((int) pX, (int) pY);
Coord v = (new Line(this.cachedSourceCoord, this.cachedTargetCoord)).getNormalVector().getVector();
float t = (float) (distance / Math.sqrt(Math.pow(v.getX(), 2) + Math.pow(v.getY(), 2)));
Coord c1 = new Coord((int) (this.cachedSourceCoord.getX() - t * v.getX()), (int) (this.cachedSourceCoord.getY() - t * v.getY()));
Coord c2 = new Coord((int) (this.cachedSourceCoord.getX() + t * v.getX()), (int) (this.cachedSourceCoord.getY() + t * v.getY()));
Coord c3 = new Coord((int) (this.cachedTargetCoord.getX() - t * v.getX()), (int) (this.cachedTargetCoord.getY() - t * v.getY()));
Coord v1 = new Coord(c2.getX() - c1.getX(), c2.getY() - c1.getY());
Coord v2 = new Coord(c3.getX() - c1.getX(), c3.getY() - c1.getY());
v = Coord.minus(p, c1);
return this.onScreen
&& 0 <= Coord.dotProduct(v, v1) && Coord.dotProduct(v, v1) <= Coord.dotProduct(v1, v1)
&& 0 <= Coord.dotProduct(v, v2) && Coord.dotProduct(v, v2) <= Coord.dotProduct(v2, v2);
}
// --------------------------------------------------------------------------------------------------------------------------------------
public boolean isOnCoord(float pX, float pY) {
return this.searchingCoordOn(Edge.SENSOR_WIDTH / 2, pX, pY);
}
// --------------------------------------------------------------------------------------------------------------------------------------
public boolean isInCoordSurroundings(float pX, float pY) {
return this.searchingCoordOn(Edge.SURROUNDING_SENSOR_WIDTH / 2, pX, pY);
}
// --------------------------------------------------------------------------------------------------------------------------------------
public Vertex getSourceVertex() {
return this.sourceVertex;
}
// --------------------------------------------------------------------------------------------------------------------------------------
public Vertex getTargetVertex() {
return this.targetVertex;
}
// --------------------------------------------------------------------------------------------------------------------------------------
public static void setDefaultLayer(Layer layer) {
Edge.defaultEdgeLayer = layer;
}
// --------------------------------------------------------------------------------------------------------------------------------------
public static Layer getDefaultLayer() {
return Edge.defaultEdgeLayer;
}
// --------------------------------------------------------------------------------------------------------------------------------------
public static void remove(Edge edge) {
edge.getSourceVertex().removeEdge(edge);
edge.getTargetVertex().removeEdge(edge);
Edge.defaultEdgeLayer.remove(edge);
if (Edge.graph != null) {
Edge.graph.remove(edge);
}
}
// --------------------------------------------------------------------------------------------------------------------------------------
public void setWeight(int weight) {
this.weight = weight;
}
// --------------------------------------------------------------------------------------------------------------------------------------
public int getWeight() {
return this.weight;
}
// --------------------------------------------------------------------------------------------------------------------------------------
public int getId() {
return this.id;
}
// --------------------------------------------------------------------------------------------------------------------------------------
public void setColored() {
this.colored = true;
}
// --------------------------------------------------------------------------------------------------------------------------------------
public void setColorToDefault() {
this.colored = false;
}
// --------------------------------------------------------------------------------------------------------------------------------------
public static ArrayList<Edge> getEdgesBetween(Vertex v1, Vertex v2) {
ArrayList<Edge> edges = v1.getEdges();
ArrayList<Edge> edgesRet = new ArrayList<Edge>();
for (Edge edge : edges) {
if (v2.hasEdge(edge)) {
edgesRet.add(edge);
}
}
return edges;
}
// --------------------------------------------------------------------------------------------------------------------------------------
}
The reason for this is that the "normal" serialization would for each entry recursively invoke another writeObject(e), and for long lists this would give a StackOverflowError. The iterative serialization avoids this.
I found this on stackoverflow. I think my problem is related to my long LinkedLists..
Any ideas, advices how to properly serialize and deserialize my Graph object?
Thank You In Advance!
As a fast fix, you could try substuting the LinkedList List implemetnations with ArrayLists. You didn't give enough details (the exact exception and the implementations of the vertexes and edges would be helpful), but what you quoted might be right: we run out of memory because of the recursive calls. I didn't try, but the ArrayList default Java serialization would avoid this issue.
For a really good solution, the serialization methods of the graph objects could be re-implemented, or a tested and more memory efficient library could be used (eg. kryo).
This question would also be helpful.
Try runnning your program with more RAM. StackOverFlowError means, that the stack is filled and cant take any other method. What you could also do, is run the deserialization in another Thread; They have a seperate stack. There are several ways to do so, I recommend using the classes from java.util.concurrent.
Related
I am trying to make a game. I want the projectiles (balls) that I shoot to each have their own individual collisions with objects and the margins of the game screen, but right now when one ball collides with the game screen margins, every balls' velocity.x or velocity.y is multiplied by -1 to simulate collision rather than just the individual ball that hit's velocity being modified. (When one ball hits, all balls' velocities are affected). The projectiles are stored in an ArrayList in the GameWorld class and are all given an identical velocity when they are shot. You pick a direction to shoot in, then balls are shot every 0.3s in that direction until all balls are shot.
Here is the code from my Projectiles class:
package com.trimble.gameobjects;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.math.Vector2;
import com.trimble.gameworld.GameWorld;
public class Projectile {
private Vector2 velocity, acceleration;
private Vector2 position;
private final float RADIUS = 3f;
private boolean active, shot;
private GameWorld world;
private float theta;
public Projectile(float x, GameWorld world) {
this.world = world;
this.position = new Vector2();
this.position.x = x;
this.position.y = world.getGameRect().y - 3;
// hitbox = new Circle(position, 3f);
this.velocity = new Vector2();
this.acceleration = new Vector2(0, 0.5f);
this.active = false;
this.shot = false;
}
public void update(float delta) {
if (active) {
position.add(velocity.cpy().scl(delta));
velocity.add(acceleration.cpy().scl(delta));
// left
if (this.position.x <= 3) {
Gdx.app.log("hit", " left");
this.position.x = 3;
this.velocity.x *= -1;
}
// right
else if (this.position.x >= world.getGameRect().width - 3) {
Gdx.app.log("hit", " right");
this.position.x = world.getGameRect().width - 3;
this.velocity.x *= -1;
}
// top
if (this.position.y < world.getGameRect().y + world.getGameRect().height + 3) {
Gdx.app.log("hit", " top");
this.position.y = world.getGameRect().y + world.getGameRect().height + 3;
this.velocity.y *= -1;
}
// bottom
else if (this.position.y > world.getGameRect().y - 3 && velocity.y > 0) {
Gdx.app.log("hit", " bottom");
if (!this.world.hasTouched()) {
this.world.getBaseCircle().setPositionX(position.x);
this.world.setTouched(true);
}
zeroVelocity();
this.active = false;
this.position = world.getBaseCirclePos();
this.world.addInactive();
}
}
}
public Vector2 getVelocity() {
return velocity;
}
public float getVelocityX() {
return velocity.x;
}
public float getVelocityY() {
return velocity.y;
}
public void setVelocity(Vector2 velocity) {
this.velocity = velocity;
}
public void setVelocityX(float x) {
this.velocity.x = x;
}
public void setVelocityY(float y) {
this.velocity.y = y;
}
public float getTheta() {
return theta;
}
public void setTheta(float theta) {
this.theta = theta;
}
public void zeroVelocity() {
this.velocity.x = 0;
this.velocity.y = 0;
}
public Vector2 getPosition() {
return position;
}
public void setPositionY(float y) {
this.position.y = y;
}
public void setPositionX(float x) {
this.position.x = x;
}
public void setPosition(Vector2 position) {
this.position = position;
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
public float getR() {
return RADIUS;
}
public boolean wasShot() {
return shot;
}
public void setShot(boolean shot) {
this.shot = shot;
}
}
Here is the code in GameWorld which is called when the player shoots:
public void transitionHasShot() {
if (currentState == GameState.READY) {
currentState = GameState.HASSHOT;
velocity.y = (float) (-165 * Math.sin(theta));
velocity.x = (float) (165 * Math.cos(theta));
baseCircle.setActive(false);
for (Projectile p : projectiles) {
p.setVelocity(velocity);
}
projectiles.get(0).setActive(true);
}
}
Let me know if you need any more info.
I solved the problem by changing the code in transitionHasShot():
change
for (Projectile p : projectiles) {
p.setVelocity(velocity);
}
to:
for (Projectile p : projectiles) {
p.setVelocity(velocity.cpy());
}
I recently got into LibGDX using the book "LibGDX Game Development By Example" (Pretty good one btw) and have been playing around with the tutorial projects for the last month.
One of these games is a FlappyBird-clone (of course it is) and I decided to add features, change sprites etc.
Now the problem is that the normal obstacle graphics (flowers) don't fit the new theme and need to be exchanged.
Doing so results in jiggering graphics for the new sprites.
I should point out that the code used to visualize these obstacles has not changed at all, simply exchanging the sprites causes this problem.
I tried a lot of different sprites and all that are not identical to the flowers seem to have this problem.
So whatever is the cause, the old flower sprites are unaffected, every other sprite is.
On to the code (Removed some Getters/Setters and other unrelated methods)
The Flower/Obstacle Class:
public class Flower
{
private static final float COLLISION_RECTANGLE_WIDTH = 13f;
private static final float COLLISION_RECTANGLE_HEIGHT = 447f;
private static final float COLLISION_CIRCLE_RADIUS = 33f;
private float x = 0;
private float y = 0;
private static final float MAX_SPEED_PER_SECOND = 100f;
public static final float WIDTH = COLLISION_CIRCLE_RADIUS * 2;
private static final float HEIGHT_OFFSET = -400.0f;
private static final float DISTANCE_BETWEEN_FLOOR_AND_CEILING = 225.0f;
private final Circle floorCollisionCircle;
private final Rectangle floorCollisionRectangle;
private final Circle ceilingCollisionCircle;
private final Rectangle ceilingCollisionRectangle;
private boolean pointClaimed = false;
private final TextureRegion floorTexture;
private final TextureRegion ceilingTexture;
float textureX,textureY;
public Flower(TextureRegion floorTexture, TextureRegion ceilingTexture)
{
this.floorTexture = floorTexture;
this.ceilingTexture = ceilingTexture;
this.y = MathUtils.random(HEIGHT_OFFSET);
this.floorCollisionRectangle = new Rectangle(x,y,COLLISION_RECTANGLE_WIDTH,COLLISION_RECTANGLE_HEIGHT);
this.floorCollisionCircle = new Circle(x + floorCollisionRectangle.width / 2, y + floorCollisionRectangle.height, COLLISION_CIRCLE_RADIUS);
this.ceilingCollisionRectangle = new Rectangle(x,floorCollisionCircle.y + DISTANCE_BETWEEN_FLOOR_AND_CEILING,COLLISION_RECTANGLE_WIDTH,
COLLISION_RECTANGLE_HEIGHT);
this.ceilingCollisionCircle = new Circle(x + ceilingCollisionRectangle.width / 2, ceilingCollisionRectangle.y, COLLISION_CIRCLE_RADIUS);
}
public void update(float delta)
{
setPosition(x - (MAX_SPEED_PER_SECOND * delta));
}
public void setPosition(float x)
{
this.x = x;
updateCollisionCircle();
updateCollisionRectangle();
}
private void updateCollisionCircle()
{
floorCollisionCircle.setX(x + floorCollisionRectangle.width / 2);
ceilingCollisionCircle.setX(x + ceilingCollisionRectangle.width / 2);
}
private void updateCollisionRectangle()
{
floorCollisionRectangle.setX(x);
ceilingCollisionRectangle.setX(x);
}
public void draw(SpriteBatch batch)
{
drawFloorFlower(batch);
drawCeilingFlower(batch);
}
private void drawFloorFlower(SpriteBatch batch)
{
textureX = floorCollisionCircle.x - floorTexture.getRegionWidth() / 2;
textureY = floorCollisionRectangle.getY() + COLLISION_CIRCLE_RADIUS;
batch.draw(floorTexture,textureX,textureY);
}
private void drawCeilingFlower(SpriteBatch batch)
{
textureX = ceilingCollisionCircle.x - ceilingTexture.getRegionWidth() / 2;
textureY = ceilingCollisionRectangle.getY() - COLLISION_CIRCLE_RADIUS;
batch.draw(ceilingTexture,textureX, textureY);
}
}
And the GameScreen/Main Class:
public class GameScreen extends ScreenAdapter
{
private static final float WORLD_WIDTH = 480;
private static final float WORLD_HEIGHT = 640;
private java.util.prefs.Preferences prefs;
private int highscore;
FlappeeBeeGame flappeeBeeGame;
private ShapeRenderer shapeRenderer;
private Viewport viewport;
private Camera camera;
private SpriteBatch batch;
private Flappee flappee;
private Flower flower;
private Array<Flower> flowers = new Array<Flower>();
private static final float GAP_BETWEEN_FLOWERS = 200.0f;
private boolean gameOver = false;
int score = 0;
BitmapFont bitmapFont;
GlyphLayout glyphLayout;
private TextureRegion background;
private TextureRegion flowerBottom;
private TextureRegion flowerTop;
private TextureRegion bee;
private TextureRegion smallCloud;
private TextureRegion lowCloud;
private Music music_background;
TextureAtlas textureAtlas;
List<Cloud> activeClouds = new ArrayList<Cloud>();
List<Cloud> cloudBarriers = new ArrayList<Cloud>();
private float cloud_minScale = 0.6f;
private float cloud_maxScale = 1.0f;
private float cloud_minY, cloud_maxY;
private float cloud_minDis, cloud_maxDis;
private float cloud_minSpeed = 17.0f;
private float cloud_maxSpeed = 27.0f;
private final float barrierCloud_speed = 150.0f;
private boolean inputBlocked = false;
private float blockTime = 0.5f;
private float remainingblockTime = blockTime;
public GameScreen(FlappeeBeeGame fpg)
{
flappeeBeeGame = fpg;
flappeeBeeGame.getAssetManager().load("assets/flappee_bee_assets.atlas",TextureAtlas.class);
flappeeBeeGame.getAssetManager().finishLoading();
textureAtlas = flappeeBeeGame.getAssetManager().get("assets/flappee_bee_assets.atlas");
prefs = java.util.prefs.Preferences.userRoot().node(this.getClass().getName());
highscore = prefs.getInt("highscore",0);
music_background = Gdx.audio.newMusic(Gdx.files.internal("assets/backgroundmusic.ogg"));
music_background.setLooping(true);
music_background.setVolume(0.5f);
music_background.play();
}
private void createNewFlower()
{
Flower newFlower = new Flower(flowerBottom,flowerTop);
newFlower.setPosition(WORLD_WIDTH + Flower.WIDTH);
flowers.add(newFlower);
}
private void checkIfNewFlowerIsNeeded()
{
if(flowers.size == 0)
{
createNewFlower();
}
else
{
Flower flower = flowers.peek();
if(flower.getX() < WORLD_WIDTH - GAP_BETWEEN_FLOWERS)
{
createNewFlower();
}
}
}
private void drawFlowers()
{
for(Flower flower : flowers)
{
flower.draw(batch);
}
}
private void removeFlowersIfPassed()
{
if(flowers.size > 0)
{
Flower firstFlower = flowers.first();
if(firstFlower.getX() < -Flower.WIDTH)
{
flowers.removeValue(firstFlower,true);
}
}
}
#Override
public void resize(int width, int height) {
super.resize(width, height);
viewport.update(width,height);
}
#Override
public void show() {
super.show();
camera = new OrthographicCamera();
camera.position.set(WORLD_WIDTH / 2, WORLD_HEIGHT / 2, 0);
camera.update();
viewport = new FitViewport(WORLD_WIDTH,WORLD_HEIGHT, camera);
shapeRenderer = new ShapeRenderer();
batch = new SpriteBatch();
bitmapFont = new BitmapFont(Gdx.files.internal("assets/score_new.fnt"));
glyphLayout = new GlyphLayout();
background = textureAtlas.findRegion("bg");
flowerBottom = textureAtlas.findRegion("pipeBottom");
flowerTop = textureAtlas.findRegion("flowerTop");
bee = textureAtlas.findRegion("bee");
smallCloud = textureAtlas.findRegion("smallCloud");
lowCloud = textureAtlas.findRegion("lowerCloud");
flower = new Flower(flowerBottom,flowerTop);
flappee = new Flappee(bee,textureAtlas);
flappee.setPosition(WORLD_WIDTH/4,WORLD_HEIGHT/2);
cloud_minDis = smallCloud.getRegionWidth() / 4;
cloud_maxDis = smallCloud.getRegionWidth();
cloud_maxY = viewport.getWorldHeight() - smallCloud.getRegionHeight()/2;
cloud_minY = viewport.getWorldHeight() - smallCloud.getRegionHeight() * 2;
Cloud a = generateCloud(null);
Cloud b = generateCloud(a);
Cloud c = generateCloud(b);
Cloud d = generateCloud(c);
Cloud e = generateCloud(d);
activeClouds.add(a);
activeClouds.add(b);
activeClouds.add(c);
activeClouds.add(d);
activeClouds.add(e);
a = new Cloud(lowCloud,batch,0,0 - lowCloud.getRegionHeight()/4,barrierCloud_speed,1.0f);
b = new Cloud(lowCloud,batch,lowCloud.getRegionWidth(),0 - lowCloud.getRegionHeight()/4,barrierCloud_speed,1.0f);
c = new Cloud(lowCloud,batch,lowCloud.getRegionWidth()*2,0 - lowCloud.getRegionHeight()/4,barrierCloud_speed,1.0f);
cloudBarriers.add(a);
cloudBarriers.add(b);
cloudBarriers.add(c);
}
public Cloud generateCloud(Cloud formerCloud)
{
Cloud d;
if(formerCloud == null)
{
float randomVal = (float)Math.random();
d = new Cloud(smallCloud,batch,viewport.getWorldWidth(),
(float)Math.random() * (cloud_maxY - cloud_minY) + cloud_minY,
randomVal * (cloud_maxSpeed-cloud_minSpeed) + cloud_minSpeed,
randomVal * (cloud_maxScale-cloud_minScale) + cloud_minScale);
return d;
}
float randomVal = (float)Math.random();
d = new Cloud(smallCloud,batch,formerCloud.getPosX() + ((float)
Math.random() * (cloud_maxDis - cloud_minDis) + cloud_minDis),(float)Math.random() * (cloud_maxY - cloud_minY) + cloud_minY,
randomVal * (cloud_maxSpeed-cloud_minSpeed) + cloud_minSpeed,
randomVal * (cloud_maxScale-cloud_minScale) + cloud_minScale);
return d;
}
#Override
public void render(float delta) {
super.render(delta);
clearScreen();
shapeRenderer.setProjectionMatrix(camera.projection);
shapeRenderer.setTransformMatrix(camera.view);
shapeRenderer.begin(ShapeRenderer.ShapeType.Line);
shapeRenderer.end();
draw(delta);
update(delta);
}
private void draw(float delta)
{
batch.setProjectionMatrix(camera.projection);
batch.setTransformMatrix(camera.view);
batch.begin();
batch.draw(background,0,0);
drawClouds(delta);
drawScore();
drawFlowers();
//drawDebug();
if(!gameOver)
{
flappee.draw(batch,delta);
}
drawBarrierClouds(delta);
batch.end();
}
private void updateClouds(float delta)
{
boolean move = false;
Cloud tmp = null;
for(Cloud c : cloudBarriers)
{
c.update(delta);
if(c.getPosX() <= -lowCloud.getRegionWidth())
{
tmp = c;
move = true;
}
}
if(move)
{
float positionX = cloudBarriers.get(cloudBarriers.size()-1).getPosX() + lowCloud.getRegionWidth();
if(positionX < viewport.getWorldWidth())
{
positionX = viewport.getWorldWidth();
}
tmp.setPos(positionX,0 - lowCloud.getRegionHeight()/4);
cloudBarriers.remove(tmp);
cloudBarriers.add(tmp);
tmp = null;
move = false;
}
for(Cloud c : activeClouds)
{
c.update(delta);
if(c.getPosX() <= -smallCloud.getRegionWidth())
{
tmp = c;
move = true;
}
}
if(move)
{
float randomVal = (float)Math.random();
float positionX = activeClouds.get(activeClouds.size()-1).getPosX() + ((float)
Math.random() * (cloud_maxDis - cloud_minDis) + cloud_minDis);
if(positionX < viewport.getWorldWidth())
{
positionX = viewport.getWorldWidth();
}
tmp.setPos(positionX,(float)Math.random() * (cloud_maxY - cloud_minY) + cloud_minY);
tmp.setSpeed(randomVal * (cloud_maxSpeed - cloud_minSpeed) + cloud_minSpeed);
tmp.setScale(randomVal * (cloud_maxScale - cloud_minScale) + cloud_minScale);
activeClouds.remove(tmp);
activeClouds.add(tmp);
move = false;
tmp = null;
}
}
private void drawBarrierClouds(float delta)
{
for(Cloud c : cloudBarriers)
{
c.render();
}
}
private void drawClouds(float delta)
{
for(Cloud c : activeClouds)
{
c.render();
}
}
private void clearScreen()
{
Gdx.gl.glClearColor(Color.BLACK.r,Color.BLACK.g,Color.BLACK.b, Color.BLACK.a);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
}
private void blockFlappeeLeavingTheWorld()
{
flappee.setPosition(flappee.getX(), MathUtils.clamp(flappee.getY(),0,WORLD_HEIGHT));
}
private void updateFlowers(float delta)
{
for(Flower flower : flowers)
{
flower.update(delta);
}
checkIfNewFlowerIsNeeded();
removeFlowersIfPassed();
}
private void update(float delta)
{
updateClouds(delta);
if(!gameOver) {
updateFlappee(delta);
updateFlowers(delta);
updateScore();
if (checkForCollision())
{
gameOver = true;
inputBlocked = true;
remainingblockTime = blockTime;
restart();
}
}
else
{
if((Gdx.input.isKeyJustPressed(Input.Keys.SPACE) || Gdx.input.isButtonPressed(Input.Buttons.LEFT)) && !inputBlocked)
{
gameOver = false;
score = 0;
}
if(inputBlocked)
{
if(remainingblockTime > 0)
{
remainingblockTime -= delta;
}
else
{
inputBlocked = false;
remainingblockTime = blockTime;
}
}
}
}
private void restart()
{
flappee.setPosition(WORLD_WIDTH / 4, WORLD_HEIGHT / 2);
flowers.clear();
}
#Override
public void dispose() {
super.dispose();
}
private boolean checkForCollision()
{
for(Flower flower : flowers)
{
if(flower.isFlappeeColliding(flappee))
{
if(score > highscore)
{
highscore = score;
prefs.putInt("highscore",highscore);
inputBlocked = true;
remainingblockTime = blockTime;
}
return true;
}
}
return false;
}
}
I'd love to give you a runnable jar but I've got some problems building a working version. Let's just say a jar is out of the question for now.
What I can give you are screenshots:
Jiggering Sprite View
Functional Flower Sprite View
The first image shows the problem: The new sprites (which are just debug) become edgy like the top of the sprite can't compete with the speed of its lower half.
The second image shows the old sprites for comparison: They don't show any of this behaviour, even if they are longer than the one on the screenshot.
So what do you people think?
What causes this behaviour and how should I fix it?
Thanks in advance for any help, I appreciate it :)
Greetz!
EDIT:
I kind of fixed it.
When switching to another computer and running the game the issue didn't come up anymore.
Specifically I went from Debian to Windows 10 and from NVIDIA Optimus to a standard desktop AMD-Card.
If you should encounter this problem try another PC with a different OS and/or GPU.
Sadly (if you were to google this question) I can't tell you how to solve it on the first machine or what exactly caused it, but at least it shouldn't come up on anyone else's computer when you send them your project.
I try to do my own version of "fruit ninja" for training based on this version : https://github.com/emmaguy/FruitNinja
I have done some minor changes. What I want to do is to affect different scores to the object in the "enum" in fruittype.
So, I add this function (in the aim to retrieve the current random value):
public static int currentrandom() {
return random.nextInt(FruitType2.values().length );
}
and I add,
if (FruitType2.currentrandom()<=9) {
score++;
} else {
score=score-5;
}
at the end of FruitProjectileManager.
Complete code for FruitProjectileManager:
public class FruitProjectileManager02 implements ProjectileManager {
private final Random random2 = new Random();
private final List<Projectile> fruitProjectiles =
new ArrayList<Projectile>();
private final SparseArray<Bitmap> bitmapCache;
private Region clip;
private int maxWidth;
private int maxHeight;
private String FruitTypen = "FruitType2";
public FruitProjectileManager02(Resources r) {
bitmapCache = new SparseArray<Bitmap>(FruitType2.values().length);
for (FruitType2 t : FruitType2.values()) {
bitmapCache.put(t.getResourceId(), BitmapFactory.decodeResource(r, t.getResourceId(), new Options()));
}
}
public void draw(Canvas canvas) {
for (Projectile f : fruitProjectiles) {
f.draw(canvas);
}
}
public void update() {
if (maxWidth < 0 || maxHeight < 0) {
return;
}
if (random2.nextInt(1000) < 30) {
fruitProjectiles.add(createNewFruitProjectile());
}
for (Iterator<Projectile> iter = fruitProjectiles.iterator(); iter.hasNext(); ) {
Projectile f = iter.next();
f.move();
if (f.hasMovedOffScreen()) {
iter.remove();
}
}
}
private FruitProjectile02 createNewFruitProjectile() {
int angle = random2.nextInt(20) + 70;
int speed = random2.nextInt(30) + 120;
boolean rightToLeft = random2.nextBoolean();
float gravity = random2.nextInt(6) + 8.0f;
float rotationStartingAngle = random2.nextInt(360);
float rotationIncrement = random2.nextInt(100) / 3.0f;
if (random2.nextInt(1) % 2 == 0) {
rotationIncrement *= -1;
}
return new FruitProjectile02(bitmapCache.get(FruitType2.randomFruit().getResourceId()), maxWidth, maxHeight,
angle, speed, gravity, rightToLeft, rotationIncrement, rotationStartingAngle);
}
public void setWidthAndHeight(int width, int height) {
this.maxWidth = width;
this.maxHeight = height;
this.clip = new Region(0, 0, width, height);
}
#Override
public int testForCollisions(List<TimedPath> allPaths) {
int score = 0;
for (TimedPath p : allPaths) {
for (Projectile f : fruitProjectiles) {
if (!f.isAlive())
continue;
Region projectile = new Region(f.getLocation());
Region path = new Region();
path.setPath(p, clip);
if (!projectile.quickReject(path) && projectile.op(path, Region.Op.INTERSECT)) {
if (FruitType2.currentrandom() <= 9) {
score++;
} else {
score = score - 5;
}
f.kill();
}
}
}
return score;
}
}
Complete code for FruitType:
public enum FruitType2 {
T02(R.drawable.n002),
T04(R.drawable.n004),
T06(R.drawable.n006),
T08(R.drawable.n008),
T10(R.drawable.n010),
T12(R.drawable.n012),
T14(R.drawable.n014),
T16(R.drawable.n016),
T18(R.drawable.n018),
T20(R.drawable.n020),
OTHER1(R.drawable.n003),
OTHER2(R.drawable.n007),
OTHER3(R.drawable.n011);
private final int resourceId;
private FruitType2(int resourceId) {
this.resourceId = resourceId;
}
public int getResourceId() {
return resourceId;
}
private static final Random random = new Random();
public static int currentrandom() {
return random.nextInt(FruitType2.values().length);
}
public static FruitType2 randomFruit() {
return FruitType2.values()[random.nextInt(FruitType2.values().length)];
}
}
I understand the problem , the current random(when the fruit is generated) is not the same that the random when the fruit is sliced and my question is how to
solve this problem. I get no idea so if you have some clues, I am interested.
Thank you in advance.
Perhaps i don't understand the problem, but why don't you store the random number in a variable? Later you can take the random number out of the variable.
I've been looking through this code for over 2 days and still can't find any errors in the code as to why it crashes on Android. It works perfectly fine on desktop.
Android Target: 4.0.3
Android Phone used: HTC Desire C
Logcat:
04-07 16:42:35.785: E/AndroidRuntime(24197): FATAL EXCEPTION: GLThread 2955
04-07 16:42:35.785: E/AndroidRuntime(24197): java.lang.ClassCastException: com.badlogic.gdx.math.Vector2 cannot be cast to java.lang.String
04-07 16:42:35.785: E/AndroidRuntime(24197): at com.survivegameandroidtest.survive.Player.readPlayer(Player.java:191)
04-07 16:42:35.785: E/AndroidRuntime(24197): at com.survivegameandroidtest.survive.MainMenu.loadPlayer(MainMenu.java:278)
04-07 16:42:35.785: E/AndroidRuntime(24197): at com.survivegameandroidtest.survive.MainMenu.show(MainMenu.java:127)
04-07 16:42:35.785: E/AndroidRuntime(24197): at com.badlogic.gdx.Game.setScreen(Game.java:62)
04-07 16:42:35.785: E/AndroidRuntime(24197): at com.survivegameandroidtest.survive.GameHub.create(GameHub.java:12)
04-07 16:42:35.785: E/AndroidRuntime(24197): at com.badlogic.gdx.backends.android.AndroidGraphics.onSurfaceChanged(AndroidGraphics.java:334)
04-07 16:42:35.785: E/AndroidRuntime(24197): at android.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1463)
04-07 16:42:35.785: E/AndroidRuntime(24197): at android.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1224)
Like I said, this works fine on desktop, but crashes on Android.
Player class
package com.survivegameandroidtest.survive;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
public class Player implements Serializable{
private static final long serialVersionUID = 1L;
SpriteBatch batch;
static Vector2 position = new Vector2(1,1);
Vector2 size;
Vector2 boundSize;
Vector2 boundOffset;
Texture texture;
Rectangle bounds;
String direction = "";
StringCheese cheese;
EnemyCheddarling enemy;
int health = 0, damage;
boolean alive;
private static final int col = 4, row = 4;
Animation animation;
TextureRegion[] frames;
TextureRegion currentFrame;
float stateTime;
int speed = 0;
static String name = "";
public Player(Texture texture, Vector2 position, Vector2 size, Vector2 boundSize, Vector2 boundOffset) {
this.texture = texture;
Player.position = position;
this.size = size;
this.boundOffset = boundOffset;
TextureRegion[][] tmp = TextureRegion.split(texture, texture.getWidth() / col, texture.getHeight() / row);
frames = new TextureRegion[col * row];
int index = 0;
for(int i = 0; i < row; i++) {
for(int j = 0; j < col; j++) {
frames[index++] = tmp[i][j];
}
}
animation = new Animation(1f, frames);
stateTime = 0f;
currentFrame = animation.getKeyFrame(0);
this.boundSize = boundSize;
bounds = new Rectangle(position.x + boundOffset.x, position.y + boundOffset.y, boundSize.x, boundSize.y);
health = 100;
damage = 20;
alive = true;
name = "SET YO NAME FOOL";
}
public void update() {
bounds.set(position.x + boundOffset.x, position.y + boundOffset.y, boundSize.x, boundSize.y);
if(health <= 0) {
alive = false;
}
stateTime += Gdx.graphics.getDeltaTime()*8;
if(stateTime > col) {
stateTime = 0;
}
//WASD MOVE METHOD
if(Gdx.input.isKeyPressed(Keys.W)) {
position.y += 2f;
currentFrame = animation.getKeyFrame(12 + stateTime);
direction = "up";
speed = 2;
}
if(Gdx.input.isKeyPressed(Keys.A)) {
position.x -= 2f;
currentFrame = animation.getKeyFrame(4 + stateTime);
direction = "left";
speed = 2;
}
if(Gdx.input.isKeyPressed(Keys.S)) {
position.y -= 2f;
currentFrame = animation.getKeyFrame(0 + stateTime);
direction = "down";
speed = 2;
}
if(Gdx.input.isKeyPressed(Keys.D)) {
position.x += 2f;
currentFrame = animation.getKeyFrame(8 + stateTime);
direction = "right";
speed = 2;
}
//ACCELEROMETER MOVE METHOD
if(Gdx.input.getAccelerometerX() != 1 || Gdx.input.getAccelerometerX() != 0 || Gdx.input.getAccelerometerX() != -1 || Gdx.input.getAccelerometerY() != 1 || Gdx.input.getAccelerometerY() != 0 || Gdx.input.getAccelerometerY() != -1) {
for(int rotation = 2; rotation < 5; rotation++) {
if(Gdx.input.getAccelerometerX() <= -rotation) { //DOWN
position.y += rotation;
currentFrame = animation.getKeyFrame(12 + stateTime);
direction = "down";
speed = rotation;
}
if(Gdx.input.getAccelerometerX() >= rotation) { //UP
position.y -= rotation;
currentFrame = animation.getKeyFrame(0 + stateTime);
direction = "up";
speed = rotation;
}
if(Gdx.input.getAccelerometerY() <= -rotation) { //LEFT
position.x -= rotation;
currentFrame = animation.getKeyFrame(4 + stateTime);
direction = "left";
speed = rotation;
}
if(Gdx.input.getAccelerometerY() >= rotation) { //RIGHT
position.x += rotation;
currentFrame = animation.getKeyFrame(8 + stateTime);
direction = "right";
speed = rotation;
}
}
}
}
public void draw(SpriteBatch batch) {
batch.draw(currentFrame, position.x, position.y, size.x, size.y);
}
public void collision() {
if(direction == "up") {
position.y += speed;
}
if(direction == "down") {
position.y -= speed;
}
if(direction == "left") {
position.y -= speed;
}
if(direction == "right") {
position.y += speed;
}
}
public static void savePlayer(Player playerName) throws IOException{
FileHandle file = Gdx.files.local("player.dat");
OutputStream out = null;
try{
file.writeBytes(serialize(name), false);
}catch(Exception ex){
System.out.println(ex.toString());
}finally{
if(out != null) try{out.close();} catch(Exception ex){}
}
try {
System.out.println("Saving Player" + " " + deserialize(file.readBytes()));
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static String readPlayer() throws IOException, ClassNotFoundException{
String playerName = "";
FileHandle file = Gdx.files.local("player.dat");
playerName = (String) deserialize(file.readBytes());
System.out.println("Reading Player");
return playerName;
}
private static byte[] serialize(Object obj) throws IOException {
ByteArrayOutputStream b = new ByteArrayOutputStream();
ObjectOutputStream o = new ObjectOutputStream(b);
o.writeObject(obj);
return b.toByteArray();
}
public static Object deserialize(byte[] bytes) throws IOException, ClassNotFoundException {
ByteArrayInputStream b = new ByteArrayInputStream(bytes);
ObjectInputStream o = new ObjectInputStream(b);
return o.readObject();
}
//GETTERS AND SETTERS
public SpriteBatch getBatch() {
return batch;
}
public String getName() {
return name;
}
public void setName(String object) {
Player.name = object;
}
public Vector2 getBoundOffset() {
return boundOffset;
}
public void setBoundOffset(Vector2 boundOffset) {
this.boundOffset = boundOffset;
}
public int getHealth() {
return health;
}
public void setHealth(int health) {
this.health = health;
}
public int getDamage() {
return damage;
}
public void setDamage(int damage) {
this.damage = damage;
}
public boolean isAlive() {
return alive;
}
public void setAlive(boolean alive) {
this.alive = alive;
}
public String getDirection() {
return direction;
}
public void setDirection(String direction) {
this.direction = direction;
}
public StringCheese getCheese() {
return cheese;
}
public void setCheese(StringCheese cheese) {
this.cheese = cheese;
}
public Rectangle getBounds() {
return bounds;
}
public void setBounds(Rectangle bounds) {
this.bounds = bounds;
}
public Vector2 getSize() {
return size;
}
public void setSize(Vector2 size) {
this.size = size;
}
public Vector2 getBoundSize() {
return boundSize;
}
public void setBoundSize(Vector2 boundSize) {
this.boundSize = boundSize;
}
public Texture getTexture() {
return texture;
}
public void setTexture(Texture texture) {
this.texture = texture;
}
public Animation getAnimation() {
return animation;
}
public void setAnimation(Animation animation) {
this.animation = animation;
}
public TextureRegion[] getFrames() {
return frames;
}
public void setFrames(TextureRegion[] frames) {
this.frames = frames;
}
public TextureRegion getCurrentFrame() {
return currentFrame;
}
public void setCurrentFrame(TextureRegion currentFrame) {
this.currentFrame = currentFrame;
}
public float getStateTime() {
return stateTime;
}
public void setStateTime(float stateTime) {
this.stateTime = stateTime;
}
public static int getCol() {
return col;
}
public static int getRow() {
return row;
}
public void setBatch(SpriteBatch batch) {
this.batch = batch;
}
public Vector2 getPosition() {
return position;
}
public void setPosition(Vector2 position) {
Player.position = position;
}
}
In savePlayer(), I try to serialize the String variable 'name'.
System.out.println("Saving Player" + " " + deserialize(file.readBytes()));
^ This line of code prints out "Saving Player Hosif Yimms" in the console, and none of the methods return a Vector2.
ANSWER: Used a FileHandle method to save the players name, as deserialize return an Object, not String.
Why are you using Object serialization for strings anyway? Just get a reader/writer for your FileHandle. – Chase
public static void savePlayerName(Player player) {
FileHandle file = Gdx.files.local("playername.dat");
file.writeString(player.getName(), false);
}
public static String readPlayerName() {
FileHandle file = Gdx.files.local("playername.dat");
String name = file.readString();
return name;
}
I would like to know, how to make scrollview swipe effect when mouse is dragged up and down. I'm developing JavaFX application which has ListView & I need to scroll the list when user drags the mouse up and down (like iOS contact list).
Also how to add the user's dragging velocity and speed to the scrolling value?
UPDATED : I got some code here but It doesn't work properly...
import java.util.ArrayList;
import java.util.Arrays;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.animation.TimelineBuilder;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.Event;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.FlowPane;
import javafx.util.Duration;
public class Swipe extends ScrollPane {
private static final int INERTIA_DURATION = 2400;
private static final double CLICK_THRESHOLD = 20;
private static final double CLICK_TIME_THRESHOLD = Integer.parseInt(System.getProperty("click", "400"));
private final double width;
private final double height;
private long startDrag;
private long lastDrag;
private long lastDragDelta;
private int startDragX;
private int startDragY;
private int lastDragX;
private int lastDragY;
private int lastDragStepX;
private int lastDragStepY;
private double dragVelocityX;
private double dragVelocityY;
private boolean clickThresholdBroken;
private Timeline inertiaTimeline = null;
private long lastClickTime = -1;
private final boolean isFiredByMe = false;
public Swipe(double width, double height) {
this.width = width;
this.height = height;
init();
}
private void init() {
setPrefSize(width, height);
setPannable(true);
setHbarPolicy(ScrollBarPolicy.NEVER);
setVbarPolicy(ScrollBarPolicy.NEVER);
setEventHandlers();
ContentPane cp = new ContentPane();
setContent(cp);
}
private void setEventHandlers() {
setOnMousePressed((MouseEvent event) -> {
lastDragX = startDragX = (int) event.getX();
lastDragY = startDragY = (int) event.getY();
lastDrag = startDrag = System.currentTimeMillis();
lastDragDelta = 0;
if (inertiaTimeline != null) {
inertiaTimeline.stop();
}
clickThresholdBroken = false;
});
setOnDragDetected((MouseEvent event) -> {
// Delta of this drag vs. last drag location (or start)
lastDragStepX = (int) event.getX() - lastDragX;
lastDragStepY = (int) event.getY() - lastDragY;
// Duration of this drag step.
lastDragDelta = System.currentTimeMillis() - lastDrag;
// Velocity of last drag increment.
dragVelocityX = (double) lastDragStepX / (double) lastDragDelta;
dragVelocityY = (double) lastDragStepY / (double) lastDragDelta;
// Snapshot of this drag event.
lastDragX = (int) event.getX();
lastDragY = (int) event.getY();
lastDrag = System.currentTimeMillis();
// Calculate distance so far -- have we dragged enough to scroll?
final int dragX = (int) event.getX() - startDragX;
final int dragY = (int) event.getY() - startDragY;
double distance = Math.abs(Math.sqrt((dragX * dragX) + (dragY * dragY)));
int scrollDistX = lastDragStepX;
int scrollDistY = lastDragStepY;
if (!clickThresholdBroken && distance > CLICK_THRESHOLD) {
clickThresholdBroken = true;
scrollDistX = dragX;
scrollDistY = dragY;
}
if (clickThresholdBroken) {
Event.fireEvent(event.getTarget(), new ScrollEvent(
ScrollEvent.SCROLL,
scrollDistX, scrollDistY,
scrollDistX, scrollDistY,
event.isShiftDown(), event.isControlDown(), event.isAltDown(), event.isMetaDown(),
true, false,
event.getX(), event.getY(),
event.getSceneX(), event.getSceneY(),
ScrollEvent.HorizontalTextScrollUnits.NONE, 0,
ScrollEvent.VerticalTextScrollUnits.NONE, 0, 0, null));
}
});
setOnMouseReleased((MouseEvent event) -> {
handleRelease(event);
});
setOnMouseClicked((MouseEvent event) -> {
final long time = System.currentTimeMillis();
if (clickThresholdBroken || (lastClickTime != -1 && (time - lastClickTime) < CLICK_TIME_THRESHOLD)) {
event.consume();
}
lastClickTime = time;
});
}
private void handleRelease(final MouseEvent me) {
if (clickThresholdBroken) {
// Calculate last instantaneous velocity. User may have stopped moving
// before they let go of the mouse.
final long time = System.currentTimeMillis() - lastDrag;
dragVelocityX = (double) lastDragStepX / (time + lastDragDelta);
dragVelocityY = (double) lastDragStepY / (time + lastDragDelta);
// determin if click or drag/flick
final int dragX = (int) me.getX() - startDragX;
final int dragY = (int) me.getY() - startDragY;
// calculate complete time from start to end of drag
final long totalTime = System.currentTimeMillis() - startDrag;
// if time is less than 300ms then considered a quick flick and whole time is used
final boolean quick = totalTime < 300;
// calculate velocity
double velocityX = quick ? (double) dragX / (double) totalTime : dragVelocityX; // pixels/ms
double velocityY = quick ? (double) dragY / (double) totalTime : dragVelocityY; // pixels/ms
final int distanceX = (int) (velocityX * INERTIA_DURATION); // distance
final int distanceY = (int) (velocityY * INERTIA_DURATION); // distance
//
DoubleProperty animatePosition = new SimpleDoubleProperty() {
double lastMouseX = me.getX();
double lastMouseY = me.getY();
#Override
protected void invalidated() {
final double mouseX = me.getX() + (distanceX * get());
final double mouseY = me.getY() + (distanceY * get());
final double dragStepX = mouseX - lastMouseX;
final double dragStepY = mouseY - lastMouseY;
if (Math.abs(dragStepX) >= 1.0 || Math.abs(dragStepY) >= 1.0) {
Event.fireEvent(me.getTarget(), new ScrollEvent(
ScrollEvent.SCROLL,
dragStepX, dragStepY,
(distanceX * get()), (distanceY * get()),
me.isShiftDown(), me.isControlDown(), me.isAltDown(), me.isMetaDown(),
true, true,
me.getX(), me.getY(),
me.getSceneX(), me.getSceneY(),
ScrollEvent.HorizontalTextScrollUnits.NONE, 0,
ScrollEvent.VerticalTextScrollUnits.NONE, 0,
0, null));
}
lastMouseX = mouseX;
lastMouseY = mouseY;
}
};
// animate a slow down from current velocity to zero
inertiaTimeline = TimelineBuilder.create()
.keyFrames(
new KeyFrame(Duration.ZERO, new KeyValue(animatePosition, 0)),
new KeyFrame(Duration.millis(INERTIA_DURATION), new KeyValue(animatePosition, 1d, Interpolator.SPLINE(0.0513, 0.1131, 0.1368, 1.0000)))
).build();
inertiaTimeline.play();
}
}
private class ContentPane extends FlowPane {
private ArrayList getList() {
String[] list = {
"Kerrie Batts", "Raina Huffstutler", "Kip Kukowski", "Trish Sullivan", "Kyla Hollingsworth", "Gearldine Leavy", "Major Langdon", "Avery Rusin", "Hedy Messina", "Audry Felps", "Tianna Robbins", "Marian Tranmer", "Lashaunda Bivona", "Leighann Schwab", "Emanuel Volpe", "Neida Geist", "Edda Placencia", "Olevia Hippe", "Fernando Cohen", "Danette Dorsett"};
ArrayList<String> nameList = new ArrayList();
nameList.addAll(Arrays.asList(list));
return nameList;
}
public ContentPane() {
setPrefSize(215, 271);
ArrayList<String> nameList = getList();
Element[] element = new Element[nameList.size()];
for (int i = 0; i < nameList.size(); i++) {
String name = nameList.get(i);
Element el = element[i] = new Element(210, 25, name);
getChildren().add(el);
}
}
}
private class Element extends AnchorPane {
private double width;
private double height;
private String name;
public Element(double width, double height, String name) {
this.width = width;
this.height = height;
this.name = name;
init();
}
private Label createName() {
Label label = new Label(name);
label.setPrefSize(width, height);
label.setAlignment(Pos.CENTER_LEFT);
AnchorPane.setLeftAnchor(label, 5.0);
label.setStyle("-fx-font-family: Calibri; -fx-font-size: 14;");
return label;
}
private void init() {
setPrefSize(width, height);
getChildren().add(createName());
}
public void setPrefSize(double width, double height) {
this.width = width;
this.height = height;
}
public void setName(String name) {
this.name = name;
}
}
}
I figured out myself. There is two classes. 1st one is for calculate the velocity of the movement.
VelocityTracker.java
import javafx.scene.input.MouseEvent;
public final class VelocityTracker {
private static final boolean DEBUG = false;
private static final boolean localLOGV = DEBUG;
private static final int NUM_PAST = 10;
private static final int MAX_AGE_MILLISECONDS = 200;
private static final int POINTER_POOL_CAPACITY = 20;
private static Pointer sRecycledPointerListHead;
private static int sRecycledPointerCount;
private static final class Pointer {
public Pointer next;
public int id;
public float xVelocity;
public float yVelocity;
public final float[] pastX = new float[NUM_PAST];
public final float[] pastY = new float[NUM_PAST];
public final long[] pastTime = new long[NUM_PAST]; // uses Long.MIN_VALUE as a sentinel
}
private Pointer mPointerListHead; // sorted by id in increasing order
private int mLastTouchIndex;
public VelocityTracker() {
clear();
}
public void clear() {
releasePointerList(mPointerListHead);
mPointerListHead = null;
mLastTouchIndex = 0;
}
public void addMovement(MouseEvent ev) {
final int historySize = 0;
final int lastTouchIndex = mLastTouchIndex;
final int nextTouchIndex = (lastTouchIndex + 1) % NUM_PAST;
final int finalTouchIndex = (nextTouchIndex + historySize) % NUM_PAST;
mLastTouchIndex = finalTouchIndex;
if (mPointerListHead == null) {
mPointerListHead = obtainPointer();
}
final float[] pastX = mPointerListHead.pastX;
final float[] pastY = mPointerListHead.pastY;
final long[] pastTime = mPointerListHead.pastTime;
pastX[finalTouchIndex] = (float) ev.getX();
pastY[finalTouchIndex] = (float) ev.getY();
pastTime[finalTouchIndex] = System.currentTimeMillis();
}
public void computeCurrentVelocity(int units) {
computeCurrentVelocity(units, Float.MAX_VALUE);
}
public void computeCurrentVelocity(int units, float maxVelocity) {
final int lastTouchIndex = mLastTouchIndex;
for (Pointer pointer = mPointerListHead; pointer != null; pointer = pointer.next) {
final long[] pastTime = pointer.pastTime;
int oldestTouchIndex = lastTouchIndex;
int numTouches = 1;
final long minTime = pastTime[lastTouchIndex] - MAX_AGE_MILLISECONDS;
while (numTouches < NUM_PAST) {
final int nextOldestTouchIndex = (oldestTouchIndex + NUM_PAST - 1) % NUM_PAST;
final long nextOldestTime = pastTime[nextOldestTouchIndex];
if (nextOldestTime < minTime) { // also handles end of trace sentinel
break;
}
oldestTouchIndex = nextOldestTouchIndex;
numTouches += 1;
}
if (numTouches > 3) {
numTouches -= 1;
}
final float[] pastX = pointer.pastX;
final float[] pastY = pointer.pastY;
final float oldestX = pastX[oldestTouchIndex];
final float oldestY = pastY[oldestTouchIndex];
final long oldestTime = pastTime[oldestTouchIndex];
float accumX = 0;
float accumY = 0;
for (int i = 1; i < numTouches; i++) {
final int touchIndex = (oldestTouchIndex + i) % NUM_PAST;
final int duration = (int) (pastTime[touchIndex] - oldestTime);
if (duration == 0) {
continue;
}
float delta = pastX[touchIndex] - oldestX;
float velocity = (delta / duration) * units; // pixels/frame.
accumX = (accumX == 0) ? velocity : (accumX + velocity) * .5f;
delta = pastY[touchIndex] - oldestY;
velocity = (delta / duration) * units; // pixels/frame.
accumY = (accumY == 0) ? velocity : (accumY + velocity) * .5f;
}
if (accumX < -maxVelocity) {
accumX = -maxVelocity;
} else if (accumX > maxVelocity) {
accumX = maxVelocity;
}
if (accumY < -maxVelocity) {
accumY = -maxVelocity;
} else if (accumY > maxVelocity) {
accumY = maxVelocity;
}
pointer.xVelocity = accumX;
pointer.yVelocity = accumY;
}
}
public float getXVelocity() {
Pointer pointer = getPointer(0);
return pointer != null ? pointer.xVelocity : 0;
}
public float getYVelocity() {
Pointer pointer = getPointer(0);
return pointer != null ? pointer.yVelocity : 0;
}
public float getXVelocity(int id) {
Pointer pointer = getPointer(id);
return pointer != null ? pointer.xVelocity : 0;
}
public float getYVelocity(int id) {
Pointer pointer = getPointer(id);
return pointer != null ? pointer.yVelocity : 0;
}
private Pointer getPointer(int id) {
for (Pointer pointer = mPointerListHead; pointer != null; pointer = pointer.next) {
if (pointer.id == id) {
return pointer;
}
}
return null;
}
private static Pointer obtainPointer() {
if (sRecycledPointerCount != 0) {
Pointer element = sRecycledPointerListHead;
sRecycledPointerCount -= 1;
sRecycledPointerListHead = element.next;
element.next = null;
return element;
}
return new Pointer();
}
private static void releasePointer(Pointer pointer) {
if (sRecycledPointerCount < POINTER_POOL_CAPACITY) {
pointer.next = sRecycledPointerListHead;
sRecycledPointerCount += 1;
sRecycledPointerListHead = pointer;
}
}
private static void releasePointerList(Pointer pointer) {
if (pointer != null) {
int count = sRecycledPointerCount;
if (count >= POINTER_POOL_CAPACITY) {
return;
}
Pointer tail = pointer;
for (;;) {
count += 1;
if (count >= POINTER_POOL_CAPACITY) {
break;
}
Pointer next = tail.next;
if (next == null) {
break;
}
tail = next;
}
tail.next = sRecycledPointerListHead;
sRecycledPointerCount = count;
sRecycledPointerListHead = pointer;
}
}
}
And this is second class
import java.util.concurrent.atomic.AtomicReference;
import javafx.animation.FadeTransition;
import javafx.animation.Interpolator;
import javafx.animation.Timeline;
import javafx.animation.TranslateTransition;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Bounds;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;
public class VerticlePane extends Region {
private ObjectProperty<Node> content;
private final VelocityTracker tracker = new VelocityTracker();
private final Rectangle clipRect = new Rectangle();
private Rectangle scrollNode;
private boolean magnet, autoTranslate = true;
private DoubleProperty targetY = new SimpleDoubleProperty(), controlHeight = new SimpleDoubleProperty(), viewHeight = new SimpleDoubleProperty();
private double startValue, endValue;
private HBox indicator;
private StringProperty indicatorValue = new SimpleStringProperty("Pull Up to Update...");
public VerticlePane() {
setFocusTraversable(false);
setClip(clipRect);
contentProperty().addListener((ObservableValue<? extends Node> observable, Node oldValue, Node newValue) -> {
getChildren().clear();
if (newValue != null) {
getChildren().addAll(newValue, createRectangle());
}
layout();
});
final AtomicReference<MouseEvent> deltaEvent = new AtomicReference<>();
final AtomicReference<TranslateTransition> currentTransition1 = new AtomicReference<>();
final AtomicReference<TranslateTransition> currentTransition2 = new AtomicReference<>();
final AtomicReference<Timeline> timeline = new AtomicReference<>();
setOnMousePressed((me) -> {
tracker.addMovement(me);
deltaEvent.set(me);
startValue = me.getY();
if (currentTransition1.get() != null) {
currentTransition1.get().stop();
}
if (currentTransition2.get() != null) {
currentTransition2.get().stop();
}
if (timeline.get() != null) {
timeline.get().stop();
}
});
setOnMouseDragged((event) -> {
tracker.addMovement(event);
double delta = event.getY() - deltaEvent.get().getY();
targetY.set(content.get().getTranslateY() + delta);
content.get().setTranslateY(targetY.get());
controlHeight.set(content.get().getLayoutBounds().getHeight());
viewHeight.set(getHeight());
double scrollTargetY = targetY.divide(controlHeight.divide(viewHeight.get()).get()).get();
double lastInstanceHeight = controlHeight.subtract(viewHeight.get()).multiply(-1).get();
scrollNode.relocate(1.0, (getHeight() - 5.0));
scrollNode.setArcHeight(5.0);
scrollNode.setArcWidth(5.0);
scrollNode.setHeight(viewHeight.divide(controlHeight.divide(viewHeight.get()).get()).get());
scrollNode.setVisible(true);
scrollNode.setOpacity(0.25);
if (targetY.lessThan(0).and(targetY.greaterThan(lastInstanceHeight)).get()) {
scrollNode.setTranslateY(Math.abs(scrollTargetY));
} else if (targetY.greaterThanOrEqualTo(0).get()) {
scrollNode.setHeight(scrollNode.getHeight() - targetY.get());
}
if (targetY.get() < lastInstanceHeight) {
double scrollNodeHeight = scrollNode.getHeight() - (lastInstanceHeight - targetY.get());
double scrollNodeTranslateY = viewHeight.subtract(viewHeight.divide(controlHeight.divide(viewHeight.get()).get()).get()).add(lastInstanceHeight - targetY.get()).get();
scrollNode.setHeight(scrollNodeHeight);
scrollNode.setTranslateY(scrollNodeTranslateY);
}
deltaEvent.set(event);
});
setOnMouseReleased((me) -> {
FadeTransition ft = new FadeTransition(Duration.millis(300), scrollNode);
ft.setFromValue(0.25);
ft.setToValue(0.00);
tracker.addMovement(me);
tracker.computeCurrentVelocity(500);
endValue = me.getY();
controlHeight.set(content.get().getLayoutBounds().getHeight());
viewHeight.set(getHeight());
float velocityY = tracker.getYVelocity();
targetY.set(scrollNode(velocityY, currentTransition1, content.get()));
TranslateTransition tt = bb(currentTransition2, scrollNode, Math.abs((targetY.divide(controlHeight.divide(viewHeight.get()).get()).get())));
tt.setOnFinished((ae) -> {
ft.play();
});
});
}
public double getStartValue() {
return startValue;
}
public double getEndValue() {
return endValue;
}
public double getControlHeight() {
return controlHeight.get();
}
public double getViewHeight() {
return viewHeight.get();
}
public VerticlePane(double prefWidth, double prefHeight) {
this();
setPrefSize(prefWidth, prefHeight);
}
public VerticlePane(double prefWidth, double prefHeight, boolean magnet) {
this(prefWidth, prefHeight);
setMagnet(magnet);
}
public final void setMagnet(boolean magnet) {
this.magnet = magnet;
}
private TranslateTransition bb(AtomicReference<TranslateTransition> currentTransition, Node node, Double targetY) {
TranslateTransition translate = new TranslateTransition(new Duration(1000), node);
if (node != null) {
translate.setInterpolator(Interpolator.SPLINE(0.0513, 0.1131, 0.1368, 1.0000));
if (targetY != null) {
translate.setFromY(node.getTranslateY());
translate.setToY(targetY);
}
translate.play();
currentTransition.set(translate);
}
return translate;
}
private Double scrollNode(float velocityY, AtomicReference<TranslateTransition> currentTransition, Node node) {
final Bounds b = node.getLayoutBounds();
Double backBouncingY = null;
Double targetY = null;
if (node != null) {
TranslateTransition translate = new TranslateTransition(new Duration(1000), node);
translate.setInterpolator(Interpolator.SPLINE(0.0513, 0.1131, 0.1368, 1.0000));
if (Math.abs(velocityY) < 10) {
velocityY = 0;
}
targetY = node.getTranslateY();
double controlHeight = b.getHeight() - indicator.heightProperty().add(10).get();
double viewHeight = getHeight();
if (controlHeight < viewHeight && targetY < controlHeight) {
targetY = 0.0;
} else if (targetY > 0) {
targetY = 0.0;
} else if ((targetY < (controlHeight - viewHeight) * -1)) {
targetY = (controlHeight - viewHeight) * -1;
} else {
targetY += velocityY;
if (controlHeight < viewHeight && targetY < controlHeight) {
targetY = -25.0;
backBouncingY = 0.0;
} else if (targetY > 0) {
targetY = 25.0;
backBouncingY = 0.0;
} else if (targetY < (controlHeight - viewHeight) * -1) {
targetY = (controlHeight - viewHeight) * -1 - 25;
backBouncingY = (controlHeight - viewHeight) * -1;
}
}
//Magnet
if (magnet) {
double dragRegion = (viewHeight / 2) * -1;
double instances = controlHeight / viewHeight;
double lastInstance = ((controlHeight - viewHeight) * -1);
double newInstanceValue = dragRegion;
if (targetY > dragRegion) {
targetY = 0.0;
}
if (instances > 1) {
for (int i = 1; i < instances - 1; i++) {
double instanceValue = (viewHeight * i) * -1;
if (targetY < newInstanceValue && targetY > instanceValue || targetY < instanceValue && targetY > (instanceValue + dragRegion)) {
targetY = instanceValue;
}
newInstanceValue += (viewHeight * -1);
}
}
if (targetY < (lastInstance - dragRegion) && targetY > lastInstance) {
targetY = lastInstance;
}
}
//
if (targetY != null) {
translate.setFromY(node.getTranslateY());
translate.setToY(targetY);
}
if (backBouncingY != null) {
final Double fbackFlingY = backBouncingY;
translate.setOnFinished((ae) -> {
currentTransition.set(null);
TranslateTransition translate1 = new TranslateTransition(new Duration(300), node);
if (fbackFlingY != null) {
translate1.setFromY(node.getTranslateY());
translate1.setToY(fbackFlingY);
}
translate1.play();
currentTransition.set(translate1);
});
} else {
translate.setOnFinished((ae) -> {
currentTransition.set(null);
});
}
translate.play();
currentTransition.set(translate);
}
return targetY;
}
private Rectangle createRectangle() {
if (scrollNode == null) {
scrollNode = new Rectangle(4.0, 4.0, Color.BLACK);
}
scrollNode.setVisible(false);
return scrollNode;
}
public ObjectProperty<Node> contentProperty() {
if (content == null) {
content = new SimpleObjectProperty<>(this, "content");
}
return content;
}
public final void setContent(Node value) {
contentProperty().set(
new VBox(10) {
{
getChildren().addAll(value, indicator());
}
}
);
contentProperty().get().layoutBoundsProperty().addListener((ObservableValue<? extends Bounds> obs, Bounds ov, Bounds nv) -> {
Double containerHeight = nv.getHeight();
Double flingHeight = prefHeightProperty().get();
if (autoTranslate) {
if (flingHeight <= containerHeight) {
contentProperty().get().setTranslateY((containerHeight - flingHeight - 34) * -1);
}
}
});
}
private HBox indicator() {
return indicator = new HBox(10) {
{
setVisible(false);
setAlignment(Pos.CENTER);
getChildren().addAll(
new ImageView(new Image(getClass().getResourceAsStream("Indicator.gif"))),
new Label() {
{
textProperty().bind(indicatorValue);
setStyle("-fx-font-family: Roboto; -fx-font-size: 14px;");
}
}
);
}
};
}
public void setAutoTranslate(Boolean autoTranslate) {
this.autoTranslate = autoTranslate;
}
public void setTranslateZero() {
this.contentProperty().get().setTranslateY(0);
}
public HBox getIndicator() {
return indicator;
}
public void showIndicator() {
indicator.setVisible(true);
}
public void hideIndicator() {
indicator.setVisible(false);
}
public void setIndicatorValue(String value) {
indicatorValue.setValue(value);
}
public final Node getContent() {
return content == null ? null : content.get();
}
#Override
protected void layoutChildren() {
super.layoutChildren();
clipRect.setWidth(getWidth());
clipRect.setHeight(getHeight());
}
}
Scroll Bar disabled why?
It should not extend:
public class VerticlePane extends Control