Changing LayoutParams in Android view has no effect, even after calling requestLayout
I ran into this situation two or three times where I have a code that looks like this:
ViewGroup.LayoutParams params = myView.getLayoutParams();
params.height = 240;
myView.requestLayout();
Or alternatively:
ViewGroup.LayoutParams params = myView.getLayoutParams();
params.height = 240;
myView.setLayoutParams(params);
And the view size never changes. I've tried the following, with no luck:
- A call
forceLayout
that ironically seems less powerful thanrequestLayout
that because it doesn't notify the parents of the View to compose. - Override
onMeasure()
to see if it's ever called (it doesn't). - Make sure I don't call on
requestLayout
during the layout pass.
I have also tried calling requestLayout
to all parents of the View, for example:
ViewParent parent = myView.getParent();
while (parent != null) {
parent.requestLayout();
parent = parent.getParent();
}
This works, but seems to be really hacky. I would rather find a real solution.
What am I doing wrong?
source to share
If you observe this behavior, it is likely that some view in your hierarchy has requested a layout on a background thread, which is no-no.
To find the offending view, go to your invocation requestLayout
and look for a variable named mAttachInfo.mViewRequestingLayout
. This link will most likely point to the view that caused the bad call. Then you can search your code for whatever places requestLayout
or setLayoutParams
is called in that view.
If you find and fix all the cases where these calls are made from a background thread, this will most likely fix the problem.
Background / Explanation
Suppose you have a view hierarchy that looks something like this:
Then, suppose NaughtyView is requesting a layout on a background thread.
In order for the NaughtyView to actually measure itself during the next layout pass, it must recursively notify its parents until the root of the view is notified:
Note that each view sets a flag on its own, indicating that it requested a layout. Also note that the view root ignored the request. But why?
There is a certain situation where the view root can ignore the layout request and it is a little thin. To see this situation, consider the implementation ViewRootImpl.requestLayout
:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
Brief information on how layout requests are handled. You don't have to call requestLayout
while the layout is passing . If you do, the view root will try to satisfy your request by laying out your view in a second pass. If the member variable mHandlingLayoutInLayoutRequest
is true, it means that the view root is currently working with this second pass. At this point, the view root ignores incoming layout requests and therefore mLayoutRequested
will not be set to true.
Assuming all calls requestLayout
happen on the ui thread as intended, this behavior is harmless (actually desirable).
However, if you make a call requestLayout
on a background thread, it can mess up the sequence of events and leave your view hierarchy in a state similar to the above diagram, with a line of ancestors that have their "mock" requested "flags, but the view root that doesn't know about it.
So, suppose someone is calling requestLayout
NiceView. This time the call is done correctly on the ui thread:
As usual NiceView tries to notify all of its ancestors. However, once this notification reaches the common NiceView ancestor with NaughtyView, the traversal is blocked. This is because each view only notifies its parent if its parent has not already been notified (as indicated by the above flag).
Since the traversal never reaches the root of the view, the link never occurs.
This issue can spontaneously fix itself if some other view in the hierarchy - any view that does not share an ancestor with a NaughtyView other than the view root - requests a layout (correctly, on the ui thread). When it does, the native view root flag will be set to true and it will finally do the layout in all the views it requested (including NaughtyView and NiceView). However, for some user interfaces, you cannot count on this before the user notices.
source to share