owned this note
owned this note
Published
Linked with GitHub
# View 執行順序
###### tags: `Android` `lifecycle` `view`
[link to custom view](https://hackmd.io/0POhTeIKRBukgamQuHDjxw?view)
# viewGroup lifecycle
## lifecycle
![](https://miro.medium.com/max/1400/0*cHmlPwPvhWJ15zU3)
可分為四大部分
連接分離、遍歷( measure → layout → draw )、狀態保存
### Attach/ detach
#### onAttachToWindow
將 view 和 window 做連接,讓 view 知道自己是 active 有 surface 可以繪製,可在此分配資源
#### onDetachedFromWindow
從 window 移除 view,已無 surface 可繪製,應釋放資源,並取消未完成的任務,當從 viewGroup 移除或 activity, fragment被銷毀時會觸發
#### onFinishInflate
當所有的子 view 都被加入後
### 遍歷
#### onMeasure,onLayout
[如何測量,擺放](https://hackmd.io/0POhTeIKRBukgamQuHDjxw?view#%E5%B0%BA%E5%AF%B8measure)
```kotlin=
<Textview
android:layoutHeight:"10dp"//給父view看的
android:text:"string/text"//給textview看得
```
### 修改已有的view尺寸
一樣可以用單個 view 和 viewGroup 去區分,單個 view 調用 onMeasure 去獲得原先測量的尺寸,計算成你要的尺寸後,再以 setMeasuredDimension 去存儲,onLayout 則是如何布局,單個 view 的時候用不到,是空方法
而 viewGroup 會以 view.measure 去調用各個子 view 的 onMeasure,而 onLayout 也是在此作用,在子 view 的 onMeasure 都計算完後,會將尺寸保存下來,並在 onLayout 決定試用保存的期望尺寸,還是以新計算的尺寸去布局
### 完全自己計算尺寸
重寫 onMeasure 不用 super.onMeasure
條件
計算出的尺寸滿足父 view 的限制( onMeasure的建構子
```kotlin=
//要滿足父 view 的限制,可調用 resolveSize()
meausreWidth = resolveSize(meausreWidth, widthMeasureSpec)
meausreHeight = resolveSize(meausreHeight, heightMeasureSpec)
setMeasuredDimension(meausreWidth, meausreHeight)
```
resolveSize() 會將父 view 傳入的尺寸限制拆成 mode 和 size,mode 分三種
```kotlin=
MeasureSpec.UNSPECIFIC
MeasureSpec.AT_MOST
MeasureSpec.EXACTLY
```
### 完全自己處理尺寸和布局
layout 或是 viewGroup 的自定義更加特別一點,有 onMeasure() 和 onLayout() 兩個步驟要完成
onMeasure -> 計算子 view 的尺寸在計算自己的尺寸
onLayout -> 擺放子 view
```kotlin=
class CustomViewPratice @JvmOverloads constructor(
context: Context, attrs:AttributeSet
): ViewGroup(context, attrs) {
...
}
```
#### override onMeasure
1. 調用每個子 view 的 measure ,讓子 view 自我測量
將在 xml 裡 layout 開頭的參數和剩餘可用空間做計算,這裡有兩種可能,一是開發者的要求,二是剩餘可用空間,而開發者的要求在地位上是比剩餘可用空間高的
運行時會一次查看每個子 view 的 width 和 height,並結合可用框度和高度,而可以透過 getLayoutParams() 獲得開發者對 view 的尺寸要求值
```kotlin=
//layoutParams.width對應到layout_width
//layoutParams.height對應到layout_height
class FLayout : ViewGroup {
override fun onMeasure(widthMeasureSpec, heightMeasureSpec){
for (i in (0..childCount)) {
val childView = getChildAt(i)
val layoutParams = childView.layoutParams
}
}
}
```
2. 根據子 view 測量的尺寸獲得子 view 的位置,並保存他們的尺寸和位置
保存起來是因為 onLayout 真正在擺放的時候會用到,現在還只是測量
```kotlin=
//兩個建構子即是可用空間,但不會是最後的尺寸
//measureSpec也分三種
//Exactly /At_Most-> viewGroup被設定了一個固定的數值,最高就是那個數值,ex.500dp
//UnSpecified -> 無限大
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
for (i in (0..childCount)) {
val childView = getChildAt(i)
val lP = childView.layoutParams
val selfMode = MeasureSpec.getMode(widthMeasureSpec)
val size = MeasureSpec.getSize(widthMeasureSpec)
var usedWidth = 0
usedWidth += lP.width
when(lP.width){
LayoutParams.MATCH_PARENT -> {
if (selfMode == MeasureSpec.EXACTLY || selfMode == MeasureSpec.AT_MOST){
childViewSpec = MeasureSpec.makeMeasureSpec(size - usedWidth , MeasureSpec.EXACTLY)
} else {
//無限大不能被填滿,所以把unspecified傳下去
childViewSpec = MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED)
}
break
}
//不能超過父view的邊界
LayoutParams.WRAP_CONTENT -> {
if (selfMode == MeasureSpec.EXACTLY || selfMode == MeasureSpec.AT_MOST){
//會改成at most是因為雖然子view是自己測量的,但是有不能超過父view範圍的限制在
childViewSpec = MeasureSpec.makeMeasureSpec(size - usedWidth , MeasureSpec.AT_MOST)
} else {
//無限大不能被填滿,所以把unspecified傳下去
childViewSpec = MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED)
}
break
}
else -> {
//傳入size和mode,會return壓縮後的尺寸限制
childViewSpec = MeasureSpec.makeMeasureSpec(lP.width, MeasureSpec.EXACTLY)
break
}
}
}
}
```
補充,並不是所有的 layout 都需要保存子 view 位置,ex.linearLayout 可以再擺設時累加計算
再補充,有時會測量不只一次 measureSpec,比如 linear Layout 是 wrap_content, 裡面的子 View 其中一個是match_parent,match_parent 的 view 會先給到限制,但在全部計算完之後,會再拿出來讓期縮短到第二寬 or 高的值
3. 根據子 view 的位置和尺寸,計算出自己的尺寸並用 setMeasureDimemsion() 保存
#### override onLayout()
一次把之前存下來的值,傳入各個 view 的 layout 方法裡面
```kotlin=
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
for (i in (0..childCount)){
val childView = getChildAt(i)
childView.layout(childView.left, childView.top, childView.right, childView.bottom)
}
}
```
![](https://i.imgur.com/fdWsJZl.jpg)
#### onDraw
[繪製](https://hackmd.io/0POhTeIKRBukgamQuHDjxw?view#%E7%B9%AA%E8%A3%BD%E9%A0%86%E5%BA%8F)
```kotlin=
override fun onDraw(canvas: Canvas?) {
//Code加在這,會在draw這個view 之前繪製
super.onDraw(canvas)
// 這個view繪製後,dispatchDraw前繪製
}
/**只有viewGroup有用到*/
override fun dispatchDraw(canvas: Canvas?) {
// 這個view繪製後,dispatchDraw前繪製
super.dispatchDraw(canvas)
//子view繪製後,才繪製這邊的code
}
```
滑動邊緣漸變和前景,這整個被封裝成 onDrawForeground,只能選擇在這個方法前或後加入東西,不能在這之中加入
![](https://i.imgur.com/jAA7fLs.jpg)
```kotlin=
override fun onDrawForeground(canvas: Canvas?) {
}
```
而上面全部都會被包在 draw 裡面
![](https://i.imgur.com/kf1yV1B.jpg)
```kotlin=
override fun draw(canvas: Canvas?) {
//在最開始繪製,背景也會蓋住他
super.draw(canvas)
//在最後面繪製,會蓋住所有東西
}
```
#### 繪製順序小節重點
在效率考量上,viewGroup 預設會跳過 draw() ,直接執行. dispatcherDraw() ,以簡化繪製流程
如果自定義了一個 viewGroup,且希望能執行完整的繪製流程,有可能需要呼叫 View.setWillNotDraw(false),而有些 viewGroup 預設就已經呼叫此方法, ex. ScrollView
如果繪製的程式碼,在不同的繪製方法中效果一樣,應優先寫在 onDraw() 裡面,因為底層有做過優化,可以在不需要重繪的時候自動跳過 onDraw()
#### invalidate
強制要求某一個 view 重新繪製,不會觸發測量和佈局,常見會用下面方式觸發
```kotlin=
//setter
var test:Int = 0
set(value) {
view?.invalidate()
field = value
}
//delegate
var testDelegate by Delegates.observable(0){ _,_,_ ->
view?.invalidate()
}
```
#### requestLayout
當狀態改變,需要重新測量和佈局時呼叫,一定要在 ui thread
## saveState
兩種做法
1. 繼承 BaseSaveState
```kotlin=
private class SavedState : BaseSavedState {
var amount: Int = 0
constructor(parcel: Parcel) : super(parcel) {
amount = parcel.readInt()
}
constructor (parcelable: Parcelable?) : super(parcelable)
override fun writeToParcel(parcel: Parcel, flags: Int) {
super.writeToParcel(parcel, flags)
parcel.writeInt(amount)
}
companion object CREATOR : Parcelable.Creator<SavedState> {
override fun createFromParcel(parcel: Parcel): SavedState {
return SavedState(parcel)
}
override fun newArray(size: Int): Array<SavedState?> {
return arrayOfNulls(size)
}
}
}
```
2. 複寫 onSaveInstance, onRestoreInstance
```kotlin=
init { isSaveEnabled = true }
...
override fun onSaveInstanceState(): Parcelable {
val bundle = Bundle()
bundle.putInt("amount", amount)
bundle.putParcelable("superState", super.onSaveInstanceState())
return bundle
}
override fun onRestoreInstanceState(state: Parcelable) {
var viewState = state
if (viewState is Bundle) {
amount = viewState.getInt("amount", 0)
viewState = viewState.getParcelable("superState")
}
super.onRestoreInstanceState(viewState)
}
```
繪製順序和佈局是參考這邊學的
作者:扔物线
链接:https://juejin.cn/post/6844903491031269383
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。