OnDraw bug in android 4.2 - android

I have downloaded the 4.2 image for tests and I'm surprised with one thing. I have a HorizontalScrollView filled by ImageView's ancestors in my application. I rely on the fact, that onDraw in these ImageViews is called only when the view becomes explicitly visible to a user. But what I'm observing in 4.2 is that onDraw is called just at the moment of adding views to the HorizontalScrollView. Is it a supposed behaviour? Or am I missing anything?
Thanks for help.

Generally onDraw is called when the view has been inflated and after its measured.
I would never assume that onDraw means that that view is visible to a user. Even the onDisplayHint is not assurance that the view is visible.
Also worth noting the onDraw can be called multiple times, as other views move or the View hierarchy is invalidated().
Hope that helps?
If not have a good read through - Custom Views

Related

Why is requestLayout being called directly after invalidate

I'm learning about custom views and wanted to learn about invalidate() and requestLayout().
Please refer to this answer and its diagram:
https://stackoverflow.com/a/25846243/4243687
invalidate() tells Android that the state of the view has changed and needs to be re-drawn.
requestLayout() means the size of the view may have changed and needs to be remeasured, and then re-drawn.
invalidate() will invoke dispatchDraw(), draw(), and onDraw() hence it re-renders the view.
requestLayout() on the other hand does pretty much everything from measuring to re-rendering again.
Why do so many of the examples out there (even the TextView source code) call invalidate() and then requestLayout() right on the next line?
invalidate() is used specifically for redrawing the content of your view. The redraw does not happen synchronously. Instead, it flags the region of your view as invalid so that it will be redrawn during the next render cycle.
requestLayout() should be used when something within it has possibly changed its dimensions. In this case, the parent view and all other parents up the view hierarchy will need to readjust themselves via a layout pass.
If you are not doing anything to your view that would change its size, then you do not have to call requestLayout().
If you go back and look at the places in the code for TextView where requestLayout() is being called, it will be on methods where the view's bounds will be affected. For example, setPadding(), setTypeface(), setCompoundDrawables(), etc.
So, when requestLayout() is called, it should be paired with a call to invalidate to ensure that the entire view is redrawn.
After seeing the following diagram, I was under the impression that calling requestLayout() would eventually result in an onDraw.
Therefore, there would be no need to call these together because it would be redundant.
invalidate();
requestLayout();
However, it turns out that that diagram is misleading. Some views might in fact invalidate themselves when there is a layout change, but this is not a certainty. Calling requestLayout() is not guaranteed to result in onDraw being called.
My source (thanks to this comment) is the Romain Guy (who is an Android engineer at Google):
requestLayout() itself does not lead to a draw pass but some views
might react to a Layout change by calling invalidate.
Therefore, to be certain a relayout will result in a redraw, then you should pair an invalidate() with the requestLayout(). (The opposite is not true, though. If you only need a redraw, then there is no need to call requestLayout(). A single invalidate() will do.)
Relevant excerpt from the book Expert Android that answers the question:
Because the onClick event has caused the dimensions to change, our
view needs to become bigger and take more space. How do we express
that need to Android, Well, we request Layout(). This method goes up
the chain, marking every view parent that it needs to be remeasured.
When the final parent gets this request (the view root), the parent
schedules a layout traversal. A layout traversal may or may not result
in onDraw, although in this case it should. As a good programming
practice, we also call invalidate() to ensure the drawing phase as
well.
Android docs: Creating a View class
public boolean isShowText() {
return mShowText;
}
public void setShowText(boolean showText) {
mShowText = showText;
invalidate();
requestLayout();
}
Notice that setShowText calls invalidate() and requestLayout(). These
calls are crucial to ensure that the view behaves reliably. You have
to invalidate the view after any change to its properties that might
change its appearance, so that the system knows that it needs to be
redrawn. Likewise, you need to request a new layout if a property
changes that might affect the size or shape of the view. Forgetting
these method calls can cause hard-to-find bugs.

Why is my layout completely redrawing when I invalidate one of it's views?

I have a complex frame layout containing several custom views. The main view is a graph.
Overlaying the graph is a custom view which represents a cross hair pointer that the user can drag around the graph to read off detailed information about a particular data point.
The frame layout captures all touch events, calls "hit test" methods on each of it's child views to determine if the touch was on that view then dispatches the touch event to the appropriate view.
When the user touches the cross hair and drags it, I pass the touch event to the cross hair which it uses to update it's position and invalidate itself.
Everything is working except...
All views in the layout are redrawing when I invalidate any of these child views. I know this as I have turned on "show screen updates" in developer options. At no point do I invalidate the containing FrameLayout - or at least, not knowingly.
Certainly, the Activity handling the layout does not invalidate it and I do not have any references to the parent in the child views.
I haven't posted code since a) I don't know if this might be normal behaviour, b) there's a lot of it! and c) if it's not normal behaviour, I don't know which part of the code is problematic. Of course, I'm happy to give anything that might be requested.
My minimum API is 2.3.3 and my target is 4.0. This happens on both of those versions so I suspect my code is the problem but where to start?
Any clues as to what might be going on?
There is no way for View to draw "just itself", since it depends on it's parent (which provides it's clipped and translated Canvas via dispatchDraw method). Invalidation requires whole view tree to be invalidated - this is the answer that you're looking for :)
If you'll check out the source of ViewGroup, you'll find there two important methods: dispatchDraw (which responsible for dispatching draw event down to it's childs) and drawChild (which called by dispatchDraw and let's you specify how childs will be drawn). Note, that there is no check on which View starts invalidation, although there is a RectF that specifies invalidation region. As far as I understand - you need to make sure that your invalidating View (cross) doesn't takes the whole screen, so redrawn will be only that part that it actually takes.
If understand you right, the lines of the cross-hair view cover the whole screen, i.e. one vertical line from top to bottom and one horizontal from left to right.
Now if you change the center of the cross-hair and the other views would not be redrawn, your screen would soon be cluttered with old lines from previous cross-hair positions. You would not want this, so you can be glad that the other views redraw itself too.
If you want, you can specify a 'dirty' rectangle and call invalidate(Rect dirty), which would not redraw Views that are completely outside that rectangle. Though it's up to you in that case, not to change anything outside that rectangle.
try this:
setDrawingCacheEnabled(true);
Enabling drawing cache for view will make view not be redrawn if view not changed
Take a look : Android Invalidate() only single view
May be my answer so simple, but I resolved this problem with help:
this.invalidate()
Run it for each canvas-view, and it will updating one by one (not all at once)

