在 Android 视图上更改 LayoutParams 无效,即使在调用 requestLayout 之后也是如此

Changing LayoutParams on an Android view has no effect, even after calling requestLayout

我已经 运行 遇到这种情况两三次,其中我有一些代码如下所示:

  ViewGroup.LayoutParams params = myView.getLayoutParams();
  params.height = 240;
  myView.requestLayout();

或者:

  ViewGroup.LayoutParams params = myView.getLayoutParams();
  params.height = 240;
  myView.setLayoutParams(params);

而且视图大小永远不会改变。我尝试了以下方法,但没有成功:

  1. 调用 forceLayout,具有讽刺意味的是,它似乎没有 requestLayout 强大,因为它没有通知视图的父级需要布局。
  2. 覆盖 onMeasure() 以查看它是否曾被调用(未调用)。
  3. 确保我没有在布局过程中调用 requestLayout

我也试过调用 requestLayout 给视图的所有父级,像这样:

  ViewParent parent = myView.getParent();
  while (parent != null) {
      parent.requestLayout();
      parent = parent.getParent();
  }

这行得通,但看起来真的很老套。我更愿意找到真正的解决方案。

我做错了什么?

如果您观察到这种行为,很可能是层次结构中的某些视图请求在后台线程上进行布局,这是一个禁忌。

为了找到有问题的视图,进入对 requestLayout 的调用并查找名为 mAttachInfo.mViewRequestingLayout 的变量。该参考可能会指向做出错误决定的观点。然后,您可以在代码中搜索在此视图上调用 requestLayoutsetLayoutParams 的任何位置。

如果您发现并修复了这些调用是从后台线程进行的任何情况,这可能会解决问题。

背景/说明

假设您有一个如下所示的视图层次结构:

然后,假设 NaughtyView 在后台线程上请求布局。

为了让 NaughtyView 实际 在下一次布局过程中得到测量,它需要递归地通知其父级,直到通知视图根:

请注意,每个视图都会为自己设置一个标志,表明它已请求布局。另请注意,视图根已忽略该请求。但是为什么?

有一种特定情况,视图根可能会忽略布局请求,这有点微妙。要了解这种情况,让我们看一下 ViewRootImpl.requestLayout:

的实现
@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

关于如何处理布局请求的简要说明。 You're not supposed to call requestLayout while a layout pass is in progress。如果您仍然这样做,视图根将 尝试 通过在第二遍中布置您的视图来满足您的请求。如果 mHandlingLayoutInLayoutRequest 成员变量为真,则表示视图根当前是 运行 第二遍。在该阶段,视图根忽略传入的布局请求,因此 mLayoutRequested 不会设置为 true。

假设对 requestLayout 的所有调用都像预期的那样在 ui 线程上发生,这种行为是无害的(事实上,是需要的)。

但是,如果您在后台线程上调用 requestLayout,它可能会打乱事件的顺序并使您的视图层次结构处于类似于上图的状态,其中有一串祖先设置了他们的“请求布局”标志,但是一个不知道它的视图根。

那么接下来,假设有人在 NiceView 上调用 requestLayout。这次,在 ui 线程上正确调用:

像往常一样,NiceView 试图通知它的所有祖先。但是,一旦这个通知到达 NiceView 与 NaughtyView 的共同祖先,遍历就会被阻止。发生这种情况是因为每个视图仅在其父级尚未被通知时才通知其父级(如上述标志所示)。

由于遍历永远不会到达视图根,因此不会发生布局。

这个问题可以如果层次结构中的某些其他视图会自行修复——任何与NaughtyView共享一个祖先的视图除了视图根之外——碰巧请求布局(正确地,在 ui 线程上)。当这种情况发生时,视图根自己的“请求布局”标志将被设置为真,它最终将对所有请求它的视图(包括 NaughtyView 和 NiceView)执行布局。但是,对于某些用户界面,您不能指望这种情况会在用户注意到之前发生。