My original problem was needing to know the height and width of my root View so that I could make programmatic layout changes. For my purposes, I don't necessarily need to determine this information during onCreate(); it's quite sufficient for me to programmatically add my child Views after layout of the root has completed, so therefore I'm happy to use onWindowFocusChanged() as a hook to determine when calling the root View's getWidth() and getHeight() will return valid results.
However, I see quite a few people have asked how to determine the root View's size during onCreate(). I'm guessing people want to do this for better user experience - perhaps so that users don't see the layout being built in stages (if anyone can clarify the reasons, I'd appreciate it). Now, the answer I have commonly seen given to this, such as the one here, is to post a Runnable to the root View within onCreate(). Inside that Runnable we perform the getWidth(), etc. The reason why this works is because, apparently, Runnable objects posted to a View are executed when the View has layout and / or is attached to the window.
This brings me to my question. In the API documentation for View, it doesn't seem to describe this behavior. So, can anyone tell me where it is defined and documented? Or, is it a matter of inspecting the source? Is it an absolutely and rigidly defined feature that Runnables will stay in a View's queue and only be handled at a certain point after the layout process?
Further clarification: On reading Kerry's answer and thinking it over a bit more, my basic question can be clarified as follows: In the answer given here and also in this CodeProject entry, I understand that we get around the problem of the root View's dimensions not being available during the onCreate() by posting a Runnable to the View. As Kerry points out, the guarantee is that this message Runnable cannot be executed until onCreate() has executed. I understand that layout occurs some point after onCreate, but I still can't grasp at the moment why this Runnable should execute when the View's dimensions are known.
I had a look at the View.post(Runnable) method and it seems to me that by calling that method you are just adding a message to the UI thread queue. The only thing that is going to be 'guaranteed' is that the Runnable will be executed sometime AFTER the method which has called post(Runnable) has completed. This assumes you are calling post(Runnable) from the UI thread but as the docs say:
This method can be invoked from outside of the UI thread only when this View is attached to a window.
I think to be sure that both your Root view and Child view both have size, you would need to call post(Runnable) from the child onSizeChanged() method because if the child has size I think it follows that the Parent i.e. Root view therefore must have size too.
Apologies if this is a bit of a rambling answer. I kind of understand what you're wanting to achieve but not 100% sure. Let me know if you want me to clarify anything.
Personally whenever I've needed the size of a View I do everything in onSizeChanged() which I do believe is the 'correct' way of doing it and it has always worked.
Related
I'm wondering why the post() method is a specific to a View and not just a static method. It doesn't seem like the Runnable argument is closely tied to a specific view anyway.
I did find this other question which explains that (as of 4 years ago) the runnable will be run after the view has been drawn, but that doesn't quite answer my question.
What event is fired after all views are fully drawn?
Because it decides which handler to post the event to based on a few different criteria. Check the code at the AOSP. If the view is attached to a window, it uses the one in the attach info. If not, it uses the ViewRoot.getRunQueue and posts it there. So there's a possibility of it posting to different handlers if, for example, you had a view in a different window like a Toast you were calling post on. Thus the need for a non-static function.
I wanted to know if View.post(Runnable r) runnable's run() method is executed after View is drawn or after View (in case it is a ViewGroup) and all it's children are drawn?
It will be put into UI message queue, and it depends on internal Android workings when exactly will be executed. If rendering of child views is in one message then it might execute after rendering, if it is split by android into several messages then your message might possibly be executed in between of rendering.
If you want to make sure it will be executed after child redraw, maybe use View.postDelayed with some small delay?
What event is fired after all views are fully drawn?
here is explanation from Romain Guy that it is actuall executed after redraw - if nothing changed (its 4 year old SO), then you might trust this answer.
I am populating a linear layout dynamically. Depending upon response, I have to clear the past child views and create new views. I have read the document, but still be confused with the couple methods, they all look the same function. Which function I should use.
As Scott Biggs points out, the difference is not a big one. The only difference is that removeAllViews() calls requestLayout() and invalidate() afterwards. The key to why this difference is here is to understand the naming of removeAllViewInLayout(). Confusingly, its meaning isn't "remove all views within this view layout."
If we look at the similar method, removeViewInLayout(), we can understand what it's supposed to mean:
Removes a view during layout. This is useful if in your onLayout() method, you need to remove more views.
So removeAllViewsInLayout() actually means "remove all views, and we're calling this method during a layout pass (i.e. onLayout())". That's why removeAllViewsInLayout() doesn't call through to requestLayout(), as it's assumed that you're already currently inside a layout pass, so requesting another layout pass is unneeded.
If you use removeAllViewsInLayout(), then it's your responsibility to ensure that you're calling this during a layout pass, or to properly call requestLayout() and invalidate() as needed.
removeAllViews() : Call this method to remove all child views from the ViewGroup.
removeAllViewsInLayout() : Called by a ViewGroup subclass to remove child views from itself, when it must first know its size on screen before it can calculate how many child views it will render.
Well, looking at the source, there isn't much difference:
public void removeAllViews() {
removeAllViewsInLayout(); // Details implemented here
requestLayout();
invalidate(true);
}
So unless you want to call invalidate() at a time of your choosing, you might as well use removeAllViews() and save yourself a bit of typing.
EDIT
For a more detailed explanation, see David Lui's answer. To sum it up, use removeAllViews() unless you're in the process of constructing a View--in which case you'd call removeAllViewsInLayout().
In GUI, when a view is required to be redraw. We usually do this as follows:
View v = View ();
v.invalidate()
I have no idea that the operation "invalidate()" is such named. In English, "invalidate" means to make something not valid. But "invalidate a view" in GUI programming is meant to make the view to be redrawn. I am not a native English speaker. Please give me hints. Tks.
To "invalidate a view" means that any data that may already be drawn is no longer valid. The view of the data is invalid, hence invalidate() to mark it as such. It's less that the function is making it invalid so much as it is already invalid, but no-one except the caller to invalidate() knows that yet. The purpose of the function is to tell the rest of the code that the views data is now invalid.
Invalidate is semantically different from redrawing in that it usually only marks something to be redrawn later, as opposed to redrawing at call-time. The idea is that when several possibly overlapping areas are invalidated, the amount of redrawing at a later time can then be made equal to only the sum of non-overlapping areas. That is, we may redraw only once for several subsequent invalidations. Hence the distinction between redrawing and invalidation exists.
Is there any way to query a root view of an activity for all of its child views even before the root view or its children have been inflated? I guess what I'm looking for is whether a view knows ahead of time what children it will have before it gets inflated, and can I get that list in some way. Bizarre I realize, but I think it will help me with some unconventional automation testing I'm working on. I haven't found anything in the API like this, so I'm hoping the community knows something spiff that will help.
Thanks!
I doubt it.
An Activity doesn't have any knowledge of its view (it just has its root window) until you call setContentView() in the onCreate() method.
There is a construct called ViewStub which allows you to delay inflating certain parts of the UI until you need them, but even then you wouldn't actually know anything about the child views until you inflate it.
Something else that may be of interest is the ViewTreeObserver and its subclasses. That allows you to receive notifications when a view hieararchy is changing.
Your test automation certainly does sound unconventional. Intriguing :)