Drawing on canvas with user interaction is a bit backward

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(); } } 
+5
source share
3 answers

The magic number is 16 milliseconds for the android to redraw the view without freudrops.

Check out this video from Android developers who explain it. https://www.youtube.com/watch?v=CaMTIgxCSqU&index=25&list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE

Especially check out this video https://youtu.be/vkTn3Ule4Ps?t=54

It explains how to use a clipping canvas so as not to draw the entire surface in each cycle, the nut draws only what it needs to draw.

+1
source

Do not use a handler to draw with Canvas. Instead, you should create a custom view and use the onDraw (Canvas canvas) method. In this method, you can draw a Canvas object, as you already did. By calling the invalidate () method, you call a new call to onDraw (). In the onTouch () or onClick () function, you also launch a new onDraw call, calling invalidate ()

 class SnakView extends View { @Override protected void onDraw(Canvas canvas) { // draw on canvas invalidate(); } @Override public void onClick(View v) { // handle the event invalidate(); } } 
+1
source

You can try and add android:hardwareAccelerated="true" to your manifest, to the or folder.

This will work for devices with 3.0+.

Also your target api level should be 11.

Then it will work more smoothly.

0
source

All Articles