Necesidad de hacer una línea especial seguir el dedo del usuario suavemente que también tiene otros comportamientos

En primer lugar esto no es un duplicado de otras preguntas de "línea suave" porque también necesito ser capaz de eliminar partes de mi línea a voluntad y como tal, necesito una forma especial de almacenar mi línea.

Necesito hacer una línea siguiendo el dedo del usuario. Sin embargo, también necesito ser capaz de eliminar el final de esta línea a voluntad.

Básicamente, necesito que el comportamiento de esta línea se parezca a la línea azul que sigue al ratón del usuario en este juego:

Http://hakim.se/experiments/html5/coil/

Para hacer esto tengo algún código en mi método onTouch que añade un punto a una matriz cada vez que el dedo del usuario se mueve.

@Override public boolean onTouch(View v, MotionEvent event) { //This for loop is supposed to add all points that were in between this //motion event and the previous motion event to the "linePoints" array. for(int i = 0; i < event.getHistorySize(); i++) { linePoints[arrayIndex] = new Point((int) event.getHistoricalX(i), (int) event.getHistoricalY(i)); arrayIndex++; } //This adds the current location of the user's finger to "linePoints" // array linePoints[arrayIndex] = new Point((int) event.getX(), (int) event.getY()); arrayIndex++; //This switch statement makes it so that if the user lifts their finger // off the screen the line will get deleted. switch (event.getAction()) { case MotionEvent.ACTION_DOWN: screenPressed = true; setEventTime(); //Ignore setEventTime(); break; case MotionEvent.ACTION_UP: screenPressed = false; linePoints = new Point[10000]; arrayIndex = 0; break; } return true; } 

Luego, en el método onDraw (), el juego dibuja cada punto de la línea:

  @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); //This code loops through all of linePoints and then draws every point // in linePoints to create a line on screen. for(Point p : linePoints) { if(p == null) { break; } canvas.drawRect(px, py, p.x+ 2, py + 2, black); invalidate(); // I have not added in the deletion behavior yet, because my current // method cannot create a smooth line. } 

La razón por la que elegí dibujar puntos para hacer una línea en lugar de usar la clase Path () de Android es porque quería borrar partes de la línea a voluntad (al eliminar puntos de la matriz "linePoints").

El problema es que si muevo mi dedo demasiado rápido, entonces los puntos se extienden y deja de parecer una línea.

¿Cómo puedo asegurarme de que la línea permanezca lisa pero también esté almacenada de tal manera que pueda eliminar partes de ella?

EDIT: Alguien pidió más detalles sobre cómo se detallará la línea por lo que voy a proporcionar.

Quiero comenzar a eliminar la línea si el usuario ha dibujado la línea durante más de "X" segundos. La forma en que quiero eliminar la línea es:

El final de la línea comenzará a desaparecer hasta que (mientras tanto, el usuario sigue dibujándolo) hasta que la línea se borre completamente o el usuario haya levantado el dedo de la pantalla.

EDIT 2: También necesito saber si la línea se ha intersecado o creado algún tipo de forma cerrada (de ahí por qué elegí el sistema de almacenamiento de punto, pensé que si 2 puntos en la matriz tenía las mismas coordenadas, entonces yo sabría si La línea se había cruzado). Actualmente no tengo idea de cómo implementar esto (porque los puntos no son continuos), pero voy a proporcionar más ediciones si se me ocurre algo.

EDIT 3: He descubierto una solución para determinar si la línea se cruza (incluso si los puntos están espaciados esporádicamente)! Sin embargo todavía no he resuelto el problema de crear una línea lisa sin brechas.

Solución:

Cada vez que el juego agrega un nuevo punto a la matriz lo comparará con el punto anterior que agregó a la matriz y modelará un segmento de línea "A". A continuación, comparará el segmento de línea "A" con todos los segmentos de línea anteriores formados a partir de 2 puntos en la matriz y determinará si los segmentos comparados se cruzan. Si lo hacen entonces sé que hay una intersección en la línea.

