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>

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM