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
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?
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.
Right now, I have it so that my layout consists of a Fragment that takes up the entire screen and displays an image. I want to make it so that an additional View exists on top of it, also taking up the entire screen. On that top layer, I want to be able to color it all black initially, and then create certain spots that are transparent (alpha?) and reveal the image displayed on the fragment behind it. So basically the screen will be all black except for a few spots where the image behind is showing through, which I would determine programmatically. I've looked into a bunch of the graphics and views that Android provides, but have no clue where to start. Is this suited for a SurfaceView if I just want it to be all black with some spots of alpha?
Once I select the correct view to use, I'm assuming that I just override the onDraw() method and then do something like canvas.setBody(black) and then add shapes of alpha to it? Will the shapes correctly affect the background color?
For your masking View, you can make a custom view that can keep track of which areas to unmask (as Rects or something similar) and then draw them in onDraw(Canvas) like this:
public class MaskView extends View {
private Set<Rect> mRects = new HashSet<Rect>();
private Paint mUnmaskPaint = new Paint();
{
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
/**
* Add an unmasking rectangle to this view's background.
*
* #param rect
* a rectangle used to unmask the background
*/
public void addUnmaskRect(Rect rect) {
mRects.add(rect);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.BLACK);
for (Rect r : mRects) {
canvas.drawRect(r, mUnmaskPaint);
}
}
}
Your Fragment (or whatever is keeping track of the unmask areas) passes Rects to MaskView via addUnmaskRect(Rect). Whenever the view is redrawn (remember to call invalidate() each time you are done passing Rects) it is first filled with black, and then has any rectangles unmask the black background. The coordinates for your rectangles must be set to the coordinate space of the view, but if the underlying image occupies the exact same area it should be relatively simple (you can also look at the View.getLocationInWindow(int[]) method to help you with this as well).
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.