今天看到了一篇不錯的文章,是一位外國小哥寫的,個人覺得不錯,遂翻譯之,英文好的同學可以直接移步 ——> 生肉: 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>