Understanding View Lifecycle in Android

sunil kumar sahoo
8 min readMar 5, 2019

Let’sCreate Custom View to understand View Life cycle

1. Create a class “CustomView” that extends View

2. implement its Constructors

public class CustomView extends View {public CustomView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
}

How to provide customised ui for the the view ?

3. Override onDraw() to provide our own view drawing

@Override
protected void onDraw(Canvas canvas) {
Log.d(TAG, "inside onDraw -");
drawLabel(canvas);
}
private void drawLabel(Canvas canvas) {
float x = getPaddingLeft();
//the y coordinate marks the bottom of the text, so we need to factor in the height
Rect bounds = new Rect();
labelPaint.getTextBounds(labelText, 0, labelText.length(), bounds);
float y = getPaddingTop() + bounds.height();
canvas.drawText(labelText, x, y, labelPaint);
}

As of now My custom view code is

package com.sunilsahoo.viewlifecycle;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

public class CustomView extends View {

private static final String TAG = "CustomView";
private Paint labelPaint;
private String labelText;


public CustomView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}

@Override
protected void onDraw(Canvas canvas) {
Log.d(TAG, "inside onDraw -");
drawLabel(canvas);
}


private void init(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
labelText = a.getString(R.styleable.CustomView_text);
labelPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
labelPaint.setTextSize(70);
labelPaint.setColor(Color.WHITE);
labelPaint.setTextAlign(Paint.Align.LEFT);
labelPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
setSaveEnabled(true);
}

private void drawLabel(Canvas canvas) {
float x = getPaddingLeft();
//the y coordinate marks the bottom of the text, so we need to factor in the height
Rect bounds = new Rect();
labelPaint.getTextBounds(labelText, 0, labelText.length(), bounds);
float y = getPaddingTop() + bounds.height();
canvas.drawText(labelText, x, y, labelPaint);
}

public void setText(String text) {
labelText = text;
invalidate();
}

}

Let’s run the code. Here is my custom view which prints “View LifeCycle” like a textview.

As of now it looks good.

What about Orientation Change?

Now you can see that view Looses its state on orientation change i.e. “child updated:15517**” is updated to initial state i.e. “View LifeCycle”

4. Override onSaveInstanceState()

@Override
public Parcelable onSaveInstanceState() {
Log.d(TAG, "inside onSaveInstanceState -");
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.state = labelText;
return ss;
}

5. Override onRestoreInstanceState(Parcelable state)

@Override
public void onRestoreInstanceState(Parcelable state) {
Log.d(TAG, "inside onRestoreInstanceState - " + state);
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
setCustomState(ss.state);
}

6. create static class that extends BaseSavedState

static class SavedState extends BaseSavedState {
String state;

SavedState(Parcelable superState) {
super(superState);
}

private SavedState(Parcel in) {
super(in);
state = in.readString();
}

@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeString(state);
}

public static final Parcelable.Creator<SavedState> CREATOR
= new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}

public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}

Now you can see that view state remain same on orientation change i.e. “child updated:1551786679673” remains same

Source code for the above custom view is

package com.sunilsahoo.viewlifecycle;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

public class CustomView extends View {

private static final String TAG = "CustomView";
private Paint labelPaint;
private String labelText;


public CustomView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}

@Override
public void layout(int l, int t, int r, int b) {
Log.d(TAG, "inside layout -");
super.layout(l, t, r, b);
}

@Override
protected void onDraw(Canvas canvas) {
Log.d(TAG, "inside onDraw -");
drawLabel(canvas);
}


private void init(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
labelText = a.getString(R.styleable.CustomView_text);
labelPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
labelPaint.setTextSize(70);
labelPaint.setColor(Color.WHITE);
labelPaint.setTextAlign(Paint.Align.LEFT);
labelPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
setSaveEnabled(true);
}

private void drawLabel(Canvas canvas) {
float x = getPaddingLeft();
//the y coordinate marks the bottom of the text, so we need to factor in the height
Rect bounds = new Rect();
labelPaint.getTextBounds(labelText, 0, labelText.length(), bounds);
float y = getPaddingTop() + bounds.height();
canvas.drawText(labelText, x, y, labelPaint);
}

public void setText(String text) {
labelText = text;
invalidate();
}

@Override
public Parcelable onSaveInstanceState() {
Log.d(TAG, "inside onSaveInstanceState -");
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.state = labelText;
return ss;
}

@Override
public void onRestoreInstanceState(Parcelable state) {
Log.d(TAG, "inside onRestoreInstanceState - " + state);
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
setCustomState(ss.state);
}

public void setCustomState(String customState) {
this.labelText = customState;
}

static class SavedState extends BaseSavedState {
String state;

SavedState(Parcelable superState) {
super(superState);
}

private SavedState(Parcel in) {
super(in);
state = in.readString();
}

@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeString(state);
}

public static final Parcelable.Creator<SavedState> CREATOR
= new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}

public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
}

activity_main

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/mainContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimaryDark"
android:onClick="updateView"
android:orientation="vertical"
tools:context=".MainActivity"
>

