I began to study canvas drawing with Android, and I would like to make a simple application.
The application runs the so-called "snake", which begins to move around the screen , when the user clicks on the screen, the "snake" changes direction.
I easily dealt with this, but there is a small problem:
When the user clicks on the screen, the snake sometimes changes direction at this particular moment, sometimes right after a few milliseconds. . Thus, the user can clearly feel that the interaction is not as sensitive as it should be, the snake's exact moment of rotation is quite unpredictable , even if you concentrate very much. There must be some other way to do this better than me.
Please check my code, I use the Handler with Runnable to move the snake. (Drawing on the canvas and every time it is set as the background of the view, that is, each time setting using setContentView to my activity.
code:
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 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); } }
ED i T:
I changed my code, now the view contains all the details , and I use the onDraw methods and the invalid ones , as Philip suggested.
The result is a little better, but I can still clearly feel that user interaction leads to a change in lag change.
Maybe something I should do with threads?
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(); } }