通过用户交互绘制到画布有点滞后



我开始用Android学习画布绘制,我想做一个简单的应用程序。

在应用程序上启动所谓的"蛇"开始在屏幕上移动,当用户轻击屏幕时,"蛇"改变方向。

我很容易做到这一点,但有一个小问题:

当用户点击屏幕时,蛇有时会在特定时刻改变方向,有时会在几毫秒后改变方向。所以用户可以清楚地感觉到交互没有像它应该的那样响应,蛇的确切转弯时刻是非常不可预测的即使你非常努力地集中注意力。一定有比我做得更好的方法。

请检查我的代码,我使用一个处理程序与Runnable移动蛇。(在画布上绘制,每次将其设置为视图的背景,即每次设置setContentView到我的活动。

代码:

public class MainActivity extends Activity implements View.OnClickListener {
    Paint paint = new Paint();
    Canvas canvas;

    View contentView; ///<The view to set by setContentView
    int moveSize; ///<Sze in px to move drawer to draw another square

    int leftIndex; ///<Indexes for square drawing
    int topIndex;
    int rightIndex;
    int bottomIndex;
    int maxBoundsX; ///<The max number of squares in X axis
    int maxBoundsY; ///<The max number of squares in Y axis
    int moveSpeedInMillis = 25; ///<One movement square per milliseconds
    Bitmap bitmapToDrawOn; ///<We draw the squares to this bitmap
    Direction currentDirection = Direction.RIGHT; ///< RIGHT,LEFT,UP,DOWN directions. default is RIGHT
    Handler  handler  = new Handler(); ///<Handler for running a runnable that actually 'moves' the snake by drawing squares to shifted positions
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            Log.i("runnable", "ran");
            //Drawing a square to the current destination
            drawRectPls(currentDirection);
            //After some delay we call again and again and again
            handler.postDelayed(runnable, moveSpeedInMillis);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        /*getting area properties like moveSize, and bounds*/
        moveSize = searchForOptimalMoveSize();
        maxBoundsX = ScreenSizer.getScreenWidth(this) / moveSize;
        maxBoundsY = ScreenSizer.getScreenHeight(this) / moveSize;
        Log.i("moveSize", "moveSize: " + moveSize);
        Log.i("maxBounds: ", "x: " + maxBoundsX + " ; " + "y: " + maxBoundsY);

        /*setting start pos*/
        //We start on the lower left part of the screen
        leftIndex = moveSize * (-1);
        rightIndex = 0;
        bottomIndex = moveSize * maxBoundsY;
        topIndex = moveSize * (maxBoundsY - 1);

        //Setting contentView, bitmap, and canvas
        contentView = new View(this);
        contentView.setOnClickListener(this);
        bitmapToDrawOn = Bitmap.createBitmap(ScreenSizer.getScreenWidth(this), ScreenSizer.getScreenHeight(this), Bitmap.Config.ARGB_8888);
        canvas = new Canvas(bitmapToDrawOn);
        contentView.setBackground(new BitmapDrawable(getResources(), bitmapToDrawOn));
        setContentView(contentView);
        /*starts drawing*/
        handler.post(runnable);

    }
    /**
     * Draws a square to the next direction
     *
     * @param direction the direction to draw the next square
     */
    private void drawRectPls(Direction direction) {
        if (direction.equals(Direction.RIGHT)) {
            leftIndex += moveSize;
            rightIndex += moveSize;
        } else if (direction.equals(Direction.UP)) {
            topIndex -= moveSize;
            bottomIndex -= moveSize;
        } else if (direction.equals(Direction.LEFT)) {
            leftIndex -= moveSize;
            rightIndex -= moveSize;
        } else if (direction.equals(Direction.DOWN)) {
            topIndex += moveSize;
            bottomIndex += moveSize;
        }

        addRectToCanvas();
        contentView.setBackground(new BitmapDrawable(getResources(), bitmapToDrawOn));
        Log.i("drawRect", "direction: " + currentDirection);
    }

    private void addRectToCanvas() {
        paint.setColor(Color.argb(255, 100, 100, 255));
        canvas.drawRect(leftIndex, topIndex, rightIndex, bottomIndex, paint);
    }
    /**
     * Upon tapping the screen the the snake is changing direction, one way simple interaction
     */
    @Override
    public void onClick(View v) {
        if (v.equals(contentView)) {
            if (currentDirection.equals(Direction.RIGHT)) {
                currentDirection = Direction.UP;
            } else if (currentDirection.equals(Direction.UP)) {
                currentDirection = Direction.LEFT;
            } else if (currentDirection.equals(Direction.LEFT)) {
                currentDirection = Direction.DOWN;
            } else if (currentDirection.equals(Direction.DOWN)) {
                currentDirection = Direction.RIGHT;
            }
        }
    }

    /**
     * Just getting the size of a square. Searching for an integer that divides both screen's width and height
     * @return
     */
    private int searchForOptimalMoveSize() {
        int i;
        for (i = 16; i <= 64; i++) {
            Log.i("iter", "i= " + i);
            if (ScreenSizer.getScreenWidth(this) % i == 0) {
                Log.i("iter", ScreenSizer.getScreenWidth(this) + "%" + i + " =0 !");
                if (ScreenSizer.getScreenHeight(this) % i == 0) {
                    Log.i("iter", ScreenSizer.getScreenHeight(this) + "%" + i + " =0 !");
                    return i;
                }
            }
        }
        return -1;
    }

    /**
     * Stops the handler
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        handler.removeCallbacks(runnable);
    }
}

E D I T:

我已经修改了我的代码,现在视图包含了所有的细节,我使用onDrawinvalidate方法就像Philipp建议的。

结果稍微好一点,但是我仍然可以清楚地感觉到用户交互导致了一个滞后的方向变化

也许我应该用线程做些什么?
public class SpiralView extends View implements View.OnClickListener {
    int leftIndex; ///<Indexes for square drawing
    int topIndex;
    int rightIndex;
    int bottomIndex;
    int speedInMillis = 50;
    int moveSize; ///<Sze in px to move drawer to draw another square
    int maxBoundsX; ///<The max number of squares in X axis
    int maxBoundsY; ///<The max number of squares in Y axis

    Paint paint = new Paint();
    Direction currentDirection = Direction.RIGHT; ///< RIGHT,LEFT,UP,DOWN directions. default is RIGHT

    public void setUp(int moveSize, int maxBoundsX, int maxBoundsY) {
        this.moveSize = moveSize;
        this.maxBoundsX = maxBoundsX;
        this.maxBoundsY = maxBoundsY;
        this.leftIndex = moveSize * (-1);
        this.rightIndex = 0;
        this.bottomIndex = moveSize * (maxBoundsY);
        this.topIndex = moveSize * (maxBoundsY - 1);
    }
    public SpiralView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setOnClickListener(this);

    }
    /**
     * Draws a square to the next direction
     *
     * @param direction the direction to draw the next square
     */
    private void drawOnPlease(Direction direction, Canvas canvas) {
        if (direction.equals(Direction.RIGHT)) {
            leftIndex += moveSize;
            rightIndex += moveSize;
        } else if (direction.equals(Direction.UP)) {
            topIndex -= moveSize;
            bottomIndex -= moveSize;
        } else if (direction.equals(Direction.LEFT)) {
            leftIndex -= moveSize;
            rightIndex -= moveSize;
        } else if (direction.equals(Direction.DOWN)) {
            topIndex += moveSize;
            bottomIndex += moveSize;
        }

        Log.i("drawRect", "direction: " + currentDirection);
        Log.i("drawRect", "indexes: "+topIndex+" , "+rightIndex+" ," +bottomIndex+" , "+leftIndex);
        addRectToCanvas(canvas);
    }
    private void addRectToCanvas(Canvas canvas) {
        paint.setColor(Color.argb(255, 100, 100, 255));
       canvas.drawRect(leftIndex, topIndex, rightIndex, bottomIndex, paint);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        try {
            drawOnPlease(currentDirection, canvas);
            synchronized (this) {
                wait(speedInMillis);
            }

            invalidate();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void onClick(View v) {
        if (currentDirection.equals(Direction.RIGHT)) {
            currentDirection = Direction.UP;
        } else if (currentDirection.equals(Direction.UP)) {
            currentDirection = Direction.LEFT;
        } else if (currentDirection.equals(Direction.LEFT)) {
            currentDirection = Direction.DOWN;
        } else if (currentDirection.equals(Direction.DOWN)) {
            currentDirection = Direction.RIGHT;
        }
        //..?
        invalidate();
    }
}

这个神奇的数字是16毫秒,android重绘视图没有帧降。

查看android开发人员的视频,其中解释了这一点。https://www.youtube.com/watch?v=CaMTIgxCSqU&指数= 25,列表= PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE

特别看看这个视频https://youtu.be/vkTn3Ule4Ps?t=54

它解释了如何使用画布剪贴,以便不在每个圆圈中绘制整个表面,而只绘制需要绘制的部分。

不要使用Handler来绘制Canvas。相反,你应该创建一个自定义视图并使用onDraw(Canvas Canvas)方法。在这个方法中,你可以像之前那样在Canvas对象上绘图。通过调用invalidate()方法,你触发了一个新的onDraw()调用。在onTouch()或onClick()函数中,你还可以通过调用invalidate()

触发一个新的onDraw调用
class SnakView extends View {
  @Override
  protected void onDraw(Canvas canvas) {
    // draw on canvas
    invalidate();
  }
  @Override
  public void onClick(View v) {
    // handle the event
    invalidate();
  }
}

您可以尝试将android:hardwareAccelerated="true"添加到您的清单中,或添加到。

这将适用于3.0+的设备。

你的目标api级别应该是11。

最新更新