Usage of forceLayout(), requestLayout() and invalidate()
To better understand answers provided by François BOURLIEUX and Dalvik I suggest you take a look at this awesome view lifecycle diagram by Arpit Mathur:
Why is requestLayout being called directly after invalidate
invalidate()
is used specifically for redrawing the content of your view. The redraw does not happen synchronously. Instead, it flags the region of your view as invalid so that it will be redrawn during the next render cycle.
requestLayout()
should be used when something within it has possibly changed its dimensions. In this case, the parent view and all other parents up the view hierarchy will need to readjust themselves via a layout pass.
If you are not doing anything to your view that would change its size, then you do not have to call requestLayout()
.
If you go back and look at the places in the code for TextView
where requestLayout()
is being called, it will be on methods where the view's bounds will be affected. For example, setPadding()
, setTypeface()
, setCompoundDrawables()
, etc.
So, when requestLayout()
is called, it should be paired with a call to invalidate to ensure that the entire view is redrawn.
How does forceLayout() work in Android
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:
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()
requestlayout, invalidate behaviour
See Simon's answer here and my comment to it. What I was been looking for was actually a callback triggered just after the layout process completed.
view.requestLayout - setting layout of a button programmatically
Setting the width and height by itself will not trigger a layout of the widget. Either a layout pass is already scheduled which will be true if your code is in the onCreate()
method of an activity or there is another piece of code that triggers the layout. For example, if, in addition to setting the width and height, you also set the scale type the method to set the scale type will trigger the layout.
From ImageView.java
/**
* Controls how the image should be resized or moved to match the size
* of this ImageView.
*
* @param scaleType The desired scaling mode.
*
* @attr ref android.R.styleable#ImageView_scaleType
*/
public void setScaleType(ScaleType scaleType) {
if (scaleType == null) {
throw new NullPointerException();
}
if (mScaleType != scaleType) {
mScaleType = scaleType;
requestLayout();
invalidate();
}
}
As you can see, setting the scale type triggers a request for a layout if the scale type has changed.
Here is a small app to test the concepts. In a nutshell, the size can be changed prior to the initial layout (in onCreate()), after the initial layout completed (in onGlobalLayout()) or via some user action (in onClick()). See the comments in the code regarding how the app works.
MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener,
ViewTreeObserver.OnGlobalLayoutListener {
private View theView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
theView = findViewById(R.id.view);
// If the following line execute, it is before the initial layout pass,
// so the new width will take effect during that layout pass.
// Comment and uncomment this line to see the effect.
// theView.getLayoutParams().width *= 2;
// Uncomment the following to change size after the initial layout.
theView.getViewTreeObserver().addOnGlobalLayoutListener(this);
}
@Override
public void onClick(View v) {
// theView.getLayoutParams().width *= 2;
// The layout pass has completed since the button is available to click.
// Here setting the width without requesting a layout will not change the
// width of the widget - at least not until there is a layout pass requested.
// Comment and uncomment this line to see the effect.
// theView.requestLayout();
}
@Override
public void onGlobalLayout() {
theView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
theView.getLayoutParams().width *= 2;
theView.requestLayout();
}
}
activity_main.xml
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<View
android:id="@+id/viewStatic"
android:layout_width="100dp"
android:layout_height="50dp"
android:layout_marginBottom="16dp"
android:background="@android:color/holo_red_light"
android:text="Hello World!"
app:layout_constraintBottom_toTopOf="@+id/view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<View
android:id="@+id/view"
android:layout_width="100dp"
android:layout_height="50dp"
android:background="@android:color/holo_red_light"
android:text="Hello World!"
app:layout_constraintBottom_toTopOf="@+id/button"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/viewStatic" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Make larger"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/view" />
</androidx.constraintlayout.widget.ConstraintLayout>
Related Topics
Googleservice Failed to Initialize
How to Properly Highlight Selected Item on Recyclerview
What Characters Allowed in File Names on Android
How to Change Android Minsdkversion in Flutter Project
Too Many Field References: 70613; Max Is 65536
How to Connect Android App to MySQL Database
Connecting to MySQL from Android with Jdbc
How to Escape Special Characters Like ' in SQLite in Android
Setting Global Styles for Views in Android
Finish an Activity from Another Activity
How to Get the Height and Width of the Android Navigation Bar Programmatically
Nullpointerexception:Println Needs a Message in Android
Counting Chars in Edittext Changed Listener
Android Studio: Gradle - Build Fails -- Execution Failed for Task ':Dexdebug'