1. 程式人生 > 實用技巧 >android: View的生命週期

android: View的生命週期

今天看到了一篇不錯的文章,是一位外國小哥寫的,個人覺得不錯,遂翻譯之,英文好的同學可以直接移步 ——> 生肉:https://proandroiddev.com/the-life-cycle-of-a-view-in-android-6a2c4665b95e

概述

當我們檢視一款App的時候,首先引起我們注意的就是螢幕上顯示的內容,而螢幕上顯示的內容就是View

View是UI介面的基本構建塊,它佔據了一塊矩形區域,負責繪圖和事件處理。View同時也是android上其它UI控制元件的基類,可以用來建立其它互動式的UI元件(比如Button, TextView,等等)。View的子類ViewGroup

則是佈局的基類,ViewGroup是一個不可見的容器,用於容納其它的View(或其它的ViewGroup),而且還定義了相關的佈局屬性。

下圖描述了android上的View與其它UI元件之間的關係:

View的生命週期

每個Activity都有自己的生命週期,同樣,View也有自己的生命週期。在螢幕上渲染的View必須經歷這些生命週期方法才能正確地在螢幕上繪製。這些生命週期方法的每一種都有其重要性。下面我們來深入瞭解下View的生命週期:

建構函式

通常,我們對於View為什麼有四種類型的建構函式感到困惑:

View(Context context) 
View(Context context, @Nullable AttributeSet attrs) 
View(Context context, @Nullable AttributeSet attrs, 
int defStyleAttr) View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)

View(Context context)

當從程式碼動態的建立View時就會用到這個簡易的構造方法。這裡的引數context是執行檢視的上下文,通過它可以訪問到當前的主題(theme),資源(resources)等東西。

View(Context context, @Nullable AttributeSet attrs)

從XML佈局里加載View時呼叫的構造方法。當從XML檔案裡建立View同時也為這個View指定了一些相應的屬性時,就會呼叫此方法。這個版本的建構函式使用的預設樣式是0,因此唯一應用的屬性值是上下文主題和給定AttrubiteSet中的屬性值。

View(Context context, @Nullable AttributeSet attrs, int defStyleAttr)

從XML檔案執行載入並從主題屬性(defStyleAttr)中應用基本樣式。View的這個構造方法允許View在載入時使用它自己定義的基本樣式。比如,系統Button類的建構函式就呼叫了這個建構函式,並且為defStyleAttr 這個引數提供R.attr.buttonStyle的樣式屬性;這樣可以使得系統提供的按鈕主題樣式可以修改所有的View(特別是View的背景),以及Button類的樣式屬性。

引數defStyleAttr是當前主題的一個屬性,其中包含了對樣式資源的引用,這個樣式資源為View的屬性提供了一個預設值,不查詢預設值可以將其設定為0。

View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)

從XML檔案執行載入,並從主題屬性或樣式資源中應用特定於類的基本樣式。View的這個構造方法允許View在載入時使用它自己的基本樣式,與上個構造方法類似。

引數defStyleRes是樣式資源的資源識別符號,為View提供預設值,僅當defStyleAttr為0或在主題中找不到時使用。如果不查詢預設值,則可以為0。

View的生命週期主要由三部分組成:

Attachment / Detachment (關聯/分離)

Traversals(遍歷)

State Save / Restore (狀態儲存/恢復)

一. Attachment / Detachment

這是將View關聯到視窗(Window)或從視窗分離時的階段。在此階段,我們有一些方法可以接收回調來執行適當的操作。

● onAttachedToWindow()

當View關聯到視窗時呼叫。在這個階段,View知道它處於活動的狀態並且具有可繪製的表面。因此,我們在此階段就可以開始分配任何資源或設定listeners。

●onDetachedFromWindow()

當View與視窗分離時將呼叫此方法。此時,View不再具有可繪製的表面。在此階段,你需要停止任何已執行的任務或清理任何已分配的資源。當我們在ViewGroup類上呼叫removeView()或者Activity被銷燬時將呼叫此方法。

●onFinishInflate()

當佈局載入器(LayoutInflater)從XML檔案裡完成了所有子View的載入時,將會呼叫此方法。

二. Traversals

之所以把此階段稱為“遍歷”,是因為View的檢視層次就像是從父節點(ViewGroup)到子節點的樹狀結構。因此,每個方法都是從父節點開始,一直遍歷執行到最後一個節點:

測量(Measure)階段和佈局(Layout)階段始終是一起發生。如上圖所示,這是一個順序的過程。

●onMeasure()

