需要使专线平滑地跟随用户的手指,也有其他行为



首先,这不是其他"平滑行"问题的重复,因为我还需要能够随意删除我的行的部分,因此我需要一种特殊的方式来存储我的行。

我需要在用户的手指后面画一条线。然而,我也需要能够随意删除这一行的末尾。

基本上,我需要这条线的行为看起来像这个游戏中用户鼠标后面的蓝线:

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(p.x, p.y, p.x+ 2, p.y + 2, black);
        invalidate();
    // I have not added in the deletion behavior yet, because my current
    // method cannot create a smooth line.
    }

我选择画点划线而不是使用Android的原因Path()类是因为我想随意删除部分线条(通过从数组"linePoints"中删除点)。

问题是,如果我的手指移动得太快,那么这些点就会散开,看起来就不再像一条线了。

我如何确保线条保持平滑,同时也以我可以删除部分线条的方式存储?

编辑:有人问我更多关于线路细节的细节,所以我会提供。

如果用户绘制该线的时间超过"X"秒,我希望开始删除该线。我想删除行的方式是:

线的末端将开始消失,直到(同时用户仍在绘制)线被完全删除或用户将手指从屏幕上移开。

编辑2:我还需要知道直线是否与自己相交或创建了某种封闭形状(因此我选择点存储系统的原因是,我认为如果阵列中的两个点具有相同的坐标,那么我就会知道直线是否与其相交)。我目前不知道如何实现这一点(因为要点不是连续的),但如果我找到了什么,我会提供进一步的编辑。

第三版:我已经找到了一个解决方案,可以确定直线是否与自己相交(即使点偶尔间隔开)!然而,我仍然没有解决创建一条没有间隙的平滑线的问题。

解决方案:

每次游戏向数组中添加新点时,都会将其与添加到数组中的前一点进行比较,并为线段"a"建模。然后,它将线段"A"与阵列中由2个点组成的所有先前线段进行比较,并确定比较的线段是否相交。如果他们这样做,那么我知道这条线上有一个交叉点。

第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 = (q.y - p.y) * (r.x - q.x)
                - (q.x - p.x) * (r.y - q.y);
        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
    }

    }

编辑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 = (q.y - p.y) * (r.x - q.x)
                - (q.x - p.x) * (r.y - q.y);
        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提供的当前代码以一致的速度删除了这条线中的点,但这实际上并不意味着这条线本身将以一致的步伐删除(因为点分布不均匀)。

非常感谢所有人在众多编辑中一直支持我,直到现在,我希望能找到一个解决方案,也允许以一致的速度删除该行。

我建议使用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();
        }
    }
}

CCD_ 1将从CCD_ 2中移除其时间差大于CCD_ 3中定义的阈值的任何点。这假设您可以定期致电removeOldPoints()。提示提示,调用onDraw()会很棒。

如果在绘制线之前在onDraw中调用了removeOldPoints(),则可以保证应绘制linePoints中的任何点。在这一点上,只需迭代列表并将点绘制为一条线,"尾巴"就会在绘制时开始消失。


您也可以将linePoints传递给removeOldPoints()0,并在构建时设置一个Timerschedule()每个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()是瓶颈),直到您的cpu被上下文切换损坏为止。如果你觉得迂腐,或者表现成了一个问题;您可以使用线程池和队列。

以下是ArrayList的文档,可帮助您适应新类。

快乐编码:)


编辑您似乎仍有问题。试试这个,让我知道它对你有什么好处。

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(p.x, p.y);
        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(...);
    }
}

基于您最近的代码,以下是我首先尝试的内容。我在这个答案中做了以下假设:

  • 在任何给定时间,您将只绘制一条线/路径(如果不是,则需要通过迭代某些路径集合,为每条路径执行下面列出的过程)

围绕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方法add将新点添加到此列表的末尾,而不是显式跟踪linePoints0。)

在您的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
}

其中validPointsList<TimeStampedPoint> s类型的另一个字段。[通常,从TimedPoint1内部调用invalidate可能不是最好的主意,但这超出了这个问题的范围。]

这里介绍了两种新方法:

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

希望这能给你一个足够的框架来开始。如果你注意到行的末尾不稳,我有一些想法可以帮助你,但这需要更多的打字——所以我们不要过早地优化:)

最新更新