View onDraw(Canvas c) versus draw(Canvas c) in android? - android

I am new to android development, I am exploring about View. I come across to known two methods onDraw(Canvas c) and draw(Canvas c).
Could please explain me the difference and usage of these two methods? Which method will give better performance(FPS) when updating canvas with images?

There is difference between them
The onDraw(Canvas c) is a override method and automatically called when the view is being rendered. Here you can do your additional drawing like make circles, lines or whatever you want.
The draw(Canvas c) is used to manually render this view (and all of its children) to the given canvas. The view must have already done a full layout before this function is called. When implementing a view, implement onDraw(android.graphics.Canvas) instead of overriding this method. If you do need to override this method, call the superclass version.
Or in simple words draw(Canvas c) is simply a function of a view that you can call after the view is rendered for the first time. This function can be used for custom drawing on any view. You need to provide the canvas on which this view will rendered and also you have to do all the drawing on the canvas before calling this function.

Just if someone was still looking for answer like me and didn't find it.
The draw() method is called by the framework when the view need to be re-drawn and the draw() method then calls the onDraw() to draw the view's content.
void draw(Canvas canvas)
{
..... do default stuff (background, layers)
onDraw(canvas)
..... do other stuff ( scroll bars, fading edges, children)
}

There is a misconception about this as a result of awkward API documentation.
The short answer is that draw(Canvas) is an inbound call on a View to do some important stuff and somewhere in the middle of the draw(Canvas) implementation it will also trigger an onDraw(Canvas) callback.
Don't override draw(Canvas) when implementing a custom View intended to be used inside a layout.
If your custom view is intended to be used as a full screen game, then overriding draw() will save you some unneeded calls on every cycle.
Longer Answer
The framework uses draw(Canvas) in its draw cycle. This is what I could find in the View code:
Step 1: draw the background, if needed
Step 2: save the canvas' layers
Step 3: draw the content --- onDraw() comes here
Step 4: draw the children
Step 5: draw the fade effect and restore layers
Step 6: draw the scrollbars
Useful tip
You can render any view into an offscreen Bitmap you create, and later use this bitmap anywhere:
Canvas c = new Canvas();
c.setBitmap(myOffscreenBitmap);
myView.draw(c);
for simple cases (when it's not a ViewGroup and no scrollbars are needed), the simple onDraw(Canvas) could also do the trick.

Related

How to draw over small portions of custom view?

I'm using a custom View to draw rectangles(which will have text inside them at a certain point) and I want to highlight each rectangle when selected, which will happen every few seconds when user selects a rectangle. Should I implement highlighted rectangle in onDraw or is there a way just to redraw each rectangle without redrawing the whole View? I was thinking of using "invalidate(rect)" but it's been deprecated.
I'm trying to be considerate of the cost of invalidating the whole View compared to just redrawing a rectangle.
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for(CellCoordinates cellCoordinate : mCoordinateCells) {
canvas.drawText(" ", cellCoordinate.getRect().exactCenterX(), cellCoordinate.getRect().exactCenterY(), cellPaint);
}
}
Using invalidate() and onDraw() is fine. Dirty rect is not really have effect on API21+
... In API 21 the given rectangle is ignored entirely in favor of an internally-calculated area instead. ...
Dirty rect is deprecated because of different drawing model in hardware accelerated views. Checkout this link for more information
Also, it seems your rectangles can be implemented as custom Drawables with states (selected and normal). It will not give you extra performance, but might help to divide and structure code for drawing. This might help

What is difference between draw() and onDraw() method in View class? [duplicate]

This question already has answers here:
View onDraw(Canvas c) versus draw(Canvas c) in android?
(3 answers)
Closed 7 years ago.
What is difference between draw() and onDraw() method in View class?
SurfaceView.draw() basically calls View.draw(); If you want to implement your drawing, you should do it in View.onDraw() which is for you to implement which even says in the source code comments.
This method is called by ViewGroup.drawChild() to have each child view draw itself. This draw() method is an implementation detail and is not intended to be overridden or to be called from anywhere else other than ViewGroup.drawChild().
As for difference between them:
draw():
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
onDraw() is empty. Its for you to implement.
The main difference between the methods is:
The onDraw(Canvas c) method is a override method and is automatically called when a view is being rendered. You can add additional functionalities here, like making circles or drawing lines. I.e. whatever you want.
The draw(Canvas c) is used in order to manually render this view and all of it's children, to the given canvas 'c'. The view must have already done a full layout before this function is to becalled. When implementing a view, implement onDraw(android.graphics.Canvas) instead of overriding this method.
This simply means that, draw(Canvas c) is a function that is Not automatically called when a given view is being rendered. The user is needed to provide the Canvas 'c', on which the view will be rendered. The user also have to do all the drawing on the canvas before calling this function.

