does adding a child to a ViewGroup (via ViewGroup.addView() ) automatically invoke onMeasure() for all childs?
ViewGroup should measure it's width and height by first measuring all children. If ViewGroup's width or height is wrap_content it will call onMeasure() for all children for sure. Can't say that about match_parent. One thing for sure that addView() calls requestLayout() which will re-calculate layout children's position call layout(l, t, r, b) on all children.
To know for sure if it calls you can create a custom View and log every call to onMeasure() and add those to a ViewGroup. Could be that different ViewGroup implementation will act differently.
Related
THE CONTEXT
I'm trying to create a custom view that extends the LinearLayout. In short, this view draws its own background by overriding View.onDraw() method. The background has a shadow around it (much like the CardView).
THE OBJECTIVE
Since the background is like the card view with a shadow, child views should not be allowed to fill in the entire view. Fixed padding should be left for the shadows around the card. This padding should not be effected the padding set by the xml padding attribute or setPadding() methods. Its something akin to the CardView's setContentPadding() method.
THE APPROACH
I'm trying the achieve this by overriding the onLayout() method of the LinearLayout. The official documentation says:
Called from layout when this view should assign a size and position to each of its children. Derived classes with children should override this method and call layout on each of their children.
The LinearLayout's implementation of the onLayout() works well work my case. So don't want to rewrite the the entire implementation. Thus I tried to achieve this by overriding the onLayout() method in my custom class and changing the arguments passed to super to account for the shadow as shown below:
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(
changed,
left + shadowSize,
top + shadowSize,
right - shadowSize,
bottom - shadowSize
);
}
THE PROBLEM
The problem, however, is that this has no effect on the LinearLayout's children. No matter what values I pass to super, the child views remain the same.
THE QUESTION
Why is this not working? Or rather whats the best approach to achieve a content inset/padding for classes such as the LinearLayout that already have an onLayout() implementation that I don't want to modify or rewrite?
It's not working because width and height are attributes which provide information for the parent of the View. Anything that starts with layout_ is an instruction to the parent.
In your case, onLayout you are supposed to layout the children of the ViewGroup, not change the dimensions of the current view. In onMeasure is where you measure the children. And onLayout is where you position them. You CAN indent all the children if you want in onLayout but you can't do it if you're calling the parent.
This post may help understand build custom ViewGroup
What I recommend is that you extend ViewGroup and have only one child in the custom ViewGroup, then layout the child to the same size as your custom view group while adjusting for the shadow. This will allow you to set a shadow on your custom ViewGroup while avoiding the issue of drawing outside the bounds. This is the easiest approach. you could layout each child if you want. Also, if using the new ConstraintsLayout is an option, that may help prevent having to use a custom ViewGroup.
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.
I am building a custom AdapterView layout for my application. So far I am able to position all the item according to my specs. The only problem is that all the children still have getMeasuredWith == screen width. So after calling the layout() method for each child and defining its new width, all the children from this view keep the same size as before.
What do I need to do in order to get all the children view from my View to keep their bound between each child view.
Thanks
Thiago
To reliably get the size that your view needs to be you have to override the onSizeChanged() method.
I've a custom ViewGroup, to which a custom ImageView is added as a child. Now, my problem is that whenever I change the image in the child, by overriding the onDraw(), the onLayout method in the parent is called. Is it not possible to just change the image in the child without calling the onLayout method in the Custom ViewGroup? Thanks.
Interestingly, no one answers this simple question.
The onLayout() method is called by default when the viewgroup needs to be redrawn.
see https://developer.android.com/guide/topics/ui/how-android-draws.html
you can actually extend the viewgroup to implement your custom onLayout() and onMeasure() method to control the behavior it is called.
I have a ViewGroup and I'm adding child Views to it like this:
mViewGroup.addView(view, new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT));
(the WRAP_CONTENT is important for the onclicklistener to work as expected).
So far so good, but later I move the child Views around dynamically by either overriding their onDraw methods or by overriding the ViewGroups:
protected boolean getChildStaticTransformation(View child, Transformation t)
The problem is that the Children disappear if they are moved outside their original region which seems to be calculated when they are first added. I tried calling setClipChildren(false); but it didn't work.
I could solve the problem by using LayoutParams.FILL_PARENT when adding the child views but in this case the onClickListener would react to any click on the whole ViewGroup and I want it to only react to clicks on the specific area where my transformed child View is located.
Maybe I could still use LayoutParams.FILL_PARENT if there is a way to define where the clickable region for the View should be.
Any suggestions?
Perhaps you should experiment with margins and paddings of your child views. Otherwise you could try to use a different ViewGroup layout like a FrameLayout where the postion of each view can be set by its margins.