android canvas - can i have multiple draw methods? - android

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.

Related

Bad performance when drawing continuously in CustomView

Use case:
I need to draw hundred lines and a few pieces of text on my view. I need to give a scrolling effect, for which I capture the ACTION_MOVE event and redraw all the lines with the updated points. To get the desire result I tried with different approaches but none works as intended.
Approach 1
I made a custom class which extends View. All the drawing and calculation is done directly in my onDraw() method. Since there is so much of operation done in onDraw() method, the performance of the app is very poor. I even checked the performance using Profile GPU rendering and I can see the lines are very tall.
Approach 2
I created a Bitmap and after drawing all the lines onto my bitmap in another thread, I used postInvalidate() to draw the bitmap in onDraw() method:
mBufferedBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
mBufferedBitmap.eraseColor(Color.TRANSPARENT);
Canvas mBufferedCanvas = new Canvas(mBufferedBitmap);
drawLines(mBufferedCanvas)
postInvalidate();
Since I erase all the previous drawing on the bitmap and draw new lines with updated points, there is a flickering on the screen.
Approach 3
I tried extending my custom class to SurfaceView and performing all the operations on canvas object in another thread. But since SurfaceView uses CPU for drawing operations, the performance will be poor in low configuration mobiles.
Can anyone guide me how to achieve this task with better performance?
It is possible to use your approach 1 to achieve good performance.
An example that sounds close to your use case (drawing lines, a little text and having these update on gesture movement) is MPAndroidChart. This is an open source library that achieves high performance (see the following comparison if you want stats)
The classes to examine are the Renderer classes as these contain code draw in the onDraw(Canvas c) of the chart subtypes. You can see some of the tricks used to achieve high performance there:
Don't allocate in a render loop. Instead, allocate outside the loop and reuse/recycle variables. See LineChartRenderer line 199
Use buffering. For example, in MPAndroidChart, the points for the four corners of the bars in the bar chart are buffered and the buffer array is reused. See the BarBuffer class.
Use the native Canvas drawing functions (drawPath, drawLine etc.)
A complete list of tips for optimising rendering can be found in the Android Performance Slow Rendering Guide
Approach 2 is the best one. If you see flickering, it means that the bitmap is drawn to screen after you erase it and before you draw all the lines. If this is the case use another bitmap and do double buffering:
ScreenBitmap is what is drawn to screen
OffScreenBitmap is used for drawing in background.
Draw all your lines and text to OffScreenBitmap, and once finished, copy it to ScreenBitmap.
in onDraw, draw the ScreenBitmap.
Create these bitmaps once (typically in onSizeChanged), so that there is no alloocation in onDraw

incremental drawing

I'm trying to incrementally draw a waveform on a canvas in a separate thread, i.e. in each loop of the thread draw only the portion recently acquired by the hardware without redrawing the whole curve.
I am not sure if I can rely on the canvas' contents retained between SurfaceHolder.unlockCanvasAndPost and SurfaceHolder.lockCanvas.
The documentation seems to be a bit ambiguous on that and my experiments give surprising results: the "old" contents is different for the canvas returned in two subsequent loops of the drawing thread. It looks as if two canvases were used alternately (but I checked - it is the same canvas but with different contents).
The simplest way which I found to reproduce this behaviour:
Import the LunarLander sample applicaton.
In the LunarView.run method insert sleep(500) instruction in the loop.
In the LunarView.doDraw method comment out the line:
canvas.drawBitmap(mBackgroundImage, 0, 0, null);, which erases the old contents.
Run the game and let the ship crash. You will see the intermediate positions of the ship but they will blink: odd and even positions will be shown alternately.
So, my question is: can we draw pictures adding changes only to the contents which has been drawn before and if so - is there any documented way of dealing with the strange behaviour described above?
The lunar landing does not cache.
It draws the background image each time (refreshing) and then draws the spaceship.
private void doDraw(Canvas canvas) {
// Draw the background image. Operations on the Canvas accumulate
// so this is like clearing the screen.
canvas.drawBitmap(mBackgroundImage, 0, 0, null);
Check your If clauses closesly. Maybe one is alternating true and false. This would cause things to draw and then not draw, in rapid succession.
Use Log if need be to output your booleans, to see if one is changing.
Failing this, this the following gaming loop instead.
http://blorb.tumblr.com/post/236799414/simple-java-android-game-loop

Draw a shape that tracks my finger

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...

Do I need to redraw all objects on a canvas every frame?

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.

How to animate in Android Canvas?

I want to draw something at about 30 frames per seconds on Android Canvas or other convenient object for this purpose. In my application different graphic objects are drawn and if any of the graphic object is touched, the graphic object changes its shape. I looked at the
onDraw(Canvas canvas) callback of View subclass but calling invalidate() does not help here: first I cannot control the frame rate and second if the objects are moving too fast, the motion appears jerky.
I personally dislike Android's built-in Animation classes, so I tend to do all animations with Canvas by hand. I have found the most luck with creating a list of the images you want to use in your animation and then an int variable to store the current "frame" you are on. To advance the frame, I create a thread that sleeps for, say, 30 ms and then update the frame variable accordingly. Then in whatever update handler you are using, you can just create a switch statement or something of the like, and draw the respective frame.
It may seem like a lot of work, but it really isn't. Shove it all into a class and you will love yourself for many animations to come.

Categories

Resources