I am setting up a game loop. The loop is set to update at 50 FPS. Currently the only action the app does is draw screen coordinates on response to touch events. Originally I set up the draw function to only draw new touch events. This caused the text to blink. Multiple touch events blink at different times, which leads me to think that Canvas uses multiple pages behind the scenes. In an effort to get around this I made a list of every touch event that happened and on every draw call I redraw the entire list.
Please correct me if I am wrong, the lockCanvas()/unlockCanvasAndPost() calls are in essence a backbuffer swap. Reading the documentation on locking and unlocking it sounds like it is necessary to redraw the entire scene between calls unless you use lockCanvas(Rect dirty). In this case the dirty rect area needs to be redraw while the outside area is preserved.
The content of the Surface is never preserved between unlockCanvas()
and lockCanvas(), for this reason, every pixel within the Surface area
must be written. The only exception to this rule is when a dirty
rectangle is specified, in which case, non-dirty pixels will be
preserved.
I know it sounds like I've answered my own question, but according to the documentation the Surface is never preserved between calls. However this does not explain the behavior of my first implementation, which was I would tap the screen and the text would start blinking. Since I only drew the text one time, this would mean that the blinking is from swapping to the "backbuffer" which didn't get the drawText() call, and the original Surface which did not get destroyed but perhaps it is to be considered unreliable.
So, the question: Do I need to redraw all objects on each draw call? And if so, do I need to "clear" the canvas, or at the least redraw the background image also?
yes, unless you're doing a dirty redraw where you define the region that you are redrawing, every point on your canvas gets destroyed.
Related
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)?
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).
When an image is being dragged using touch over another custom view, I am setting the background color of the custom view from green to red. Off course I am listening to the onTouchEvents and I am comparing the coordinates of the dragged view to the custom view to see if there is a collision. Once collision is determined, I set the color and call invalidate on the view.
So invalidate is being called on every touch (when there is a collision). Is this frown upon? I feel the device heating up when this happens so not sure if this is normal when it comes to games
Thanks
View#nvalidate() indicates that a View needs to be redrawn and signals a draw pass. You can call this 50 times in a row, and it will only signal one draw pass. The draw pass will happen as soon as the system regains control on the main thread (or perhaps a little later depending on any other background operations that take control).
If you are animating something as you are with this custom View, then you are calling many draw passes to begin with so you may not even need to call View#invalidate() and simply just wait for the screen to redraw.
In fact, you can look at the source code and see that invalidate() is being called at the end of setBackground() to begin with. It's actually pretty rare to need to call it when you're changing properties of a View that the View itself is in control of. It's only needed if there are drawing operations that you're doing in addition to what the View itself is doing.
I know how to draw paths on a canvas and understand how to undo/redo. But now I want to draw shapes (lines, circles, rectangles) that dynamically resize depending on how I drag them - just like we have in MS Paint.
If I use "drawLine", the line is there permanently with no way of erasing it and redrawing it to my new finger location. Same with circle as I want the circle to constantly change width as I drag my finger. So the old one has to erased (keeping all the other paths on the bitmap intact) and the new one drawn in its place.
Been searching a lot for this, but haven't come across how t do it. Maybe I'm using the wrong keywords, but I don't know. Any pointers?
Each time you move the finger, call the underlying view's invalidate() function, it will trigger erasing the entire background
public void invalidate ()
Since: API Level 1
Invalidate the whole view. If the view is visible, onDraw(android.graphics.Canvas) will be called at some point in the future. This must be called from a UI thread. To call from a non-UI thread, call postInvalidate().
Then redraw your shape based on your finger's new position.
Managed to do it. I misunderstood the way the offscreen drawing thing worked. The idea is to write to the bitmap only after you have the shape you want - ie on "touch up". Till then, draw the shape using the "canvas" object in on Draw...
Is that possible? Because I need to draw a photo-background with moving objects on top of it, in atleast 35 fps. It must take alot of resources to redraw that whole background every frame, even for a short time? (live wallpaper)
I tried to redraw the background only at each moving object's Rect, but that only makes those parts of the screen flickering.
Ok it seems there is no big gain anyways in doing these opmimizations.
The flicker is ofcourse because of double-buffering, there are Two buffers to erase, therefore the flickering.
I have made my own canvas class which extends an imageView. My onDraw() method draws out the users gps position and I keep calling this onDraw method every time the user moves. My problem is I also want to draw out a gps trail which only needs to be drawn once (doesnt need to be updated when a user moves). I am wondering is it possible to have more than 1 onDraw method or is there any way of separating 1) the user location and 2) the gps trail??
My reason is I do not want to waste memory by redrawing the gps route everytime the users gps position changes. It is a waste.
Have you seen performance take a hit? If not, don't worry about it. I would think that this would be wasting CPU cycles if anything... not memory. So if the app seems fast enough already, don't worry about optimizing it.
If your app is a bit laggy, and you've found that the trail is the bottleneck... I would suggest caching it into a bitmap. This way, you will still have to draw the trail, but you will not have to calculate the coordinates of the trail on each frame.
I have had to solve a somewhat similar problem recently and I'll explain briefly what I did in case it's of any help.
What you can do is use multiple overlapping Views, where one may contain the background graphics that you don't want to redraw often, and a foreground View that contains the graphics that are frequently updated. Then, to gain performance, you can design the background View's onDraw() so that it is backed by a Bitmap that you then retain as a class variable. In the very first onDraw() of your background graphics, you do the relatively slow drawing to Canvas. In subsequent calls to onDraw(), you simply draw that Bitmap to Canvas.
I've just done this myself. Basically what my application does is display a number of graphical gauges. Those gauges have lots of graphics that are drawn just once (gauge face, number legends), and the pointer graphic that needs to be redrawn over and over as the model data changes. First of all, I split the background graphics and moving foreground graphics into separate overlapping Views. Now, invalidating and redrawing the foreground pointer graphic of course causes anything it overlaps to be invalidated too, so the onDraw() method for the background graphics View is being called each time the pointer View is redrawn. The background View only needs to draw the background graphics once, but retains the Bitmap that backs the canvas, and in subsequent onDraw() calls it draws this bitmap back to Canvas (which is a lot faster than initially creating the graphics using Path() objects).
Bitmap bm;
....
protected void onDraw(Canvas canvas){
if(null==bm){
bm=Bitmap.createBitmap(getMeasuredWidth(),getMeasuredHeight(),Bitmap.Config.ARGB_8888);
// do your slow vector graphics drawing to Canvas here
}
Paint drawPaint = new Paint();
drawPaint.setAntiAlias(false);
drawPaint.setFilterBitmap(false);
drawPaint.setDither(false);
canvas.drawBitmap(bm, 0, 0, drawPaint);
}
Well, there can't be more than 1 onDraw method, assuming that I understood your question correctly. You will need to think about alternate approaches about how to handle this.
#DeeV suggested, that can be a solution for you.