问题描述
在
A 部分 - 在此部分中,对三个视图调用 forceLayout()
.正如 Suragch 指出的那样,除了在所有视图上调用 forceLayout()
时调用 onDraw()
之外,什么都没有发生.这是 A 部分的日志:
I/MainActivity: 1*********************************************
I/MainActivity: 2***********************************************
I/MainActivity: 3************************************************
I/MainActivity: 4***********************************************
I/MainActivity: 5************************************************
I/MainActivity: 6************************************************
I/MainActivity: 7************************************************
I/MyChildView: onDraw 调用 (1)
1****..."对应于电子表格中的行.像I/MyChildView: onDraw called (1)"这样的行标识了视图(MyChildView"),视图方法(onDraw")和(x)"将是第一个子视图的(1)","(2)" 表示第二个子视图,"(null)" 表示其他非子视图.
鉴于该方法的名称,这是一个意外的结果:forceLayout()
.
Section B - 在第 8 行,在子视图上调用 requestLayout()
并且结果是预期的:对所有进行了测量、布局和绘图过程观点.第 9 行向孩子添加了对 forceLayout()
的调用,但结果是一样的.这是 B 部分的日志:
/MainActivity: 8********************************************
I/requestLayout: MyChildView (1)
I/requestLayout: MyChildView isLayoutRequested=false (1)
I/requestLayout: ViewGroupParent (null)
I/requestLayout: ViewGroupParent isLayoutRequested=false (null)
I/requestLayout: ViewGroupGrandparent (null)
I/requestLayout: ViewGroupGrandparent isLayoutRequested=false (null)
I/requestLayout: LinearLayout (null)
I/requestLayout: LinearLayout isLayoutRequested=false (null)
I/requestLayout: ContentFrameLayout (null)
I/requestLayout: ContentFrameLayout isLayoutRequested=false (null)
I/requestLayout: ActionBarOverlayLayout (null)
I/requestLayout: ActionBarOverlayLayout isLayoutRequested=false (null)
I/requestLayout: FrameLayout (null)
I/requestLayout: FrameLayout isLayoutRequested=false (null)
I/requestLayout: LinearLayout (null)
I/requestLayout: LinearLayout isLayoutRequested=false (null)
I/requestLayout: DecorView (null)
I/requestLayout: DecorView isLayoutRequested=false (null)
I/measure: ViewGroupGrandparent (null)
I/ViewGroupGrandparent: onMeasure 调用
I/measure: ViewGroupParent (null)
I/ViewGroupParent: onMeasure 被调用
I/measure: MyChildView (1)
I/MyChildView: onMeasure 调用 (1)
I/measure: MyChildView (2)
I/ViewGroupGrandparent: onLayout 调用
I/ViewGroupParent: onLayout 调用
I/MyChildView: onLayout 调用 (1)
I/MyChildView: onDraw 调用 (1)
I/MainActivity: 9***********************************************
I/requestLayout: MyChildView (1)
I/requestLayout: MyChildView isLayoutRequested=false (1)
I/requestLayout: ViewGroupParent (null)
I/requestLayout: ViewGroupParent isLayoutRequested=false (null)
I/requestLayout: ViewGroupGrandparent (null)
I/requestLayout: ViewGroupGrandparent isLayoutRequested=false (null)
I/requestLayout: LinearLayout (null)
I/requestLayout: LinearLayout isLayoutRequested=false (null)
I/requestLayout: ContentFrameLayout (null)
I/requestLayout: ContentFrameLayout isLayoutRequested=false (null)
I/requestLayout: ActionBarOverlayLayout (null)
I/requestLayout: ActionBarOverlayLayout isLayoutRequested=false (null)
I/requestLayout: FrameLayout (null)
I/requestLayout: FrameLayout isLayoutRequested=false (null)
I/requestLayout: LinearLayout (null)
I/requestLayout: LinearLayout isLayoutRequested=false (null)
I/requestLayout: DecorView (null)
I/requestLayout: DecorView isLayoutRequested=false (null)
I/measure: ViewGroupGrandparent (null)
I/ViewGroupGrandparent: onMeasure 被调用
I/measure: ViewGroupParent (null)
I/ViewGroupParent: onMeasure 被调用
I/measure: MyChildView (1)
I/MyChildView: onMeasure 调用 (1)
I/measure: MyChildView (2)
I/ViewGroupGrandparent: onLayout 调用
I/ViewGroupParent: onLayout 调用
I/MyChildView: onLayout 调用 (1)
I/MyChildView: onDraw 调用 (1)
C 部分 - 这里是事情变得有趣的地方.对于第 10 行和第 11 行,在子视图上调用 requestLayout()
,在子视图上调用 forceLayout()
.结果是我们在 B 部分看到的后续测量/布局/绘制通道不会发生.我相信这就是流体声波说 forceLayout()
已损坏的原因.请参阅https://stackoverflow.com/a/44781500/6287910.
其实子视图考虑在父视图上调用requestLayout()
,但是发现已经在父视图上请求了一个布局.(mParent != null && !mParent.isLayoutRequested()
).这是一个链接isLayoutRequested()
的代码.
public boolean isLayoutRequested() {返回 (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;}
记住 forceLayout()
设置了 PFLAG_FORCE_LAYOUT
标志.这就是 requestLayout()
链停在父级的原因.这可能是一个问题,也可能只是对 forceLayout()
的误用.
继续 C 部分的其余部分,我们最多可以强制"调用孩子的 onDraw()
.
这是 C 部分的日志:
I/MainActivity: 10*********************************************
I/requestLayout: MyChildView (1)
I/requestLayout: MyChildView isLayoutRequested=true (1)
I/MainActivity: 11***********************************************
I/requestLayout: MyChildView (1)
I/requestLayout: MyChildView isLayoutRequested=true (1)
I/MainActivity: 12***********************************************
I/requestLayout: MyChildView (1)
I/requestLayout: MyChildView isLayoutRequested=false (1)
I/requestLayout: ViewGroupParent (null)
I/requestLayout: ViewGroupParent isLayoutRequested=true (null)
I/MyChildView: onDraw 调用 (1)
I/MainActivity: 13************************************************
I/requestLayout: MyChildView (1)
I/requestLayout: MyChildView isLayoutRequested=false (1)
I/requestLayout: ViewGroupParent (null)
I/requestLayout: ViewGroupParent isLayoutRequested=true (null)
I/MyChildView: onDraw 调用 (1)
I/MainActivity: 14***********************************************
I/requestLayout: MyChildView (1)
I/requestLayout: MyChildView isLayoutRequested=true (1)
I/MyChildView: onDraw 调用 (1)
I/MainActivity: 15************************************************
I/requestLayout: MyChildView (1)
I/requestLayout: MyChildView isLayoutRequested=true (1)
I/MyChildView: onDraw 调用 (1)
D 部分 - 此部分可能包含 forceLayout()
的秘密.在第 16 行,对父级的 requestLayout()
调用导致父级和祖父级但不是子级的测量/布局传递.如果对子项进行了对 forceLayout()
的调用,则包括子项.事实上,调用的是孩子的onMeasure()
,而不会调用其兄弟的onMeasure()
.这是由于对子进程调用了 forceLayout()
.所以,看起来,这里 forceLayout()
被用来强制框架测量通常不会被测量的孩子.我会注意到,这似乎只发生在 forceLayout()
在 requestLayout()
的目标视图的 _direct 后代上调用时.
此类处理的一个示例是在 TableLayout 中.在 TableLayout 的 requestLayout()
覆盖中,forceLayout()
在每个子节点上调用.这将避免在每个子节点上调用 requestLayout()
以及相关的开销(尽管可能很小).它还可以避免灾难性的递归,因为孩子的 requestLayout()
可能会调用父母的 requestLayout()
,后者将调用孩子的......你明白了.这是来自TableLayout的代码:
public void requestLayout() {如果(m初始化){int count = getChildCount();for (int i = 0; i < count; i++) {getChildAt(i).forceLayout();}}super.requestLayout();}
在ListView.java中,需要重新测量一个child再重用查看代码此处.forceLayout()
在这里工作以重新测量孩子.
//因为这个视图是直接在父度量上度量的//规范,我们必须在重用之前再次测量它.child.forceLayout();
这是 D 部分的日志:
I/MainActivity: 16*********************************************
I/requestLayout: ViewGroupParent (null)
I/requestLayout: ViewGroupParent isLayoutRequested=false (null)
I/requestLayout: ViewGroupGrandparent (null)
I/requestLayout: ViewGroupGrandparent isLayoutRequested=false (null)
I/requestLayout: LinearLayout (null)
I/requestLayout: LinearLayout isLayoutRequested=false (null)
I/requestLayout: ContentFrameLayout (null)
I/requestLayout: ContentFrameLayout isLayoutRequested=false (null)
I/requestLayout: ActionBarOverlayLayout (null)
I/requestLayout: ActionBarOverlayLayout isLayoutRequested=false (null)
I/requestLayout: FrameLayout (null)
I/requestLayout: FrameLayout isLayoutRequested=false (null)
I/requestLayout: LinearLayout (null)
I/requestLayout: LinearLayout isLayoutRequested=false (null)
I/requestLayout: DecorView (null)
I/requestLayout: DecorView isLayoutRequested=false (null)
I/measure: ViewGroupGrandparent (null)
I/ViewGroupGrandparent: onMeasure 调用
I/measure: ViewGroupParent (null)
I/ViewGroupParent: onMeasure 被调用
I/measure: MyChildView (1)
I/measure: MyChildView (2)
I/ViewGroupGrandparent: onLayout 调用
I/ViewGroupParent: onLayout 调用
I/MainActivity: 17************************************************
I/requestLayout: ViewGroupParent (null)
I/requestLayout: ViewGroupParent isLayoutRequested=false (null)
I/requestLayout: ViewGroupGrandparent (null)
I/requestLayout: ViewGroupGrandparent isLayoutRequested=false (null)
I/requestLayout: LinearLayout (null)
I/requestLayout: LinearLayout isLayoutRequested=false (null)
I/requestLayout: ContentFrameLayout (null)
I/requestLayout: ContentFrameLayout isLayoutRequested=false (null)
I/requestLayout: ActionBarOverlayLayout (null)
I/requestLayout: ActionBarOverlayLayout isLayoutRequested=false (null)
I/requestLayout: FrameLayout (null)
I/requestLayout: FrameLayout isLayoutRequested=false (null)
I/requestLayout: LinearLayout (null)
I/requestLayout: LinearLayout isLayoutRequested=false (null)
I/requestLayout: DecorView (null)
I/requestLayout: DecorView isLayoutRequested=false (null)
I/measure: ViewGroupGrandparent (null)
I/ViewGroupGrandparent: onMeasure 调用
I/measure: ViewGroupParent (null)
I/ViewGroupParent: onMeasure 被调用
I/measure: MyChildView (1)
I/MyChildView: onMeasure 调用 (1)
I/measure: MyChildView (2)
I/ViewGroupGrandparent: onLayout 调用
I/ViewGroupParent: onLayout 调用
I/MyChildView: onLayout 调用 (1)
I/MyChildView: onDraw 调用 (1)
E 部分 - 本部分进一步说明,只有调用 requestLayout()
的目标视图的直接后代似乎参与触发的布局传递.第 34 和 35 行似乎表明嵌套视图可以链接.
这是 E 部分的日志:
I/MainActivity: 32*********************************************
I/requestLayout: ViewGroupGrandparent (null)
I/requestLayout: ViewGroupGrandparent isLayoutRequested=false (null)
I/requestLayout: LinearLayout (null)
I/requestLayout: LinearLayout isLayoutRequested=false (null)
I/requestLayout: ContentFrameLayout (null)
I/requestLayout: ContentFrameLayout isLayoutRequested=false (null)
I/requestLayout: ActionBarOverlayLayout (null)
I/requestLayout: ActionBarOverlayLayout isLayoutRequested=false (null)
I/requestLayout: FrameLayout (null)
I/requestLayout: FrameLayout isLayoutRequested=false (null)
I/requestLayout: LinearLayout (null)
I/requestLayout: LinearLayout isLayoutRequested=false (null)
I/requestLayout: DecorView (null)
I/requestLayout: DecorView isLayoutRequested=false (null)
I/measure: ViewGroupGrandparent (null)
I/ViewGroupGrandparent: onMeasure 被调用
I/measure: ViewGroupParent (null)
I/ViewGroupGrandparent: onLayout 调用
I/MainActivity: 33***********************************************
I/requestLayout: ViewGroupGrandparent (null)
I/requestLayout: ViewGroupGrandparent isLayoutRequested=false (null)
I/requestLayout: LinearLayout (null)
I/requestLayout: LinearLayout isLayoutRequested=false (null)
I/requestLayout: ContentFrameLayout (null)
I/requestLayout: ContentFrameLayout isLayoutRequested=false (null)
I/requestLayout: ActionBarOverlayLayout (null)
I/requestLayout: ActionBarOverlayLayout isLayoutRequested=false (null)
I/requestLayout: FrameLayout (null)
I/requestLayout: FrameLayout isLayoutRequested=false (null)
I/requestLayout: LinearLayout (null)
I/requestLayout: LinearLayout isLayoutRequested=false (null)
I/requestLayout: DecorView (null)
I/requestLayout: DecorView isLayoutRequested=false (null)
I/measure: ViewGroupGrandparent (null)
I/ViewGroupGrandparent: onMeasure 被调用
I/measure: ViewGroupParent (null)
I/ViewGroupGrandparent: onLayout 调用
I/MainActivity: 34***********************************************
I/requestLayout: ViewGroupGrandparent (null)
I/requestLayout: ViewGroupGrandparent isLayoutRequested=false (null)
I/requestLayout: LinearLayout (null)
I/requestLayout: LinearLayout isLayoutRequested=false (null)
I/requestLayout: ContentFrameLayout (null)
I/requestLayout: ContentFrameLayout isLayoutRequested=false (null)
I/requestLayout: ActionBarOverlayLayout (null)
I/requestLayout: ActionBarOverlayLayout isLayoutRequested=false (null)
I/requestLayout: FrameLayout (null)
I/requestLayout: FrameLayout isLayoutRequested=false (null)
I/requestLayout: LinearLayout (null)
I/requestLayout: LinearLayout isLayoutRequested=false (null)
I/requestLayout: DecorView (null)
I/requestLayout: DecorView isLayoutRequested=false (null)
I/measure: ViewGroupGrandparent (null)
I/ViewGroupGrandparent: onMeasure 调用
I/measure: ViewGroupParent (null)
I/ViewGroupParent: onMeasure 被调用
I/measure: MyChildView (1)
I/measure: MyChildView (2)
I/ViewGroupGrandparent: onLayout 调用
I/ViewGroupParent: onLayout 调用
I/MainActivity: 35***********************************************
I/requestLayout: ViewGroupGrandparent (null)
I/requestLayout: ViewGroupGrandparent isLayoutRequested=false (null)
I/requestLayout: LinearLayout (null)
I/requestLayout: LinearLayout isLayoutRequested=false (null)
I/requestLayout: ContentFrameLayout (null)
I/requestLayout: ContentFrameLayout isLayoutRequested=false (null)
I/requestLayout: ActionBarOverlayLayout (null)
I/requestLayout: ActionBarOverlayLayout isLayoutRequested=false (null)
I/requestLayout: FrameLayout (null)
I/requestLayout: FrameLayout isLayoutRequested=false (null)
I/requestLayout: LinearLayout (null)
I/requestLayout: LinearLayout isLayoutRequested=false (null)
I/requestLayout: DecorView (null)
I/requestLayout: DecorView isLayoutRequested=false (null)
I/measure: ViewGroupGrandparent (null)
I/ViewGroupGrandparent: onMeasure 被调用
I/measure: ViewGroupParent (null)
I/ViewGroupParent: onMeasure 被调用
I/measure: MyChildView (1)
I/MyChildView: onMeasure 调用 (1)
I/measure: MyChildView (2)
I/ViewGroupGrandparent: onLayout 调用
I/ViewGroupParent: onLayout 调用
I/MyChildView: onLayout 调用 (1)
I/MyChildView: onDraw 调用 (1)
所以这是我对 forceLayout()
的总结:当有需要重新测量的子项(例如在 TableLayout 中)并且您不想调用 时使用它requestLayout()
在每个孩子上 - forceLayout()
重量更轻,将避免递归.(请参阅 C 部分中的注释.)forceLayout()
还可用于在需要时强制重新测量特定的直接子项,而它们通常不会被测量.forceLayout()
不能单独工作,必须与对 requestLayout()
In this answer I wrote
As I recall I wrote my answer by reading the documentation and source code. However, I didn't experience using forceLayout
. A user commented that it was not working as I described.
Testing forceLayout
I am finally getting around to researching the cause for this. I set up a simple project with a grandparent ViewGroup
, a parent ViewGroup
, and a child View
. I used custom views for each of them so that I could watch the log statements in onMeasure
, onLayout
, and onDraw
.
When the layout is first created from xml I get the following log:
ViewGroupGrandparent onMeasure called
ViewGroupParent onMeasure called
MyChildView onMeasure called
ViewGroupGrandparent onMeasure called
ViewGroupParent onMeasure called
MyChildView onMeasure called
ViewGroupGrandparent onLayout called
ViewGroupParent onLayout called
MyChildView onLayout called
MyChildView onDraw called
forceLayout
This looks like reasonable output. However, when I subsequently call forceLayout
individually on any of the views I get nothing. If I call them all at once, then the child view's onDraw
gets called.
child
childView.forceLayout();
// (no log output)
parent
viewGroupParent.forceLayout();
// (no log output)
grandparent
viewGroupGrandparent.forceLayout();
// (no log output)
all together
childView.forceLayout();
viewGroupParent.forceLayout();
viewGroupGrandparent.forceLayout();
// MyChildView onDraw called
requestLayout
On the other hand, calling requestLayout
has a much bigger effect.
child
childView.requestLayout();
// ViewGroupGrandparent onMeasure called
// ViewGroupParent onMeasure called
// MyChildView onMeasure called
// ViewGroupGrandparent onLayout called
// ViewGroupParent onLayout called
// MyChildView onLayout called
// MyChildView onDraw called
parent
viewGroupParent.requestLayout();
// ViewGroupGrandparent onMeasure called
// ViewGroupParent onMeasure called
// ViewGroupGrandparent onLayout called
// ViewGroupParent onLayout called
grandparent
viewGroupGrandparent.requestLayout();
// ViewGroupGrandparent onMeasure called
// ViewGroupGrandparent onLayout called
Question
When does forceLayout
have any effect? Why doesn't it seem to work as it is supposed to in my examples above?
Supplemental code
Here is the code I used to make the tests above.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.forcelayout.MainActivity">
<com.example.forcelayout.ViewGroupGrandparent
android:id="@+id/view_group_grandparent"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.example.forcelayout.ViewGroupParent
android:id="@+id/view_group_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.example.forcelayout.MyChildView
android:id="@+id/child_view"
android:layout_width="100dp"
android:layout_height="100dp"/>
</com.example.forcelayout.ViewGroupParent>
</com.example.forcelayout.ViewGroupGrandparent>
<Button
android:text="Click me"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="buttonClick"/>
</LinearLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void buttonClick(View view) {
Log.i("TAG", "buttonClick: ");
ViewGroupGrandparent viewGroupGrandparent = (ViewGroupGrandparent) findViewById(R.id.view_group_grandparent);
ViewGroupParent viewGroupParent = (ViewGroupParent) findViewById(R.id.view_group_parent);
MyChildView childView = (MyChildView) findViewById(R.id.child_view);
childView.forceLayout();
//viewGroupParent.forceLayout();
//viewGroupGrandparent.forceLayout();
//childView.requestLayout();
//viewGroupParent.requestLayout();
//viewGroupGrandparent.requestLayout();
}
}
ViewGroupGrandparent.java
public class ViewGroupGrandparent extends LinearLayout {
public ViewGroupGrandparent(Context context) {
super(context);
}
public ViewGroupGrandparent(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ViewGroupGrandparent(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.i("TAG", "ViewGroupGrandparent onMeasure called");
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Log.i("TAG", "ViewGroupGrandparent onLayout called");
super.onLayout(changed, l, t, r, b);
}
@Override
protected void onDraw(Canvas canvas) {
Log.i("TAG", "ViewGroupGrandparent onDraw called");
super.onDraw(canvas);
}
}
ViewGroupParent.java
public class ViewGroupParent extends LinearLayout {
public ViewGroupParent(Context context) {
super(context);
}
public ViewGroupParent(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ViewGroupParent(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.i("TAG", "ViewGroupParent onMeasure called");
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Log.i("TAG", "ViewGroupParent onLayout called");
super.onLayout(changed, l, t, r, b);
}
@Override
protected void onDraw(Canvas canvas) {
Log.i("TAG", "ViewGroupParent onDraw called");
super.onDraw(canvas);
}
}
MyChildView.java
public class MyChildView extends View {
public MyChildView(Context context) {
super(context);
}
public MyChildView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyChildView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.i("TAG", "MyChildView onMeasure called");
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Log.i("TAG", "MyChildView onLayout called");
super.onLayout(changed, left, top, right, bottom);
}
@Override
protected void onDraw(Canvas canvas) {
Log.i("TAG", "MyChildView onDraw called");
super.onDraw(canvas);
}
}
TL;DR Consider the following code from TableLayout:
public void requestLayout() {
if (mInitialized) {
int count = getChildCount();
for (int i = 0; i < count; i++) {
getChildAt(i).forceLayout();
}
}
super.requestLayout();
}
Here each child of the TableLayout will be flagged to be measured on a future layout pass through a call to forceLayout()
. Similar processing will occur if requestLayout()
is called on each child, but requestLayout()
bubbles up through the view hierachy, so the requestLayout()
of a child of TableLayout will call its parent's requestLayout()
. This will set up an infinite loop with TableLayout and its child calling one another. forceLayout()
forces measurement without the threat of infinite recursion.
forceLayout()
does not call requestLayout()
on its parent as stated but clears the view's cache and sets a couple of flags.
public void forceLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
}
requestLayout()
clears the cache and sets these same flags as forceLayout()
but also might call requestLayout()
on the parent.
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
...
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
...
}
requestLayout()
should bubble up through the entire hierarchy.
So, what does forceLayout()
actually do? To research this question, I took the provided app and modified it to trace calls to onMeasure()
, onLayout()
and onDraw()
for the two view groups (Grandparent and Parent) and the child view. I added a sibling child to the first to compare what happens to the two of them. I also used the debugger to trace calls to measure()
and requestLayout()
. Output was captured in logcat and from logcat a spreadsheet was produced summarizing the operations. (Source code and documentation reference in this answer is cataloged at this GitHub project.
The test app calls forceLayout()
and requestLayout()
for the two view groups and child view in all possible combinations - 64 in all. (Many of these combinations are not realistic in the real world, but are included for completeness.) The spreadsheet below summarizes key areas for discussion. The full sheet can be found at the GitHub repository.
Section A - In this section, forceLayout()
is called on the three views. As Suragch noted, nothing really happens other than onDraw()
is called when forceLayout()
is invoked on all the views. Here is the log for section A:
"1****..." corresponds to the line in the spreadsheet. Lines like "I/MyChildView: onDraw called (1)" identifies the view ("MyChildView"), the view method ("onDraw") and "(x)" will be "(1)" for the first child view, "(2)" for the second child view and "(null)" for other non-child views.
This is an unexpected result given the name of the method: forceLayout()
.
Section B - On line 8, requestLayout()
is called on the child view and the results are expected: a measure, layout and drawing pass are taken on all the views. Line 9 adds a call to forceLayout()
to the child, but the results are the same. Here is the log for section B:
Section C - Here is where things get interesting. For lines 10 and 11, requestLayout()
is called on the child view and forceLayout()
is called on the child's parent view. The result is that the subsequent measure/layout/draw passes that we saw in section B do not happen. I believe that this is why fluidsonic said that forceLayout()
is broken. See https://stackoverflow.com/a/44781500/6287910.
In fact, the child view considers calling requestLayout()
on the parent but finds that a layout has already been requested on the parent. (mParent != null && !mParent.isLayoutRequested()
). Here is a link to the code for isLayoutRequested()
.
public boolean isLayoutRequested() {
return (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
}
Remember that forceLayout()
set the PFLAG_FORCE_LAYOUT
flag. This is why the requestLayout()
chain stops at the parent. This could be an issue or just a misuse of forceLayout()
.
Continuing on with the rest of section C, the most we can "force" is a call to the child's onDraw()
.
Here is the log for section C:
Section D - This section may hold the secret to forceLayout()
. On line 16, a call to requestLayout()
on the parent results in a measure/layout pass for the parent and the grandparent but not the child. If a call to forceLayout()
is made on the child, then the child is included. In fact, a call is made to the child's onMeasure()
while a call is not made to its sibling's onMeasure()
. This is due to the call to forceLayout()
on the child. So, it seems, that here forceLayout()
is being used to force the framework to measure a child that would not ordinarily be measured. I will note that this only seems to happen when forceLayout()
is called on a _direct descendent of the target view of requestLayout()
.
One such example of this type of processing is in TableLayout. In the requestLayout()
override in TableLayout, forceLayout()
is called on each child. This will avoid calling requestLayout()
on each child and the associated overhead that would entail (although probably small). It will also avoid disastrous recursion since the child's requestLayout()
may call the parent's requestLayout()
that will call the child's...you get the idea. Here is the code from TableLayout:
public void requestLayout() {
if (mInitialized) {
int count = getChildCount();
for (int i = 0; i < count; i++) {
getChildAt(i).forceLayout();
}
}
super.requestLayout();
}
In ListView.java, there is a need to remeasure a child before reuse See the code here. forceLayout()
works here to get the child remeasured.
// Since this view was measured directly aginst the parent measure
// spec, we must measure it again before reuse.
child.forceLayout();
Here is the log for section D:
Section E - This section further demonstrates that only direct descendents of the target view of a call to requestLayout()
seems to participate in the triggered layout passes. Lines 34 and 35 seem to indicate that a nested views can chain.
Here is the log for section E:
So this is my take-away for forceLayout()
: Use it when there are children that need to be re-measured such as in TableLayout and you don't want to call requestLayout()
on each child - forceLayout()
is lighter weight and will avoid recursion. (See notes in Section C.) forceLayout()
can also be used to force a remeasurement specific direct children when needed when they would not ordinarily be measured. forceLayout()
does not work alone and must be paired with appropriate calls to requestLayout()
这篇关于forceLayout() 在 Android 中是如何工作的的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!