Необходимо сделать специальную линию, следуя пальцем пользователя плавно, что также имеет другое поведение

Во-первых, это не дубликат других вопросов «гладкой линии», потому что я также должен иметь возможность удалять части своей строки по своему усмотрению, и поэтому мне нужен особый способ хранения моей строки.

Мне нужно сделать линию, следуя пальцем пользователя. Однако мне также нужно иметь возможность удалять конец этой строки по своему усмотрению.

В основном мне нужно, чтобы поведение этой строки выглядело как синяя линия, следующая за мышью пользователя в этой игре:

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

Для этого у меня есть код в моем методе onTouch, который добавляет точку в массив каждый раз, когда палец пользователя перемещается.

@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; } 

Затем в методе onDraw () игра рисует каждую точку на линии:

  @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. } 

Причина, по которой я решил нарисовать точки, чтобы сделать строку вместо использования класса Path (), – это потому, что я хотел удалить части строки по желанию (удалив точки из массива «linePoints»).

Проблема в том, что если я слишком быстро передвигаю палец, то точки разбросаны и перестают выглядеть как линия.

Как я могу убедиться, что линия остается гладкой, но также хранится таким образом, что я могу удалить ее части?

EDIT: Кто-то попросил более подробно о том, как строка будет подробно описана, поэтому я предоставлю.

Я хочу начать удаление строки, если пользователь рисовал строку более чем за «X» секунд. Я хочу удалить строку:

Конец строки начнет исчезать до тех пор, пока пользователь пока не выполнит его рисование до тех пор, пока линия не будет полностью удалена, или пользователь не отпустит свой палец от экрана.

EDIT 2: Мне также нужно знать, пересекла ли линия себя или создала какую-то закрытую форму (поэтому, почему я выбрал систему хранения точек, я думал, что если 2 точки в массиве имеют одинаковые координаты, я бы знал, если Линия пересекла себя). В настоящее время я понятия не имею, как реализовать это (потому что точки не являются непрерывными), но я дам дополнительные изменения, если я что-то придумаю.

РЕДАКТИРОВАТЬ 3: Я выяснил решение, чтобы определить, пересекает ли линия себя (даже если точки распределены спорадически)! Однако я еще не решил проблему создания гладкой линии без пробелов.

Решение:

Каждый раз, когда игра добавляет новую точку в массив, она сравнивает ее с предыдущей точкой, добавленной в массив, и моделирует сегмент линии «А». Затем он сравнивает сегмент линии «А» со всеми предыдущими сегментами линии, сделанными из 2-х точек в массиве, и определяет, пересекаются ли сегменты сравнения. Если они это сделают, я знаю, что есть пересечение в линии.

EDIT 4: Это полный обновленный код, который я использую в настоящее время.

Внутри этого кода я (попробую) предоставить подробные комментарии и резюме наверху, которые объясняют мои цели и что я сделал до сих пор.

Чтобы предисловие к этому большому фрагменту кода, моя текущая проблема заключается в том, что вы удаляете линию с непрерывным темпом (например, 10 миллиметров в секунду), если пользователь рисовал свою линию более определенного времени.

  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:

Я выполнил свою реализацию кода stKent / Titan, и мои кодовые сбои из-за ошибки индекса за пределами границ.

Я попытаюсь найти проблему и исправить ее, но до тех пор, пока не сделаю, я отправлю свой код здесь, если кто-то еще захочет взять ее за фиксацию.

  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); } } } 

Есть и еще кое-что, что очень важно, что я должен отметить. Я считаю, что это моя ошибка, если не отметить это до редактирования 4, но пока я хочу, чтобы строка была удалена с конца, мне также хотелось бы, чтобы она была удалена равномерно, я думаю, что текущий код, предоставляемый stkent и Titan, удаляет точки в Но на самом деле это означает, что сама линия будет удалена с непрерывным темпом (поскольку точки распределены неравномерно).