EDIT 4: Este es el código completo actualizado que estoy usando actualmente.

Dentro de este código (tratar) de proporcionar comentarios detallados y un resumen en la parte superior que explica mis objetivos y lo que he hecho hasta ahora.

Para hacer un prefacio a esta gran pieza de código, mi problema actual es ser capaz de eliminar la línea a un ritmo constante (por ejemplo, 10 milímetros por segundo) si el usuario ha estado dibujando su línea durante más de una cierta cantidad de tiempo.

  package com.vroy.trapper; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Point; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; public class GameView extends View implements View.OnTouchListener { // I am basically trying to replicate the game: http://hakim.se/experiments/html5/coil/ // without any of the lighting effects and my version will have slightly // different behavior. // Right now all I am concerned with is allowing the line to be deleted at a constant pace // if the user has been drawing a line for more than "X" seconds. /* OVERVIEW: array of points "linePoints" stores all locations of user touching screen that are captured by system. Each time a new point is added to "linePoints" I draw a path from the previous point to the new point. (Sidenote: While this does make the line look very smooth it can still look odd sometimes) The game also checks for intersections in the line to see if the line has made a polygon. I do this because this is important for a feature that will be implemented. The system then draws the path on screen. The system also checks if the user has lifted their finger off the screen, if the user has then the system deletes the current line on screen and resets all variables. TO BE IMPLEMENTED: If the line has formed a polygon then the game will check if that polygon contains certain objects that will randomly spawn onscreen. PROBLEMS: 1. Currently I want the line to start deleting itself from the back if the user has been drawing the line for more then "X" seconds. However I am not sure how to do this. */ // General variables. private int screenWidth; private int screenHeight; public static boolean screenPressed; //Might not need. // public static float contactLocX; // public static float contactLocY; //Time variables. private static long startTime; //This variable is used in conjunction with the //elapsedTime() method to determine if the user // has been drawing a line for more then "X" seconds. //Game variables. private static int orbWidth; //Not used currently. This will be the width of the randomly spawned tokens. private Point[] linePoints; //The array that holds all captured points. private int arrayIndex; private Path linePath; //The path that the canvas draws. private boolean firstPoint; //If firstPoint is true then that means is 1st point in current line. //I need this for the path.MoveTo() method. //Debug values. (Not used currently) private int debug; private String strdebug; //Paints Paint black = new Paint(); public GameView(Context context, AttributeSet attrs) { super(context, attrs); black.setARGB(255, 0, 0, 0); //Paint used to draw line. black.setStyle(Paint.Style.STROKE); black.setStrokeWidth(3); linePoints = new Point[10000]; GameView gameView = (GameView) findViewById(R.id.GameScreen); //Setting up onTouch listener. gameView.setOnTouchListener(this); arrayIndex = 0; linePath = new Path(); //Setting up initial path. firstPoint = true; } //Currently OnSizeChanged is not needed, I only keep it for the future when I implement // the random object spawning system. @Override public void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); screenHeight = getHeight(); screenWidth = getWidth(); orbWidth = screenHeight / 20; } @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawPath(linePath, black); //Currently "1000000000" is a placeholder value (in nano-seconds) if(elapsedTime() > 1000000000 ) { //Code that evenly deletes the line starting from the back //(this is where I most need your assistance). } invalidate(); //I don't know if this is the best way to refresh the screen } @Override public boolean onTouch(View v, MotionEvent event) { //Sets up starting point of path if(firstPoint) { firstPoint = false; linePath.moveTo(event.getX(),event.getY()); linePoints.add(new TimeStampedPoint((int)event.getX(), (int)event.getY(),event.getEventTime())); } //Adds points to path & linePoints that were missed. for(int i = 0; i < event.getHistorySize(); i++) { linePoints[arrayIndex] = new Point((int) event.getHistoricalX(i), (int) event.getHistoricalY(i)); linePath.lineTo(linePoints[arrayIndex].x,linePoints[arrayIndex].y); if(arrayIndex >= 1) { checkForIntersections(linePoints[arrayIndex - 1], linePoints[arrayIndex]); } arrayIndex++; } //Adds current point to path & linePath(); linePoints[arrayIndex] = new Point((int) event.getX(), (int) event.getY()); if (arrayIndex >= 1) { checkForIntersections(linePoints[arrayIndex - 1] ,linePoints[arrayIndex]); } linePath.lineTo(linePoints[arrayIndex].x,linePoints[arrayIndex].y); arrayIndex++; //This switch statements creates initial actions for when the finger is pressed/lifted. switch (event.getAction()) { case MotionEvent.ACTION_DOWN: screenPressed = true; setEventTime(); //This starts the timer that will eventually reach "X" seconds. break; case MotionEvent.ACTION_UP: //The primary purpose of this "switch" is to delete the old line // & reset variables in preparation for new line screenPressed = false; linePoints = new Point[10000]; //Possibly filling heap with empty arrays. linePath = new Path(); arrayIndex = 0; firstPoint = true; break; } return true; } private void checkForIntersections(Point p, Point p2) { for(int i = arrayIndex - 3; i > 0; i--) { if(intersect(p,p2,linePoints[i],linePoints[i-1])) { //RETURN POINTS IN THE POLYGON THAT WILL BE USED TO DETERMINE IF "TOKENS" // ARE IN POLYGON. } } } private void setEventTime() { startTime = System.nanoTime(); } //Checks current time since setEventTime private long elapsedTime() { return System.nanoTime() - startTime; } // Things used to determine intersections. //Used to determine orientation of <something> private static int orientation(Point p, Point q, Point r) { double val = (qy - py) * (rx - qx) - (qx - px) * (ry - qy); if (val == 0.0) return 0; // colinear return (val > 0) ? 1 : 2; // clock or counterclock wise } //Determines intersection of 2 lines (P1,Q1) & (P2,Q2). private static boolean intersect(Point p1, Point q1, Point p2, Point q2) { int o1 = orientation(p1, q1, p2); int o2 = orientation(p1, q1, q2); int o3 = orientation(p2, q2, p1); int o4 = orientation(p2, q2, q1); if (o1 != o2 && o3 != o4) return true; return false; } //Will shorten checking process by determining if 2 lines do/don't have the same bounding box. //Not yet implemented. private static boolean boundBoxCheck(Point p1, Point q1, Point p2, Point q2) { return true; //Placeholder code } } 

EDIT 5:

Hice mi implementación del código de stKent / Titan y mi código se bloquea debido a un error fuera de límites del índice.

Voy a estar tratando de encontrar el problema y arreglarlo, pero hasta que lo hago voy a publicar mi código aquí incase alguien quiere tomar una mano en la fijación.

  package com.vroy.trapper; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Point; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import java.sql.Time; import java.util.ArrayList; import java.util.List; public class GameView extends View implements View.OnTouchListener { // I am basically trying to replicate the game: http://hakim.se/experiments/html5/coil/ // without any of the lighting effects and my version will have slightly // different behavior. // Right now all I am concerned with is allowing the line to be deleted at a constant pace // if the user has been drawing a line for more than "X" seconds. /* OVERVIEW: array of points "linePoints" stores all locations of user touching screen that are captured by system. Each time a new point is added to "linePoints" I draw a path from the previous point to the new point. (Sidenote: While this does make the line look very smooth it can still look odd sometimes) The game also checks for intersections in the line to see if the line has made a polygon. I do this because this is important for a feature that will be implemented. The system then draws the path on screen. The system also checks if the user has lifted their finger off the screen, if the user has then the system deletes the current line on screen and resets all variables. TO BE IMPLEMENTED: If the line has formed a polygon then the game will check if that polygon contains certain objects that will randomly spawn onscreen. PROBLEMS: 1. Currently I want the line to start deleting itself from the back if the user has been drawing the line for more then "X" seconds. However I am not sure how to do this. */ // General variables. private int screenWidth; private int screenHeight; public static boolean screenPressed; //Might not need. // public static float contactLocX; // public static float contactLocY; //Time variables. private static long startTime; //This variable is used in conjunction with the //elapsedTime() method to determine if the user // has been drawing a line for more then "X" seconds. //Game variables. private static int orbWidth; //Not used currently. This will be the width of the randomly spawned tokens. private List<TimeStampedPoint> linePoints; //The array that holds all captured points. private int arrayIndex; private Path linePath; //The path that the canvas draws. private List<TimeStampedPoint> validPoints; private boolean firstPoint; //If firstPoint is true then that means is 1st point in current line. //I need this for the path.MoveTo() method. //Debug values. (Not used currently) private int debugint; private String strdebug; //Paints Paint black = new Paint(); public GameView(Context context, AttributeSet attrs) { super(context, attrs); black.setARGB(255, 0, 0, 0); //Paint used to draw line. black.setStyle(Paint.Style.STROKE); black.setStrokeWidth(3); linePoints = new ArrayList<>(); validPoints = new ArrayList<>(); GameView gameView = (GameView) findViewById(R.id.GameScreen); //Setting up onTouch listener. gameView.setOnTouchListener(this); arrayIndex = 0; linePath = new Path(); //Setting up initial path. validPoints = new ArrayList<>(); firstPoint = true; } //Currently OnSizeChanged is not needed, I only keep it for the future when I implement // the random object spawning system. @Override public void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); screenHeight = getHeight(); screenWidth = getWidth(); orbWidth = screenHeight / 20; } @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); linePath.rewind(); validPoints = removeExpiredPoints(); updatePathUsingPoints(validPoints); canvas.drawPath(linePath, black); linePoints = validPoints; invalidate(); //I don't know if this is the best way to refresh the screen } @Override public boolean onTouch(View v, MotionEvent event) { debugint = arrayIndex; strdebug = Integer.toString(debugint); Log.i("ARRAY INDEX: ",strdebug); debugint = linePoints.size(); strdebug = Integer.toString(debugint); Log.i("LIST SIZE: ",strdebug); //Sets up starting point of path if(firstPoint) { firstPoint = false; linePath.moveTo(event.getX(),event.getY()); linePoints.add(new TimeStampedPoint((int)event.getX(),(int)event.getY(),event.getEventTime())); } //Adds points to path & linePoints that were missed. for(int i = 0; i < event.getHistorySize(); i++) { linePoints.add(new TimeStampedPoint((int) event.getHistoricalX(i), (int) event.getHistoricalY(i), event.getHistoricalEventTime(i))); linePath.lineTo(linePoints.get(arrayIndex).x,linePoints.get(arrayIndex).y); if(arrayIndex >= 1) { checkForIntersections(linePoints.get(arrayIndex), linePoints.get(arrayIndex)); } arrayIndex++; } //Adds current point to path & linePath(); debugint = linePoints.size(); strdebug = Integer.toString(debugint); Log.i("Before" , strdebug); linePoints.add(new TimeStampedPoint((int) event.getX(), (int) event.getY(),event.getEventTime())); debugint = linePoints.size(); strdebug = Integer.toString(debugint); Log.i("After:", strdebug); if (arrayIndex >= 1) { checkForIntersections(linePoints.get(arrayIndex - 1) ,linePoints.get(arrayIndex)); } linePath.lineTo(linePoints.get(arrayIndex).x,linePoints.get(arrayIndex).y); arrayIndex++; //This switch statements creates initial actions for when the finger is pressed/lifted. switch (event.getAction()) { case MotionEvent.ACTION_DOWN: screenPressed = true; setEventTime(); //This starts the timer that will eventually reach "X" seconds. break; case MotionEvent.ACTION_UP: //The primary purpose of this "switch" is to delete the old line // & reset variables in preparation for new line screenPressed = false; linePoints.clear(); linePath = new Path(); arrayIndex = 0; firstPoint = true; break; } return true; } private void checkForIntersections(TimeStampedPoint p, TimeStampedPoint p2) { for(int i = arrayIndex - 3; i > 0; i--) { if(intersect(p,p2,linePoints.get(i),linePoints.get(i-1))) { //RETURN POINTS IN THE POLYGON THAT WILL BE USED TO DETERMINE IF "TOKENS" // ARE IN POLYGON. } } } private void setEventTime() { startTime = System.nanoTime(); } //Checks current time since setEventTime private long elapsedTime() { return System.nanoTime() - startTime; } // Things used to determine intersections. //Used to determine orientation of <something> private static int orientation(Point p, Point q, Point r) { double val = (qy - py) * (rx - qx) - (qx - px) * (ry - qy); if (val == 0.0) return 0; // colinear return (val > 0) ? 1 : 2; // clock or counterclock wise } //Determines intersection of 2 lines (P1,Q1) & (P2,Q2). private static boolean intersect(TimeStampedPoint p1, TimeStampedPoint q1, TimeStampedPoint p2, TimeStampedPoint q2) { int o1 = orientation(p1, q1, p2); int o2 = orientation(p1, q1, q2); int o3 = orientation(p2, q2, p1); int o4 = orientation(p2, q2, q1); if (o1 != o2 && o3 != o4) return true; return false; } //Will shorten checking process by determining if 2 lines do/don't have the same bounding box. //Not yet implemented. private static boolean boundBoxCheck(Point p1, Point q1, Point p2, Point q2) { return true; //Placeholder code } //Point class that also stores time of creation @SuppressLint("ParcelCreator") private static class TimeStampedPoint extends Point { private final long timeStamp; private TimeStampedPoint(final int x, final int y, final long timeStamp) { super(x, y); this.timeStamp = timeStamp; } } private List<TimeStampedPoint> removeExpiredPoints() { final List<TimeStampedPoint> result = new ArrayList<>(); for (final TimeStampedPoint point: linePoints) { if (System.currentTimeMillis() - point.timeStamp <= 10000) { // We only include points in the result if they are not expired. result.add(point); } } return result; } private void updatePathUsingPoints(final List<TimeStampedPoint> validPoints) { if (validPoints.size() < 2) { return; // Return the empty path here; nothing to draw. } linePath.moveTo(validPoints.get(0).x,validPoints.get(0).y); for (int i = 1; i < validPoints.size(); i++) { final Point targetPoint = validPoints.get(i); linePath.lineTo(targetPoint.x, targetPoint.y); } } } 

También hay algo más que es muy muy importante que debo tener en cuenta. Creo que es mi culpa por no anotar esto hasta editar 4, pero mientras que quiero que la línea de ser eliminado desde el final también me gustaría que se suprimirá de manera uniforme, creo que el código actual proporcionado por stkent y Titan elimina los puntos en el Line a un ritmo constante pero que en realidad no significa que la línea en sí se eliminará a un ritmo constante (porque los puntos se distribuyen de manera desigual).

Muchas gracias a todos por pegarme a través de las numerosas ediciones hasta ahora espero que se pueda encontrar una solución que también permita borrar la línea a un ritmo constante.

Basado en su código más reciente, esto es lo que intentaría primero. Estoy haciendo los siguientes supuestos en esta respuesta:

  • Sólo dibujarás una línea / ruta en un momento dado (si no, necesitarás realizar el procedimiento descrito a continuación para cada ruta, iterando sobre alguna colección de rutas)

Cree un contenedor alrededor de la clase Point que añada una marca de tiempo:

 private static class TimeStampedPoint extends Point { private final long timeStamp; private TimeStampedPoint(final int x, final int y, final long timeStamp) { super(x, y); this.timeStamp = timeStamp; } } 

A continuación, actualice su almacenamiento de punto a lo siguiente:

 List<TimeStampedPoint> linePoints = new ArrayList<>(); 

(Tendrá que hacer un montón de cambios en el código como resultado de arrayIndex particular, puede utilizar el método List add añadir nuevos puntos al final de esta lista, en lugar de rastrear el arrayIndex explícitamente).

En su método onTouchEvent , reemplace este bloque de código:

 for(int i = 0; i < event.getHistorySize(); i++) { linePoints[arrayIndex] = new Point((int) event.getHistoricalX(i), (int) event.getHistoricalY(i)); linePath.lineTo(linePoints[arrayIndex].x,linePoints[arrayIndex].y); if(arrayIndex >= 1) { checkForIntersections(linePoints[arrayIndex - 1], linePoints[arrayIndex]); } arrayIndex++; } 

Con algo que se parece a esto:

 for(int i = 0; i < event.getHistorySize(); i++) { TimeStampedPoint point = new TimeStampedPoint((int) event.getHistoricalX(i), (int) event.getHistoricalY(i), event.getHistoricalEventTime(i)); linePoints.add(point); linePath.lineTo(point.x, point.y); int numberOfPoints = linePoints.size(); if(numberOfPoints >= 2) { checkForIntersections(linePoints.get(numberOfPoints - 2), linePoints.get(numberOfPoints - 1)); } } 

Haga un ajuste similar en cualquier otro lugar que agregue valores a la matriz linePoints . Tenga en cuenta también que ya no creamos la Path incremental durante este bucle. Esto se debe a que realizaremos un proceso de desinfección (es decir, eliminar los puntos expirados) antes de construir el Path . Para ello, borre el linePath cada vez que se prepare para dibujar (es posible que pueda mover este método en otro lugar si el rendimiento es pobre, sólo estoy sugiriendo que suceda en onDraw para hacer claro el ciclo de vida sugerido). Su método onDraw se vería así:

 @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); // Reset the Path. linePath.rewind(); validPoints = removeExpiredPoints(); updatePathUsingPoints(validPoints); canvas.drawPath(linePath, black); linePoints = validPoints; invalidate(); //I don't know if this is the best way to refresh the screen } 

Donde validPoints es otro campo del tipo List<TimeStampedPoint> s. [En general, llamar a invalidate desde dentro onDraw probablemente no es la mejor idea, pero eso está fuera del alcance de esta pregunta.]

Aquí se han introducido dos nuevos métodos:

 private List<TimeStampedPoint> removeExpiredPoints() { final List<TimeStampedPoint> result = new ArrayList<>(); for (final TimeStampedPoint point: linePoints) { if (System.uptimeMillis() - point.getTimeStamp <= 10000) { // We only include points in the result if they are not expired. result.add(point); } } return result; } 

y

 private void updatePathUsingPoints(final List<TimeStampedPoint> validPoints) { if (validPoints.size() < 2) { return linePath; // Return the empty path here; nothing to draw. } linePath.moveTo(validPoints.get(0)); for (int i = 1; i < validPoints.size(); i++) { final Point targetPoint = validPoints.get(i); linePath.lineTo(targetPoint.x, targetPoint.y); } } 

Esperemos que esto le da suficiente de un marco para empezar. Si te das cuenta de que el final de la línea es desigual, tengo ideas que pueden ayudar, pero es un montón más de mecanografía – así que no optimizamos prematuramente 🙂

Sugiero utilizar un ArrayList en lugar de una matriz estática, ya que no siempre es necesario para almacenar 10000 puntos. También sugiero hacer una subclase de punto, y tenerlo almacenar una marca de tiempo en la instancia. Considerar:

 public class TimedPoint extends Point { private static final int KEEP_ALIVE_TIME_MS = 200; //tweak this value to your needs private final long time; public TimedPoint(int x, int y) { super(x, y); time = System.currentTimeMillis(); } public TimedPoint(int x, int y, long time) { super(x, y); this.time = time; } public boolean hasExpired(long time) { return (time-this.time>KEEP_ALIVE_TIME_MS); } } public class GameView extends View ... { ArrayList<TimedPoint> linePoints = new ArrayList<>(); //Lists can grow and shrink to demand //this implementation is backed by an array. ... public void addPoint(int x, int y) { linePoints.add(new TimedPoint(x, y); } public void removeOldPoints() { long time = System.currentTimeMillis(); Iterator<TimedPoint> i = linePoints.iterator(); while(i.hasNext()) { TimedPoint point = i.next(); if(point.hasExpired(time)) i.remove(); } } } 

removeOldPoints() eliminará los puntos de los puntos de linePoints cuya diferencia de tiempo es mayor que el umbral definido en TimedPoint . Esto supone que puede llamar a removeOldPoints() regularmente. Sugerencia de pista, llamando a onDraw() sería genial.

Si se llama a removeOldPoints() en onDraw antes de onDraw la línea, se puede garantizar que se linePoints cualquier punto que se mantenga en linePoints . En ese punto es tan simple como iterar sobre la lista y dibujar los puntos como una línea, y la "cola" comenzará a desaparecer a medida que dibuja.


También podría pasar linePoints de linePoints a TimedPoint y establecer un Timer en la construcción, y schedule() cada TimedPoint para eliminar a sí mismo en un momento determinado en el futuro. Esto no supone que puede llamar a removeOldPoints() regularmente. Considerar:

 public class TimedPoint extends Point { private static final long KEEP_ALIVE_TIME_MS = 200; //tweak this value to your needs //we don't need a timestamp, because every point disposes of itself. We do need a timer, though. private final Timer lifetime = new Timer(); public TimedPoint(final List<TimedPoint> linePoints, int x, int y) { super(x, y); lifetime.schedule(new TimerTask() { @Override public void run() { linePoints.remove(TimedPoint.this); } }, KEEP_ALIVE_TIME_MS); } } public class GameView extends View ... { List<TimedPoint> linePoints = Collections.synchronizedList(new ArrayList<>()); //Lists can grow and shrink to demand //this implementation is backed by an array. //and is thread safe for Timer ... public void addPoint(int x, int y) { linePoints.add(new TimedPoint(x, y); } //notice removeOldPoints() is gone! Each point now disposes of itself, no calls needed. } 

Hay un par de cosas que podría ajustar con este enfoque también. Por ejemplo, los puntos empiezan a "morir" tan pronto como "nacen". Podemos cambiar eso solo cuando se agrega a la lista, si eso es más apropiado.

Además, es probable que haya espacio para la optimización, así, ya que creo que esto puede generar un nuevo hilo por punto. Esto realmente debería mejorar el rendimiento (si removeOldPoints() era el cuello de botella), hasta que su CPU se ve paralizada por los cambios de contexto. Si te sientes pedante, o el rendimiento se convierte en un problema; Usted podría utilizar un filtrado y una cola.

Aquí está la documentación de ArrayList para ayudarle a aclimatarse a la nueva clase.

Feliz codificación


EDITAR , parece que sigue teniendo problemas. Prueba esto y hazme saber lo que hace por ti.

 public class GameView ... { ArrayList<TimedPoint> linePoints = new ArrayList<>(); //Lists can grow and shrink to demand //this implementation is backed by an array. ... @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); removeOldPoints(); Path path = linePointsToPath(); //I'm not sure if you need to store path, let's generate it. if(path != null) canvas.drawPath(path, black); } public void addPoint(int x, int y) { linePoints.add(new TimedPoint(x, y); invalidate(); } public void removeOldPoints() { int oldLen = linePoints.size(); long time = System.currentTimeMillis(); Iterator<TimedPoint> i = linePoints.iterator(); while(i.hasNext()) { TimedPoint point = i.next(); if(point.hasExpired(time)) i.remove(); } int newLen = linePoints.size(); if(newLen != oldLen) //if we removed items from list invalidate(); } //small tweaks to stKents method private Path linePointsToPath() { if(linePoints.size() < 2) return null; Path path = new Path(); Point p = points.get(0); Path.moveTo(px, py); for(Point point : linePoints) { if(p != point) path.lineTo(point.x, point.y); //skip first point, because of moveTo } return path; } @Override public boolean onTouch(View v, MotionEvent event) { ... addPoint(...); } } 
FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.