最近在作重慶掌廳的項目,發現使用ViewGroup
包裹的子view
,常常子view
的高度測量不許,爲了瞭解這個問題的本質緣由,簡單寫了幾個demo做爲演示,自定義View
是比較重要的,尤爲是佈局和測量流程
測量和佈局的方法實際上是比較少的,一個是 onMeasure
,一個是 onLayout
,這個是佈局的核心所在。
下面咱們就開始佈局過程:java
view
的位置和尺寸 通俗來講就是:計算每一個 view
的尺寸以及相對於他們的父view
的相對位置,目的是幫助咱們寫佈局文件的難。ide
View
繪製和觸摸反饋作支持1. 測量過程:
從根 View
遞歸調用每個子View
的 measure()
方法。讓每一個子view
進行自我測量,每次測量以後,它會算出每一個子view
,都會算出本身的指望尺寸局,根據本身指望的尺寸得出子view,實際尺寸。佈局
2. 佈局過程:
從根 View
遞歸調用到每一級的 layout()
方法,把測量過程得出的子view
位置和尺寸傳給子 view
,子view
保存。ui
3. 爲何要有兩個流程呢?
主要是測量過程比較複雜,可能測量某個子view
,直接獲得它的位置和尺寸,可是有的時候,你可能須要測量屢次,你一次不行可能須要測量屢次。spa
運行前,開發者在 xml
文件寫入對View
的佈局要求layout_xxx
。code
父 view
在本身的onMeasure
中,根據開發者在xml
中寫對子view
的要求,和本身的可用空間,得出子view的具體尺寸要求。xml
子view
在本身的onMeasure()
中根據本身的父view
的特性算出本身的指望值。遞歸
viewGroup
,還會在這裏調用每一個子view
的measure()
進行測量。父view
在子view
計算出指望尺寸後,得出子view
的實際尺寸和位置。
開發
子view
在本身的layout()
方法中,將view
傳進本身的實際尺寸和位置保存。get
viewGroup
,還會在onLayout
裏面每一個字view
的onLayut()
把它們尺寸位置傳給它們。extend
已有的view
,簡單修改他們的尺寸public class SquareImageView extends AppCompatImageView {
public SquareImageView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
/** 1. 重寫`onMeasure()` */
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 2. ⽤ getMeasuredWidth() 和 getMeasuredSize() 獲取到測量出的尺寸
int measuredWidth = getMeasuredWidth();
int measuredHeight = getMeasuredHeight();
// 3. 計算最終的尺寸
int size = Math.max(measuredWidth, measuredHeight);
// 4.用 setMeasuredDimension 把最終的結果保存起來
setMeasuredDimension(size, size);
}
}
複製代碼
須要注意的是在自定義view
裏面是不能用 onLayout
去獲取 view
寬高的,由於父view
可能會被測量屢次,拿到的寬高並不必定是真實的寬高。
咱們須要獲取的是測量階段算出來的view
的寬高,getWidth
和getHeight
其實是在layout
裏面獲取的寬高。即右 - 左邊距;那個實際的寬度和高度
通常狀況下,測量的結果都同樣,你的寬度和高度在測量過程是拿不到的,還沒佈局,因此保險起見只能用getMeasureWidth
和getMeasureHeight
。就至關於你論文的初稿。真正定稿是getWidth
和getHeight
。
因此只要遵循一個原則,在繪製階段,只能用getMeasureWidth
和getMeasureHeight
,在觸摸反饋和佈局階段只能用getWidth
和getHeight
。
View
的尺寸onMeasure
resolveSize()
或 resolveSizeAndState()
修正結果public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
// 1.MeasureSpec.getMode(measureSpec) 和 MeasureSpec.getSize(measureSpec) 取出對本身的尺寸類型和具體尺寸
final int specMode = MeasureSpec.getMode(measureSpec);
final int specSize = MeasureSpec.getSize(measureSpec);
final int result;
switch (specMode) {
case MeasureSpec.AT_MOST:
// 若是measure spec 的model是 AT_MOST,表示view對子view的尺寸只限制了上限
if (specSize < size) {
// 若是計算出的size 不大於 spec 的size,而且在 resolveSizeAndState 會添加標誌 MEASURED_STATE_TOO_SMALL 這個能夠輔助父view的測量
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
// 若是模式是`EXACTLY`,表示父view對子view作出了精確的限制,因此就放棄計算的size直接選擇measureSpec的size
result = specSize;
break;
case MeasureSpec.UNSPECIFIED:
// 若是measure spec 的 model 是 UNSPECIFIED , 表示父 view 對 子 view 沒有任何尺寸限制,因此直接選用計算出來的size,忽略 spec 的 size
default:
result = size;
}
return result | (childMeasuredState & MEASURED_STATE_MASK);
}
複製代碼
setMeasureDimension(width,height)
保存結果@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int size = (int) ((PADDING + radius) * 2);
int measuredWidth = resolveSize(size, widthMeasureSpec);
int measuredHeight = resolveSize(size, heightMeasureSpec);
setMeasuredDimension(measuredWidth, measuredHeight);
}
複製代碼
Layout
: 重寫onMeasure()
和 onLayout()
: TagLayout
時間關係,明天總結~