Android中自定義ViewGroup最重要的就是onMeasure和onLayout方法,都需要重寫這兩個方法,ViewGroup繪製 的過程是這樣的:onMeasure → onLayout → DispatchDraw
其實我覺得官方文檔解釋有大大的問題,剛開始一直很疑惑onMeasure和onLayout是什麼意思,看了很多資料後豁然開朗,總結如下
首先要知道ViewGroup是繼承View的,後面的解釋跟View有關。ViewGourp可以包含很多個View,View就是它的孩子,比如LinearLayout佈局是一個ViewGroup,在佈局內可以放TextEdit、ImageView等等常用的控件,這些叫子View,當然不限於這個固定的控件。
onMeasure → onLayout → DispatchDraw:onMeasure負責測量這個ViewGroup和子View的大小,onLayout負責設置子View的佈局,DispatchDraw就是真正畫上去了。
onMeasure
官方解釋:
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int width = MeasureSpec.getSize(widthMeasureSpec); //獲取ViewGroup寬度
- int height = MeasureSpec.getSize(heightMeasureSpec); //獲取ViewGroup高度
- setMeasuredDimension(width, height); //設置ViewGroup的寬高
- int childCount = getChildCount(); //獲得子View的個數,下面遍歷這些子View設置寬高
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- child.measure(viewWidth, viewHeight); //設置子View寬高
- }
- }
- protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
- boolean optical = isLayoutModeOptical(this);
- if (optical != isLayoutModeOptical(mParent)) {
- Insets insets = getOpticalInsets();
- int opticalWidth = insets.left + insets.right;
- int opticalHeight = insets.top + insets.bottom;
- measuredWidth += optical ? opticalWidth : -opticalWidth;
- measuredHeight += optical ? opticalHeight : -opticalHeight;
- }
- mMeasuredWidth = measuredWidth; //這就是保存到類變量
- mMeasuredHeight = measuredHeight;
- mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
- }
- public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
- <span style="white-space:pre"> </span>.........
- <span style="white-space:pre"> </span>// measure ourselves, this should set the measured dimension flag back
- onMeasure(widthMeasureSpec, heightMeasureSpec);
- <span style="white-space:pre"> </span>..........
- }
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
- getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
- }
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- int mTotalHeight = 0;
- // 當然,也是遍歷子View,每個都要告訴ViewGroup
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View childView = getChildAt(i);
- // 獲取在onMeasure中計算的視圖尺寸
- int measureHeight = childView.getMeasuredHeight();
- int measuredWidth = childView.getMeasuredWidth();
- childView.layout(left, mTotalHeight, measuredWidth, mTotalHeight + measureHeight);
- mTotalHeight += measureHeight;
- }
- }
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int width = MeasureSpec.getSize(widthMeasureSpec); //獲取真實width
- int height = MeasureSpec.getSize(heightMeasureSpec); //獲取真實height
- setMeasuredDimension(width, height); //設置ViewGroup的寬高
- for (int i = 0; i < getChildCount(); i++) {
- getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec); //遍歷孩子設置寬高
- }
- }
- public static class MeasureSpec {
- private static final int MODE_SHIFT = 30; //
- private static final int MODE_MASK = 0x3 << MODE_SHIFT;
- public static int getMode(int measureSpec) {
- return (measureSpec & MODE_MASK);
- }
- public static int getSize(int measureSpec) {
- return (measureSpec & ~MODE_MASK);
- }
- }
發現解析前後的值差很遠,再結合源代碼 widthMeasureSpec & ~ MODE_MASK,運算後剛好匹配得到width。運算方法:0x3=0011, 它向左移位30位,得到1100 0000 .....(1後面一共有30個0.) ~取反後就是0011 1111……(0後面有30個1). 上面的widthMeasureSpec是1073742304,轉換成二進制是 0100 0000 0000 0000 0000 0001 1110 0000,和前面那個 ~MODE_MASK &之後(注意MODE_MASK要先取反再與widthMeasureSpec),最前面那個2個1就去掉了,widthMeasureSpec只留下了後面一段有1,即得到0000 …(省略16個0)… 0001 1110 0000,得到的值轉換成 十進制剛好是480,完美,轉換後得到了真實的width。手機的屏幕剛好是480*854,這是小米1的屏幕。