android.graphics.View invalidate(int, int, int, int) part of a canvas, but onRedraw() the entire canvas?

Android View has three versions of invalidate(): one that invalidates the whole view, and two that invalidate only a portion of it. But it only has one onDraw(), which draws the entire canvas. There must be some use that the system makes of the hint that I only want to invalidate part of the view, but I'm unclear on what it is.
I have a view that does custom drawing in onDraw(). Do I have a way to find out which parts of the canvas are invalid, so I only draw those?
When Android gets ready to render changes to the screen, it does so by creating a union of all of the individual rectangle areas of the screen that need to be redrawn (all the regions that have been invalidated.)
When your view's onDraw(Canvas canvas) method is called, you can check to see if the Canvas has a clip bounds.
If there is a non-empty clip bounds, you can use this information to determine what you will and won't need to draw thus saving time.
If the clip bounds is empty, you should assume Android wants you to draw the entire area of your view.
Something like this:
private Rect clipBounds = new Rect();
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
boolean isClipped = canvas.getClipBounds(clipBounds);
// If isClipped == false, assume you have to draw everything
// If isClipped == true, check to see if the thing you are going to draw is within clipBounds, else don't draw it
}
There must be some use that the system makes of the hint that I only want to
invalidate part of the view, but I'm unclear on what it is.
Yes, it is. The method invalidate (int l, int t, int r, int b) has four parameters which are used by the View's parent View to calculate the mLocalDirtyRect which is a filed of the View class. And the mLocalDirtyRect is used by the getHardwareLayer() method in the View class, here is its description:
/**
* <p>Returns a hardware layer that can be used to draw this view again
* without executing its draw method.</p>
*
* #return A HardwareLayer ready to render, or null if an error occurred.
*/
HardwareLayer getHardwareLayer() {
Means that Android can refresh part of your view without call the View's onDraw() method. So you don't need to try drawing part of the View yourself, because Android will do it for you when you tell it the dirty part of your View.
Finally, I think you can refer to the source code of View and ViewGroup for more details, here is the link that you can read them online:
https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/view/View.java
https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/view/ViewGroup.java

Am I invalidating the entire screen on every call?

I am new to Android Development and reading the book Hello Android. It uses a Sudoku example, and the code that I am referring to is here.
In this , onTouchScreen, it calls select method, that calls invalidate twice. The question is that, on invalidating is the onDraw method called right after that? So will in this case, inside my select method, it will do
invalidate
call onDraw
Do some stuff
invalidate
call onDraw
Is this how it will happen, also, will the entire screen be regenerated? All the numbers and hints etc., because from the book the author says
In an earlier version of this example, I invalidated the entire
screen whenever the cursor was moved. Thus, on every key
press, the whole puzzle had to be redrawn. This caused it to lag
noticeably. Switching the code to invalidate only the smallest
rectangles that changed made it run much faster.
What exactly is he trying to say here?
Added Info
I added some logs in the onDraw method, some at the starting, some in the for loop. Whenever I touched a new rectangle, all the logs were called. Doesnt that mean that the entire screen is geting repopulated, since all the code in onDraw is reexecuted?
Kraken
Q: But what about the logs, surely if my loops are getting executed it means that all the canvas.draw will be getting executed too?
A: Yes, the whole drawing will be executed in your sample code. You have to optimize the rendering process by yourself, in onDraw method.
Q: How does the system know, what piece of code will "only" redraw the dirty area?
A: Canvas::getClipBounds will give you a dirty rect, which you should draw something on.
Inside your for loop in onDraw, compare the dirty rect with the rect which you want to draw. Then do continue if they do not intersect.
But remember, if you have several area set to dirty, the returned rect will be a union of all dirty areas.
Please see the following two questions below:
Getting the dirty region inside draw()
Android: invalidate(dirty)
Hope this will help you.
==========================
The author is right. But this still can be optimized.
Calling invalidate(Rect) will automatically set a clip area for the canvas. (That's why canvas.getClipBounds() can return that area).
Then, during onDraw(), anything drawing out of the clip area, will be ignored. They do not appear on the screen, so it REALLY reduce the time of drawing.
But ignoring them still costs overhead. Therefore, for graphical intensive app, onDraw() could be better optimized if you exclude them in advance.
You can find a great example for optimizing onDraw() in android's KeyboardView, which provide the view of your android's input method.
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/1.5_r4/android/inputmethodservice/KeyboardView.java
This is directly from View documentation:
Drawing is handled by walking the tree and rendering each view that intersects the invalid region. Because the tree is traversed in-order, this means that parents will draw before (i.e., behind) their children, with siblings drawn in the order they appear in the tree. If you set a background drawable for a View, then the View will draw it for you before calling back to its onDraw() method.
Note that the framework will not draw views that are not in the invalid region.`
From what I understand, once your view is drawn for the first time, a tree if formed with parent and child objects along with their positions on screen. When you pass a designated area to invalidate, this tree is checked for effected nodes in that area and only those nodes will be called for draw.
Now what I also don't understand is that in this example, the only View is the PuzzleView. I'm not sure how a drawing a single view can be optimized. Check if it is discussed further in the text.
If it is not, then my theory would be that the canvas objects(rectangles) are also part of the above said tree and only those parts, i.e. the rectangles in the specified area are drawn.
More importantly, do you see any improvement after using area invalidate vs full invalidate?
Even if you call invalidate multiple times the onDraw method will only be called once. Basically the onDraw gets called inside the RunLoop method when a view has been invalidated. That means that if you invalidate the view multiple times before giving back the control to the runloop the view will be redrawn only once.
Notice that if you invalidate two different rects of the view the system will try to make an union of those rects before redrawing your view.
In the code, the invalidate that you are talking about is this:
invalidate(selRect);
?
If it is he only calls the onDraw of this selected rectangle selRect.
Only the invalidate(); redraws the hole screen.
Hope it helps.
On this example, you should notice that invalidate() calls have a Rect as parameter. This mean that only this zone of the view is getting dirty and is going to be redrawn by the system.
Calling invalidate() will not trigger the onDraw() method right after. The system only decide whenever he wants to redraw the view.
From Android documentation :
If the view is visible, onDraw(android.graphics.Canvas) will be called
at some point in the future.
Knowing that, inside the select method, this will probably happen :
1. Invalidate a small portion of the View
2. Do some stuff
3. Invalidate another small portion of the View
4. Theses 2 portions of the View are getting redrawn
Hope that helped.
As #jjxtra mentioned above
Invalidate with rect does not alter canvas clip bounds with hardware acceleration turned on. The entire view is always redrawn regardless of the rect passed to Invalidate.
In API 21 the given rectangle is ignored entirely in favor of an internally-calculated area instead. public void invalidate(int l, int t, int r, int b); and public void invalidate(Rect dirty); already marked as deprecated!
I solved the problem by specifing the subset of the bitmap to draw.
class MyView extends View {
private Bitmap mBitmap;
private Rect mBound = new Rect(0, 0, 300, 300); // 300x300 by default, invoke updateBound if in needed
...
private void updateBound(PointF pointF) {
if (mBound.left > (int)pointF.x) {
mBound.left = (int)pointF.x;
}
if (mBound.bottom < (int)pointF.y) {
mBound.bottom = (int)pointF.y;
}
if (mBound.top > (int)pointF.y) {
mBound.top = (int)pointF.y;
}
if (mBound.right < (int)pointF.x) {
mBound.right = (int)pointF.x;
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
...
invalidate();
return true;
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(mBitmap, mBound, mBound, null);
}
}
By default I only draw in the area of the (0, 0, 300, 300). But you can update the bound if in needed, just invoke updateBound.

onDraw() on View draws behind the layout

Ok, here's the deal. I want to move items in my extended gallery class to change the order of images. The way i'm doing it now:
on long press remove the current selected item,
use onDraw() to draw the same image so i can move it around using onTouchEvent()
on release add the item again
This works fine but the problem is that when using the onDraw() method it will draw the image behind the gallery items. Is there a way to change the priority of what is drawn?
Well i found this out after going into a totally different direction =/
Here's the solution for people that have the same problem:
In constructor (or anywhere else you initialize the component) set setWillNotDraw(false) and override dispatchDraw(). dispatchDraw() draws the ViewGroup children so you can decide yourself if you want to draw behind or a top of the other views.
Example taken from Custom drawing on top of Gallery view (and it's child views)
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
// do your drawing stuff here
canvas.drawPath(mPath,mPaint);
}

Categories

Resources