21

In this answer I wrote

forceLayout()

Call forceLayout() if you only want to relayout your own view's content, but don't need to trigger remeasuring of the entire view tree (all the parent views). If you had a custom ViewGroup that wasn't changing its own size but needed to remeasure and relayout its children, this would be an appropriate situation to call forceLayout().

Basically, calling requestLayout() results in a call to parent.requestLayout(), but calling forceLayout() doesn't.

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);
    }
}
Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
  • `This looks like reasonable output` I'm a bit confused with that output, because as [docs](https://developer.android.com/guide/topics/ui/how-android-draws.html) tell: `The measuring pass is implemented in measure(int, int) and is a top-down traversal of the View tree`. Can you please clarify, why in this case it is a bottom-up traversal? – azizbekian Aug 30 '17 at 07:12
  • 1
    @azizbekian, I am guessing it is because I have the `Log` statements after the calls to `super` in the `onMeasure` methods. – Suragch Aug 30 '17 at 07:54
  • Exactly, now [logs are printed](http://i.imgur.com/GjehM41.png) in correct order. I think it makes sense to edit the question so that other readers do not get confused. – azizbekian Aug 30 '17 at 08:08
  • @azizbekian, OK, I edited the question with the reordered Log statements. – Suragch Aug 30 '17 at 11:13

1 Answers1

33

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.

enter image description here

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:

I/MainActivity: 1*******************************************
I/MainActivity: 2*******************************************
I/MainActivity: 3*******************************************
I/MainActivity: 4*******************************************
I/MainActivity: 5*******************************************
I/MainActivity: 6*******************************************
I/MainActivity: 7*******************************************
I/MyChildView: onDraw called (1)

"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:

/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 called
I/measure: ViewGroupParent (null)
I/ViewGroupParent: onMeasure called
I/measure: MyChildView (1)
I/MyChildView: onMeasure called (1)
I/measure: MyChildView (2)
I/ViewGroupGrandparent: onLayout called
I/ViewGroupParent: onLayout called
I/MyChildView: onLayout called (1)
I/MyChildView: onDraw called (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 called
I/measure: ViewGroupParent (null)
I/ViewGroupParent: onMeasure called
I/measure: MyChildView (1)
I/MyChildView: onMeasure called (1)
I/measure: MyChildView (2)
I/ViewGroupGrandparent: onLayout called
I/ViewGroupParent: onLayout called
I/MyChildView: onLayout called (1)
I/MyChildView: onDraw called (1)

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:

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 called (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 called (1)
I/MainActivity: 14*******************************************
I/requestLayout: MyChildView (1)
I/requestLayout: MyChildView isLayoutRequested=true (1)
I/MyChildView: onDraw called (1)
I/MainActivity: 15*******************************************
I/requestLayout: MyChildView (1)
I/requestLayout: MyChildView isLayoutRequested=true (1)
I/MyChildView: onDraw called (1)

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:

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 called
I/measure: ViewGroupParent (null)
I/ViewGroupParent: onMeasure called
I/measure: MyChildView (1)
I/measure: MyChildView (2)
I/ViewGroupGrandparent: onLayout called
I/ViewGroupParent: onLayout called
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 called
I/measure: ViewGroupParent (null)
I/ViewGroupParent: onMeasure called
I/measure: MyChildView (1)
I/MyChildView: onMeasure called (1)
I/measure: MyChildView (2)
I/ViewGroupGrandparent: onLayout called
I/ViewGroupParent: onLayout called
I/MyChildView: onLayout called (1)
I/MyChildView: onDraw called (1)

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:

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 called
I/measure: ViewGroupParent (null)
I/ViewGroupGrandparent: onLayout called
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 called
I/measure: ViewGroupParent (null)
I/ViewGroupGrandparent: onLayout called
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 called
I/measure: ViewGroupParent (null)
I/ViewGroupParent: onMeasure called
I/measure: MyChildView (1)
I/measure: MyChildView (2)
I/ViewGroupGrandparent: onLayout called
I/ViewGroupParent: onLayout called
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 called
I/measure: ViewGroupParent (null)
I/ViewGroupParent: onMeasure called
I/measure: MyChildView (1)
I/MyChildView: onMeasure called (1)
I/measure: MyChildView (2)
I/ViewGroupGrandparent: onLayout called
I/ViewGroupParent: onLayout called
I/MyChildView: onLayout called (1)
I/MyChildView: onDraw called (1)

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()

Cheticamp
  • 61,413
  • 10
  • 78
  • 131
  • Very thorough analysis! – Suragch Aug 31 '17 at 01:06
  • Let me see if I am understanding correctly: `forceLayout()` does not actually initiate any sort of layout pass itself, but if it is called on a view, then that view will be included in the parent's layout pass (if the parent happens to have one). Is that correct? – Suragch Aug 31 '17 at 01:15
  • @Suragch Yes, that's it. With TableLayout, `forceLayout()` is called on the children and TableLayout initiates the `requestLayout()` chain that bubbles up the hierarchy. When the layout pass is initiated as a result, the children of TableLayout are included. Without the call to `forceLayout()` they would have not been included. I have not tested this with TableLayout, but it makes sense with what I know. Another poster I read likens this to a *nix `touch` command - don't do anything now but include me when its time and the time will be right now and not some vague future time. – Cheticamp Aug 31 '17 at 01:23
  • @Suragch It seems like the names are backward: `forceLayout()` should be `requestLayout()` and vice versa. – Cheticamp Aug 31 '17 at 01:24
  • In my test project (and your derived project) calling `requestLayout()` on the parent does not result in an `onMeasure` or `onLayout` on the child. However, in a real world parent (ie, `ViewGroup`), it would result in `onMeasure` and `onLayout` because it is the responsibility of the parent to measure and layout its children. I'm still trying to grasp when I would actually use `forceLayout()` in my custom views. I can't think of a practical example and I'm having trouble understanding why TableView and ListView needed to include their children in the layout pass. – Suragch Aug 31 '17 at 01:33
  • On a side note, I now realize that what I originally ascribed to `forceLayout()` (ie, layout children without remeasuring the entire view tree all the way up the line) is done by just calling `measure` and `layout` directly on the children. – Suragch Aug 31 '17 at 01:50
  • 1
    @Suragch Off the top of my head, I will say that when a parent changes (layout parameters, etc), it could effect its children. `forceLayout()` causes a view to clear its cache of past measurements. If the view's cache is cleared, `onMeasure()` is called since the cache is empty; otherwise, the cache is used and `onMeasure()` has nothing to do. If you take a look at the logs, I think that you will see `measure()` called when `onMeasure()` is not. I will take another look, but I think that's right. I hope that helps. This may also vary across revisions. I am looking at API 25. – Cheticamp Aug 31 '17 at 01:51
  • Sir, I'm delighted by your detailed answers. Keep up the good work! – azizbekian Aug 31 '17 at 07:49
  • 1
    @Suragch VIews measure themselves while parents set the stage. I have written a [sample app](https://github.com/Cheticamp/ForceLayout/tree/master/TableTest) that demonstrates why TableLayout calls forceLayout() on its children. See the comments in MainActivity.java for a description. I hope this helps to clear things up a bit. – Cheticamp Aug 31 '17 at 14:08
  • @Cheticamp, wow, you have gone way beyond the call of duty. Thank you. I will study it. I have to do a little bit of traveling and I am going to let the bounty finish out the week just to get the maximum views and comments. But this answer already is more than worthy of it. – Suragch Aug 31 '17 at 14:16
  • @Suragch No problem. It was an interesting question. – Cheticamp Aug 31 '17 at 14:19