Is there way to prevent drawing of parts of objects, like bitmaps or paths, which go beyond the borders of Canvas in SurfaceView?
When I gradually scale an object beyond the Canvas' size, especially with blurred paint, it all slows down to a stand still and I have to wait few seconds to get the control back -- it is not responsive. I scale an object by moving my finger over the screen; if I do it too fast and scale it up, then it really slows down drawing.
I did not have the same problem when using just ordinary View canvas, so don't know what is slowing it down. It's as though SurfaceView responds too fast and then gets congested.
So one idea to improve this, is to prevent drawing outside canvas, but not sure if SurfaceView has such clipping options.
One solution perhaps is by using one of the Canvas .drawBitmap methods that accepts a source Rect / RectF argument, so that only the visible portion of the Bitmap is rendered. I've just done this on my own project to speed up SurfaceView performance where I have several Bitmaps that are sometimes only partially in view.
I'm not sure if canvas would actually draw things beyond the dimensions, that wouldn't be a smart thing to do. Still, you can use clipRect() to define a clip region.
Are you applying filters like blur per frame ? that might be pretty expensive.
Also, are you triggering draw calls on touch event ? Touch events are generated very quickly and will flood the rendering thread. You might want to pick up events at a slower rate: https://groups.google.com/forum/?fromgroups=#!topic/android-developers/Oe6k1_bm38o
When you put the SurfaceView on the layout of your activity, you can use the padding attributes like:
android:paddingLeft="20dp"
android:paddingTop="20dp"
android:paddingRight="20dp"
android:paddingBottom="20dp"
It works like this:
To draw your canvas you can draw dinamically the borders with:
canvas.drawLine(1,1,canvas.getWidth()-1,1); //Top border
canvas.drawLine(1,1,1,canvas.getHeight()-1); //Left border
canvas.drawLine(1,canvas.getHeight()-1,canvas.getWidth()-1,canvas.getHeight()-1); //Bottom border
canvas.drawLine(canvas.getWidth()-1,1,canvas.getWidth()-1,canvas.getHeight()-1); //Right border
Related
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
I have a surfaceview in my activity. Initially some circles are drawn using
Canvas c = SurfaceView.getHolder().lockCanvas();
and
SurfaceView.getHolder().unlockCanvasAndPost(c);
Later on I want to draw on a very small part of the surfaceview. I do not want to redraw the whole surfaceview. Is it possible to update just the required part?
Use lockCanvas(Rect dirty) instead. Pass in the area you want to redraw.
Note that dirty is an in-out parameter. The Rect may be expanded to cover a larger area, and you need to set every pixel it covers. So if (for example) SurfaceView no longer has access to the previous pixels, it'll expand Rect to cover the entire surface.
Because SurfaceView is double- or triple-buffered, the implementation will copy chunks of pixels from the previous buffer. Since the Canvas you get from a SurfaceView is never hardware-accelerated, specifying a dirty rect is often a performance win.
I want to draw a big image on canvas, and after that I want to move that canvas so it shows me different part of this image. Without repainting it (or eventually repainting only the needed part). How is it done in android?
Check this method. All you need to do is to specify horizontal and vertical distance to move your canvas. You can also scale, rotate or even apply custom matrix on canvas. This tutorial also might be helpful.
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.
In my android application I am stuck in a problem and nothing seems to work for me.
I have an ImageView on the top of another ImageView inside a relative layout.
Now I need to resize the imageview on top when user touches one of its corners and drags.
Just like a cropping frame we generally see. When we drag any one corner, then the diagonally opposite corner must remain fixed and the resizing must be done across the corner which is being dragged.
What I am doing is setting OnTouchListener and getting new/dragged coordinates on Action.MOVE then I tried to resize using Bitmap's createScaledBitmap. This does resize the image view but not across the corner which is being dragged. I am totally confused .
How I can use the coordinates to draw an Image View just like we do it while drawing a rectangle using Canvas.
Please help.
I wouldn't do this in an ImageView. I would subclass View and override onTouchEvent and onDraw to handle the input and draw all the various components. You have to break this down into it's components and manage a number of objects in this view.
You have a Rect that represents the size of the crop area. This probably defaults to the size of the control. In onTouchEvent, you need to test for an area around each corner and then keep track of which corner is being dragged to resize your Rect appropriately.
You don't have to call createScaledBitmap each time you draw it, and you probably shouldn't because you are flirting with an OutOfMemoryException at that point (clean up your Bitmaps too slowly and you'll find out the hard way what this is). Just decode the Bitmap when the control gets created and draw it to the canvas using a destination Rect.
Lots of code to write, but it sounds like a fun project. There's no easy way to drop in a control like this (if I'm understanding you correctly). You have to manage the touches, drags, and the destination rectangle inside the custom View.