Am I invalidating the entire screen on every call? - android

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.

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

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

How to translate the canvas and still get the touch events on the correct places

I'm trying to create a custom AbsListView (overriding the same stuff on ListView, GridView and HeaderGridView) that will relocate all its drawing and touching events based on an external factor (other stuff that moves on the layout).
Paddings are not an option here, because then the only way to try to control the AbsListView position is using the super crappy method smoothScrollBy(int distance, int duration) and that is giving me a whole level of different issues that I'm trying to overcome by just all together drawing in a different area of the screen.
At the moment I found out that draw on a different area of the screen is as easy as:
#Override
protected void onDraw(Canvas canvas) {
canvas.translate(0, 400);
super.onDraw(canvas);
}
but the touch events don't get translated, and all the touches are 400 pixels up. So I've tried to translate the touch as well with:
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
ev.offsetLocation(0, -400);
return super.dispatchTouchEvent(ev);
}
it does relatively work, to the point that 1) on item click get's called and 2) the view changes color based on the theme android:state_pressed="true", but the Up or Cancel touch evens never get called and the view does not refresh the drawing back to the default color.
I'll still further analyse, but I'm not sure if complex custom adapter with buttons and gesture detector, if that would work or not.
So the question:
How can I translate the drawing and still properly get touch events?

View onDraw(Canvas c) versus draw(Canvas c) in 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.

Android: How to get a custom view to redraw partially?

I have a custom view that fills my entire screen. (A piano keyboard)
When a user touches the key, it causes invalidate() to be called and the whole keyboard gets redrawn to show the new state with a touched key.
Currently the view is very simple, but I plan to add a bit more nice graphics. Since the whole keyboard is dynamically rendered this would make redrawing the entire keyboard more expensive.
So I thought, let's look into partial redrawing. Now I call invalidate(Rect dirty) with the correct dirty region. I set my onDraw(Canvas canvas) method to only draw the keys in the dirty region if I do indeed want a partial redraw. This results in those keys being drawn, but the rest of the keyboard is totally black/not drawn at all.
Am I wrong in expecting that calling invalidate(Rect dirty) would "cache" the current canvas, and only "allows" drawing in the dirty region?
Is there any way I can achieve what I want? (A way to "cache" the canvas and only redraw the dirty area?"
Current nice workaround is to manually cache the full canvas to a bitmap:
private void onDraw(Canvas canvas)
{
if (!initialDrawingIsPerformed)
{
this.cachedBitmap = Bitmap.createBitmap(getWidth(), getHeight(),
Config.ARGB_8888); //Change to lower bitmap config if possible.
Canvas cacheCanvas = new Canvas(this.cachedBitmap);
doInitialDrawing(cacheCanvas);
canvas.drawBitmap(this.cachedBitmap, 0, 0, new Paint());
initialDrawingIsPerformed = true;
}
else
{
canvas.drawBitmap(this.cachedBitmap, 0, 0, new Paint());
doPartialRedraws(canvas);
}
}
Ofcourse, you need to store the info about what to redraw yourself and preferably not use a new Paint everytime, but that are details.
Also note: Bitmaps are quite heavy on the memory usage of your app. I had crashes when I cached a View that was used with a scroller and that was like 5 times the height of the device, since it used > 10MB memory!
To complement Peterdk's answer, you could save your operations in a Picture instead of a Bitmap.
A Bitmap will save all pixels, like
he said it could take a lot of
memory.
A Picture will save the
calls, like drawRect, drawLine, etc.
It depends of what is really heavy in your application : a lot of draw operations, a few draw operations but controlled by heavy calculations, a lot of blank/unused space (prefer Picture) etc...

Categories

Resources