Огромное спасибо всем за то, что вы продолжаете пользоваться мной через многочисленные изменения до сих пор, я надеюсь, что можно найти решение, которое также позволяет удалять строку в последовательном порядке.

Solutions Collecting From Web of "Необходимо сделать специальную линию, следуя пальцем пользователя плавно, что также имеет другое поведение"

Основываясь на вашем последнем коде, вот что я попробую в первую очередь. Я делаю следующие предположения в этом ответе:

  • Вы будете рисовать только одну строку / путь в любой момент времени (если нет, вам нужно будет выполнить описанную ниже процедуру для каждого пути, итерации по некоторой коллекции путей)

Создайте обертку вокруг класса Point которая добавляет метку времени:

 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; } } 

Затем обновите хранилище точек следующим образом:

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

(В результате вы должны будете сделать кучу изменений в коде. В частности, вы можете использовать метод List чтобы добавить новые точки в конец этого списка, а не отслеживать arrayIndex явно.)

В методе onTouchEvent замените этот блок кода:

 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++; } 

С чем-то, что выглядит так:

 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)); } } 

Сделайте аналогичную настройку везде, где вы добавляете значения в массив linePoints . Также обратите внимание, что мы больше не создаем Path поэтапно во время этого цикла. Это связано с тем, что перед конструированием Path мы выполним некоторую дезинфекцию (то есть удалим истекшие точки). Чтобы сделать это, linePath каждый раз, когда вы готовитесь рисовать (вы можете переместить этот метод где-нибудь еще, если производительность низкая, я просто предлагаю, чтобы это произошло в onDraw чтобы сделать предлагаемый жизненный цикл понятным). Ваш метод onDraw будет выглядеть примерно так:

 @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 } 

Где validPoints – другое поле типа List<TimeStampedPoint> s. [В общем, вызов invalidate изнутри onDraw , вероятно, не самая лучшая идея, но это выходит за рамки этого вопроса.]

Здесь были введены два новых метода:

 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; } 

а также

 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); } } 

Надеюсь, это даст вам достаточно возможностей для начала работы. Если вы заметили, что исчезающий конец строки отрывистый, у меня есть идеи, которые могут помочь, но это пучок больше набрав – так что давайте не будем преждевременно оптимизировать 🙂

Я предлагаю использовать ArrayList вместо статического массива, так как вам не всегда нужно хранить 10000 очков. Я также предлагаю создать подкласс Point и сохранить его при создании экземпляра. Рассматривать:

 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() удалит любые точки из linePoints чья разница во времени больше порога, определенного в TimedPoint . Это предполагает, что вы можете вызывать removeOldPoints() регулярно. Подсказка подсказка, вызов в onDraw() будет замечательным.

Если removeOldPoints() вызывается в onDraw до того, как линия будет нарисована, вы можете гарантировать, что любая точка, которая хранится в linePoints должна быть нарисована. В этот момент это так же просто, как итерация по списку и рисование точек в виде строки, а «хвост» начнет исчезать при рисовании.


Вы также можете передавать linePoints в TimedPoint и устанавливать Timer при построении и schedule() каждый TimedPoint чтобы удалить себя в определенное время в будущем. Это не означает, что вы можете называть removeOldPoints() регулярно. Рассматривать:

 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. } 

Есть несколько вещей, которые вы могли бы настроить с помощью этого подхода. Например, очки начинают «умирать», как только они «рождаются». Мы можем изменить это только при добавлении в список, если это более уместно.

Кроме того, вероятно, есть место для оптимизации, так как я думаю, что это может порождать новый поток за точку. Это должно фактически повысить производительность (если removeOldPoints() было узким местом), до тех пор, пока ваш процессор не будет искалечен контекстными коммутаторами. Если вы чувствуете педантичность, или производительность становится проблемой; Вы можете использовать threadpool и очередь.

Вот документация для ArrayList которая поможет вам перейти на новый класс.

Счастливое кодирование 🙂


EDIT, похоже, у вас все еще есть проблемы. Попробуйте это и дайте мне знать, что он делает для вас.

 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(...); } }