<com.sunilsahoo.viewlifecycle.CustomView
android:id="@+id/customview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:text="@string/app_name"
android:background="@color/colorAccent"
/>
</LinearLayout>

When I execute the above layout the screen looks like following

How to Provide custom width and height for the view dynamically?

7. Override onMeasure(), set desired width and height as dimension

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.d(TAG, "inside onMeasure -" + widthMeasureSpec + " : " + heightMeasureSpec);
setMeasuredDimension(800, 300);
}

The view looks like following with width 800 height 300

The above view starts from start of parent i.e with left margin and top margin as 0

How to change view alignment?

8. Override onLayout, call layout() with specified left, top, right, bottom coordinates.

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Log.d(TAG, "inside onLayout - "+changed+" left : "+left);
if (left == 0) {
layout(50, 100, right, bottom);
} else {
super.onLayout(changed, left, top, right, bottom);
}
}

Now the view looks like following with 50 and 100 as left and top margin from parent

package com.sunilsahoo.viewlifecycle;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

public class CustomView extends View {

private static final String TAG = "CustomView";
private Paint labelPaint;
private String labelText;


public CustomView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}

@Override
public void layout(int l, int t, int r, int b) {
Log.d(TAG, "inside layout -");
super.layout(l, t, r, b);
}

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Log.d(TAG, "inside onLayout - "+changed+" left : "+left);
if (left == 0) {
layout(50, 100, right, bottom);
} else {
super.onLayout(changed, left, top, right, bottom);
}
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.d(TAG, "inside onMeasure -" + widthMeasureSpec + " : " + heightMeasureSpec);
setMeasuredDimension(800, 300);
}

@Override
protected void onDraw(Canvas canvas) {
Log.d(TAG, "inside onDraw -");
drawLabel(canvas);
}


private void init(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
labelText = a.getString(R.styleable.CustomView_text);
labelPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
labelPaint.setTextSize(70);
labelPaint.setColor(Color.WHITE);
labelPaint.setTextAlign(Paint.Align.LEFT);
labelPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
setSaveEnabled(true);
}

private void drawLabel(Canvas canvas) {
float x = getPaddingLeft();
//the y coordinate marks the bottom of the text, so we need to factor in the height
Rect bounds = new Rect();
labelPaint.getTextBounds(labelText, 0, labelText.length(), bounds);
float y = getPaddingTop() + bounds.height();
canvas.drawText(labelText, x, y, labelPaint);
}

public void setText(String text) {
labelText = text;
invalidate();
}

@Override
public Parcelable onSaveInstanceState() {
Log.d(TAG, "inside onSaveInstanceState -");
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.state = labelText;
return ss;
}

@Override
public void onRestoreInstanceState(Parcelable state) {
Log.d(TAG, "inside onRestoreInstanceState - " + state);
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
setCustomState(ss.state);
}

public void setCustomState(String customState) {
this.labelText = customState;
}

static class SavedState extends BaseSavedState {
String state;

SavedState(Parcelable superState) {
super(superState);
}

private SavedState(Parcel in) {
super(in);
state = in.readString();
}

@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeString(state);
}

public static final Parcelable.Creator<SavedState> CREATOR
= new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}

public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
}

How to update View dynamically?

90

invalidate()

Calling invalidate() is done when you want to schedule a redraw of the view. It will result in onDraw being called eventually (soon, but not immediately). An example of when a custom view would call it is when a text or background color property has changed.

The view will be redrawn but the size will not change.

requestLayout()

If something about your view changes that will affect the size, then you should call requestLayout(). This will trigger onMeasure and onLayout not only for this view but all the way up the line for the parent views.

forceLayout()

When there is a requestLayout() that is called on a parent view group, it does not necessary need to remeasure and relayout its child views. However, if a child should be included in the remeasure and relayout, then you can call forceLayout() on the child. forceLayout() only works on a child if it occurs in conjunction with a requestLayout() on its direct parent.

Calling forceLayout() by itself will have no effect since it does not trigger a requestLayout() up the view tree.

What is View Life Cycle?

When view is created and visible for the first time. it calls onMeasure()-> onLayout()-> onDraw()

CustomView: === ADD VIEW ===
CustomView: inside onMeasure --2147482568 : -2147482064
CustomView: inside layout -
CustomView: inside onLayout - true
CustomView: inside onDraw -

When view is updated using invalidate(), It calls onDraw()

CustomView: === UPDATE VIEW USING invalidate()===
CustomView: inside onDraw -

When orientation is change, View calls onSaveInstanceState() -> onRestoreInstanceState() ->onMeasure()-> onLayout()-> onDraw()

CustomView: === ON ORIENTATION CHANGE ===
CustomView: inside onSaveInstanceState -
CustomView: inside onRestoreInstanceState -
CustomView: inside onMeasure --2147481981 : -2147482821
CustomView: inside onLayout - true
CustomView: inside onDraw -

Github url for source code

Feel free to comment, clap and follow me on github and medium

--

--