Why is requestLayout being called directly after invalidate - android

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.

Related

Android View bizarre behaviour

I have this bizarre behaviour when calling invalidate.
I have made a custom viewgroup containing a bunch of imageviews.
I set up a onTouch listener with the on touch method to scroll the view.
I know all the code is correct in terms of distances to scroll etc. by using logcat.
Now, for some reason the onLayout method is not being called after I call this.invalidate(). When I try to scroll, nothing changes on the screen. However, after I pause the activity and resume it, the screen will have shifted by the amount I scrolled.
I am not blocking the UI thread because there is nothing to block it with. In any case, I have tried postInvalidate() and nothing has worked.
Anybody have any bloody idea what's going on?
invalidate() does not relayout the view, it only causes the view to be redrawn. If you want to relayout it (which will invoke onLayout()) you also have to make a call to requestLayout().

Usage of forceLayout(), requestLayout() and invalidate()

I'm a bit confused about the roles of forceLayout(), requestLayout() and invalidate() methods of the View class.
When shall they be called?
To better understand answers provided by François BOURLIEUX and Dalvik I suggest you take a look at this awesome view lifecycle diagram by Arpit Mathur:
invalidate()
Calling invalidate() is done when you want to schedule a redraw of the view. It will result in onDraw being called eventually (soon, but not immediately). An example of when a custom view would call it is when a text or background color property has changed.
The view will be redrawn but the size will not change.
requestLayout()
If something about your view changes that will affect the size, then you should call requestLayout(). This will trigger onMeasure and onLayout not only for this view but all the way up the line for the parent views.
Calling requestLayout() is not guaranteed to result in an onDraw (contrary to what the diagram in the accepted answer implies), so it is usually combined with invalidate().
invalidate();
requestLayout();
An example of this is when a custom label has its text property changed. The label would change size and thus need to be remeasured and redrawn.
forceLayout()
When there is a requestLayout() that is called on a parent view group, it does not necessary need to remeasure and relayout its child views. However, if a child should be included in the remeasure and relayout, then you can call forceLayout() on the child. forceLayout() only works on a child if it occurs in conjunction with a requestLayout() on its direct parent. Calling forceLayout() by itself will have no effect since it does not trigger a requestLayout() up the view tree.
Read this Q&A for a more detailed description of forceLayout().
Further study
Creating a View Class: Add Properties and Events (helpful docs)
View documentation
View source code
Here you can find some response:
http://developer.android.com/guide/topics/ui/how-android-draws.html
For me a call to invalidate() only refreshes the view and a call to requestLayout() refreshes the view and compute the size of the view on the screen.
invalidate() ---> onDraw() from UI thread
postInvalidate() ---> onDraw() from background thread
requestLayout() ---> onMeasure() and onLayout() AND NOT Necessarily onDraw()
IMPORTANT: Calling this method does not affect the called class's child.
forceLayout() ---> onMeasure() and onLayout() JUST IF the direct parent called requestLayout().
you use invalidate() on a view that you want to redraw, it'll make its onDraw(Canvas c) to invoked, and requestLayout() will make the whole layout rendering ( measurement phase and positioning phase) run again. You should use it if you are changing child view's size on runtime but only in particular cases like constraints from the parent view(by that I mean that the parent height or width are WRAP_CONTENT and so match measure the children before they can wrap them again)
This answer is not correct about forceLayout().
As you can see in the code of forceLayout() it merely marks the view as "needs a relayout" but it does neither schedule nor trigger that relayout. The relayout will not happen until at some point in the future the view's parent was laid out for some other reason.
There is also a much bigger issue when using forceLayout() and requestLayout():
Let's say you've called forceLayout() on a view. Now when calling requestLayout() on a descendent of that view, Android will recursively call requestLayout() on that descendent's ancestors. The problem is that it will stop the recursion at the view on which you've called forceLayout(). So the requestLayout() call will never reach the view root and thus never schedule a layout pass. An entire subtree of the view hierarchy is waiting for a layout and calling requestLayout() on any view of that subtree will not cause a layout. Only calling requestLayout() on any view outside that subtree will break the spell.
I'd consider the implementation of forceLayout() (and how it affects requestLayout() to be broken and you should never use that function in your code.

OnDraw bug in android 4.2

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

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.

Confused with removeAllViewsInLayout(), postInvalidate() and refreshDrawableState()

I am really confused with this three functions: removeAllViewsInLayout(), postInvalidate() and refreshDrawableState().
removeAllViewsInLayout()
When I use removeAllViewsInLayout() in my program, all views were gone. But when trigger postInvalidate() to refresh, nothing. I think removeAllViewsInLayout() deletes all of my views. Is there a way to clear all things in my view but not delete it?
postInvalidate()
I want to refresh my view. But with refreshDrawableState(), I can do only once, why?
If you call postInvalidate() (or just call invalidate() if on the UI thread), the system will schedule a redraw of your view. Your onDraw() method can then do whatever it wants, including just drawing a blank canvas.
If you call removeAllViewsInLayout() it will do just that -- remove every view, including your custom view. You probably don't want to do that. (EDIT: removeAllViewsInLayout() is intended to be called only as part of layout calculations for a ViewGroup. A typical use would be by a ViewGroup that "owns" a large number of views but during onLayout() processing decides to display only those children that actually fits on the screen. If you are not in the process of computing the view layout, you should call removeAllViews() instead.)
Calling refreshDrawableState() is only useful if your view is using a Drawable object that is sensitive to the state of the view. For example, a Button will use this so that the background drawable changes color when the button is pressed. For what you are doing, you don't need to bother with this method, either.

Categories

Resources