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 than requestLayout

    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?

+3


source to share


1 answer


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:

view hierarchy

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:

view root ignores request

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:

layout request blocked

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.

+5


source







All Articles