Android EditText over SurfaceView

I have a custom View inherited from SurfaceView and I have EditText in the same RelativeLayout (both custom View and EditText are siblings in layout's xml).
When I make visible an EditText, this way
setVisibility(View.VISIBLE);
the custom View receives continuous calls of onDraw(Canvas c) method.
Does anybody know why? I never called invalidate() explicitly.
There isn't anything wrong here. This in normal behaviour. onDraw() is called a lot of times because Android redraws your activity whenever it feels there is a change it needs to display. There are a lot of sophisticated internal algorithms that decide when and why an activity is redrawn. Android takes care of it for us and we don't need to worry about it.
Once an activity is redrawn, all it's children are redrawn as well. This happens with all views and not custom views. Rest assured that there isn't any problem with your code.
Interested in learning more about the internals? Check out the source code for the View class!

View.onDraw() --- when does it get called?

I put a Log.d() call into the onDraw() of my extended View, so I could see how often and when it's getting called. It gets called upon instantiation of the view, which is not surprising. But then I notice, it gets called on every tap that is handled by onTouchEvent(), even though my code there isn't doing anything remotely related to graphics. However, in the documentation for Views, I can't seem to find anything about when onDraw() is actually called. I'm not really concerned about my particular project here (this doesn't cause a problem for me), I would just like to know if there is a list somewhere or something that shows the order of operations for a View, particularly what causes onDraw() to get called.
AFAIK, a View's onDraw() is called when:
The view is initially drawn
Whenever invalidate() is called on the view
Invalidate can be called by you or the system whenever needed. For example, a lot of Views change how they look onTouch, like an EditText getting an outline and cursor, or a button being in the pressed state. Due to this, Views are redrawn on touch.
I agree that it would be nice to have a document that detailed the working of Views, and if one exists and somebody knows where to find it, please let us know.
onDraw() is called when invalidate() is called.
But you should know for ViewGroups: onDraw() will not be called like you expect. Rather, onDispatchDraw().
However, in a ViewGroup you can call setWillNotDraw(false) in the constructor to make onDraw() to be called on invalidate().
Take a look at this answer
If you set a background drawable for a View, then the View will draw
it for you before calling back to its onDraw() method.
onAttachedToWindow () is called when the view is attached to a window.
At this point it has a Surface and will start drawing. Note that this
function is guaranteed to be called before
onDraw(android.graphics.Canvas), however it may be called any time
before the first onDraw -- including before or after onMeasure(int,
int).
invalidate() mark the area defined by dirty as needing to be drawn. If
the view is visible, onDraw(android.graphics.Canvas) will be called at
some point in the future.
One important thing to keep in mind is that try to minimize calling of invalidate() function with no arguments.
Instead try to maximize the invalidate() function with four arguments.As drawing whole view is very expensive.The second variant refreshes only the part of view.
Additional to the above: The soft keyboard causes a View.invalidate()-->View.onDraw() sequence after resizing the Window to sensibly accommodate the 'keyboard'. A custom View.onDraw() must leave itself in a state that anticipates this possibility.
Such phenomenum explains why the app you developed and tested on a tablet with a bluetooth keyboard went to the dogs once it reached the real world (-:
When is onDraw called (check this for more details)
The onDraw method is called whenever android thinks that your view
should be redrawn. This can be tha case when your view is animated, in
which case onDraw is called for every frame in the animation. It is
also called when the layout changes and your view is re-positioned on
the screen.
But what if some data inside your view has changed and you want to
make sure that the view is redrawn. You can’t call onDraw directly.
Instead you should call invalidate to tell the view that it needs to
redraw itself.

Android - onDraw not being called in dynamically inserted custom view

I have a custom view being dynamically inserted and the onDraw is not being called. I initially made the custom view without the dynamic insertion and it worked fine, however when moving it to my working code base, I cannot get onDraw to get called ever, even though the view constructor gets called and the onMeasure also gets called. I've tried putting invalidate() and postInvalidate() calls around the place, but they don't seem to force the view to draw. The view hierarchy looks the same for both version and I'm lost.
Can anyone help?
EDIT: A bit more testing has shown that onDraw is getting called on the emulator, but not the handset. Also onSizeChanged on the handset has the correct values coming through, but on the emulator the values are all 0.
EDIT: I've been playing about with it and still can't get anywhere, so have put a bounty on it. If you need more code / xml / info then please let me know.
This sounds a lot like a problem i had recently, with my first custom view. Did you notice if, when loading view hierarchy, your custom views show up? and anyway do they appear laid out in the wireframe preview or not?
If the latter is the case, it's requestLayout() that doesn't get called (loading view hierarchy calls that on every view - that's why it makes views show up). i've also found that a similar bug was reported already here.
I changed the View to a TextView and it seems to work now, although I'm a little confused as to why the View didn't work.

Categories

Resources