I've created an activity that calls a DrawView extends View, I draw on a Canvas something like a binary tree. The canvas size is greater than the screen size, and the onDraw function draws all the canvas, not only the visible part of it. I've added an horizontal and a vertical scrollView, and obviously onDraw is called again and again to refresh the view.
I was wondering if I can draw the canvas on an image (a bitmap or something like that) using that image to show the tree, without the need to recall the onDraw function.
If I can't do this, what can I do in order to get a faster view?
If you're using API 11 or above you can try using the hardware acceleration attribute in the application tag in your manifest.
<application ... android:hardwareAccelerated="true" ...>
Other than that you could consider using another rendering than View, have a look at SurfaceView too.
Generally, I think you shouldn't override View unless you're building a UI component (like a Button), don't quote me on this though.
Technically the canvas draws on a Bitmap. So it's actually the Bitmap that's bigger than the screen. Why are you making the Bitmap bigger than the screen? Bigger Bitmaps = more memory usage - which could slow things down a bit. You only really draw within the clipped bounds of the screen - you may think you're drawing off screen but you're not. Drawing on another Bitmap won't help you - you're already doing that. Without looking at your code you could try:
only draw what changes in the binary tree by calling invalidate(rect) which only repaints what has changed
if you binary tree is meant to be static, just create a png/jpg of it and display that image versus drawing it all yourself
make sure you're not calling invalidate more than you need to
Related
I have an app that connects to an external printer device and I need to take some user data and dynamically make an image and print it. The printer API requires a bitmap as input for printing. Note that I never want to draw the programmatically created image to the screen; the generation and printing of the image have no visible UI, it happens in the background.
So far I've considered doing it one of the following ways:
1) Make a canvas, never call the draw functions, translate it to a bitmap then print (a bit of a departure from the intended way to use the Canvas API)
2) Make a hidden ImageView xml layout, never make it visible, translate it to a bitmap then print (this can get tricky because I'd probably have to include it in the Activity's layout somewhere to dynamically edit it, but that consumes resources and feels inefficient)
What are your opinions on the best approach? Either of the two ways I've considered feel a bit hacky and leave me lusting for a dedicated API for custom image generation.
The most platform-conformant solution is to take the Canvas approach.
First you should create a Bitmap of the needed dimensions and pass it to the Canvas(Bitmap) constructor. Then you draw whatever you need onto your canvas and retain the reference to the bitmap to use wherever you need throughout your app (in my case it's being sent to the printer).
From the Android Docs:
For software-rendered snapshots of a small part of the View hierarchy or individual Views it is recommended to create a Canvas from either a Bitmap or Picture and call draw(android.graphics.Canvas) on the View.
Example code:
bitmap = Bitmap.createBitmap(LABEL_SIDE_LENGTH, LABEL_SIDE_LENGTH, Bitmap.Config.ARGB_8888)
Next [in override fun draw(canvas: Canvas)] set the Canvas' Bitmap like so;
canvas.setBitmap(bitmap).
You retain the reference to bitmap and it can be used wherever you need
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 fullscreen custom view in my activity where I have to draw paths that are formed by a line each one. Since my view supports scaling, depending on how zoomed in the view is, the paths get to big and therefore the view becomes slow, even crashing sometimes. What I want to know is if there's some built-in function where I can draw only the part of the path that is actually inside the canvas.
I've tried using Path.op with a rect the size of the screen, but didn't get any results; it either returns an empty path or a path that contours the screen. Same thing with Canvas.clipPath(). Passing a rect on through invalidate() also doesn't work for me since the said path IS insterecting the drawing area.
It's also worth metioning that I'm using paths because I need to use a PathDashPath effect. So, if there's a way os doing that with drawLine, it'd also help.
I need update a small portion of a custom view in order to display a small animation. The rest portion of the view has only static image. The most straightforward would be to obtain the canvas of the view and update only that particular portion directly. But I can't really find anyway to get the view's canvas object outside of the view::onDraw method.
The only alternative I know is this: call view::invalidate() with a specified rectangle to minimize the drawing flicker. I have the code to update the entire view within onDraw. So the best thing to do is to detect the clipping rect and only run the code to update the specified area, in order to minimize CPU usage as well?
I guess I will try to answer this question myself to the best my knowledge so far.
There is no direct access to the canvas outside of the onDraw method.
Although we can detect the clipping rect with the function Canvas.getClipBounds(), the getClipBounds function always return the entire view area if GPU is enabled. When GPU is not used, getClipBounds() returns the actual dirty area. Since there is a GPU in most phones, it makes the function getClipBounds pretty much useless.
I want to be able to dynamically place image(s) over another image in my app.
Consider the first image as background and the other images to be on top level, I will also need to move those top level images (change their x and y on the screen) by code too.
Imagine, for example, a sea in which the user places fish and sea animals, then those sea animals start to move here and there on the screen: it will be like that.
How can I do this? If you don't know but remember any simple program or demo that does that, it will be also very welcome!
Thank you!
There is, of course, more than one way to do this, but I would say that the best way to do it would be to create a custom View (class that derives from View) and have this handle your bitmap drawing and all of your touch events.
There's a lot of code to write for loading the Bitmaps and keeping track of all of their positions (and then drawing them to the canvas in onDraw), but if you start really small by just allowing one image to be drawn and dragged around the screen, you can build on that and keep your code organized.
You would need to override onDraw(Canvas) and onTouchEvent(MotionEvent) in your custom View. You'll load your bitmaps with BitmapFactory (decodeResource method if you're including your images as resources in your project) and you'll need to remember to call recycle on your bitmaps when you're no longer using them.
In onDraw, you draw your bitmaps to the canvas at a specific location using Canvas.drawBitmap. There are two overloads of this method you can choose from, one that takes the top and left coordinates of the bitmap as floats (and therefore performs no scaling or stretching) and one that takes a destination and source rectangle to perform scaling, stretching and placement.
I always use the latter as it gives me finer tuned control. If you choose this route, you'll want to keep two Rect instances and a Bitmap instance for each image being drawn, update them in the touch events and draw them to the canvas in the draw event.
When something changes inside your view (as in the case of a touch event), call invalidate() method and the framework will know to redraw everything which triggers your onDraw method.