Draw object's trails on a SurfaceView

375 views Asked by At

I have to simulate the motion of some objects, so I have created a SurfaceView on which I draw them with a dedicated Thread. Every loop I call canvas.drawColor() to clean up all previous object's positions and to draw the new states. Everything works fine and the frame rate is decent.

The problem is: what if I want to draw the trails of the objects' trajectories? In that case I have to memorize the positions for every object and, at every loop, draw all past positions that are hundreds of points. This task keep the frame rate lower and it seems to me absurd that the only way is to redraw every time the same points! There is a way to keep the points painted on the canvas and not to cancel them with the canvas.drawColor() at every loop (that is necessary for others tasks)?

1

There are 1 answers

0
fadden On BEST ANSWER

Sort of.

The SurfaceView's Surface uses multiple buffers. If it's double-buffered, and you don't clear the screen every frame, then you'll have the rendering from all the odd-numbered frames in one buffer, and all the even-numbered frames in the other. Every time you draw a new frame, it'll flip to the other buffer, and half of your positions will disappear (looks like everything is vibrating).

You could, on each frame, draw each object at its current position and its previous position. That way both frames would get every object position.

The practical problem with this idea is that you don't know how many buffers the Surface is using. If it's triple-buffered (which is very possible) then you would need to draw the current, previous, and previous-previous positions to ensure that each buffer had every position. Higher numbers of buffers are theoretically possible but unlikely.

Having said all this, you don't want to pursue this approach for a simple reason: when you lock the canvas, you are agreeing to modify every pixel in the dirty area. If you don't, the results are unpredictable, and your app could break weirdly in a future version of the operating system.

The best way to do what you want is to draw onto an off-screen Bitmap and then blit the entire thing onto the Surface. It's a huge waste at first, since you're copying a screen-sized bitmap for just a couple of objects, but very shortly the reduced draw calls will start to win.

Create a Bitmap that's the same size as the Surface, then create a Canvas using the constructor that takes a Bitmap. Do all your drawing through this Canvas. When you want to update the screen, use a drawBitmap() method on the SurfaceView's Canvas.

I recommend against using software scaling due to the performance cost -- make sure you're doing a 1:1 copy. You can use the setFixedSize() call on the SurfaceView surface to make it a specific size if that's helpful -- for devices with larger pixel densities it can improve your frame rates and reduce battery usage (blog post here).