這一步用於測量View到底應該有多大。如果是ViewGroup,它會對它的每一個子View都呼叫測量,測量的結果可以幫助ViewGroup確定其自身的大小。

onMeasure(int widthMeasureSpec, int heightMeasureSpec)
@param widthMeasureSpec Horizontal space requirements as imposed by the parent
@param heightMeasureSpec Vertical space requirements as imposed by the parent

onMeasure()沒有返回值,由系統呼叫。

setMeasuredDimension()用於顯式地設定寬度(width)和高度(height)值。

●MeasureSpec

MeasureSpec類封裝了從父View傳遞到子View的佈局要求。每個MeasureSpec代表對寬度或高度的要求。MeasureSpec由大小(size)和模式(mode)組成,有三種可能的模式:

MeasureSpec.EXACTLY :父View已經為View確定了一個確切的尺寸。不管子View有多大,都會給子View一個最大限制。這個屬性給View設定了一個固定的寬度(比如為LinearLayout指定weight,或者是為View指定match_parent屬性)。

MeasureSpec.AT_MOST: 子View可以根據需要變化到它想要的大小。

MeasureSpec.UNSPECIFIED:父View沒有對子View新增任何約束,子View可以是任意大小。

●onLayout()

View在呼叫onMeasure()完成測量後即會呼叫此方法,目的是在於確定View在螢幕上的位置。

●onDraw()

尺寸(通過onMeasure)和位置(通過onLayout())已經由前面的步驟計算出來了,因此View在這個時候已經可以根據上述的尺寸和位置繪製自身。在onDraw(Canvas canvas)中,生成或更新的Canvas物件具有要傳送到GPU的 OpenGL- ES命令列表(displayList)。

注意:切勿在onDraw()方法中建立物件,因為這個方法會被多次呼叫

當特定的View的屬性發生變化時,還有另外兩個方法可以被使用,那就是:

●invalidate()

invalidate()方法用於強制重繪我們希望顯示更改的特定View。 簡單的說,如果View的外觀發生了變化,而我們又想看到這些變化,就可以呼叫invalite()。

●requestLayout()

在某些情況下,View的狀態會發生變化,requestLayout()可以向android的檢視系統發出訊號,告訴系統,View需要重新執行自身的測量和佈局階段(onMeasure()->onLayout()->onDraw())。簡單的說,當View的範圍發生變化時,我們可以呼叫此函式。

注意:在View上呼叫任何方法時,必須在UI執行緒上,如果你正在其它執行緒上工作,並且想要從該執行緒更新View的狀態,則應使用Handler。

三. State Save / Restore

●onSaveInstanceState()

要儲存檢視狀態,首先需要為其提供一個ID。如果你的檢視層級結構中有多個具有相同ID的View,則將儲存所有的狀態,為了避免這種情況,最好為View指定一個唯一的ID。

其次,你需要一個類來繼承View.BaseSavedState ,然後儲存其屬性。為了更好的理解,下面舉例說明:

●onRestoreInstanceState(Parcelable state)

在這裡我們需要重寫此方法,並從Parcelable讀取資料,然後根據Parcelable可用的資料編寫邏輯。

@Override
public Parcelable onSaveInstanceState() {
    Bundle bundle = new Bundle();
    // The vars you want to save - in this instance a string and a boolean
    String someString = "something";
    boolean someBoolean = true;
    State state = new State(super.onSaveInstanceState(), someString, someBoolean);
    bundle.putParcelable(State.STATE, state);
    return bundle;
}

@Override
public void onRestoreInstanceState(Parcelable state) {
    if (state instanceof Bundle) {
        Bundle bundle = (Bundle) state;
        State customViewState = (State) bundle.getParcelable(State.STATE);
        // The vars you saved - do whatever you want with them
        String someString = customViewState.getText();
        boolean someBoolean = customViewState.isSomethingShowing());
        super.onRestoreInstanceState(customViewState.getSuperState());
        return;
    }
    // Stops a bug with the wrong state being passed to the super
    super.onRestoreInstanceState(BaseSavedState.EMPTY_STATE); 
}

protected static class State extends BaseSavedState {
    protected static final String STATE = "YourCustomView.STATE";

    private final String someText;
    private final boolean somethingShowing;

    public State(Parcelable superState, String someText, boolean somethingShowing) {
        super(superState);
        this.someText = someText;
        this.somethingShowing = somethingShowing;
    }

    public String getText(){
        return this.someText;
    }

    public boolean isSomethingShowing(){
        return this.somethingShowing;
    }
}

<END>