直接繼承View來自定義控制元件時,需要重寫onMeasure()方法並設定wrap_content時的大小 原理分析
之前在校學習的時候,一直沒有在網上找到比較靠譜的解釋,現在畢業了,程式設計能力也比之前有了不小的提高,就讀了一些原始碼,加上一些書上的解釋,現在算是大體知道原因了吧!如果哪裡說的不對,歡迎批評指正。
在開始本篇的正文之前,請允許我先粗略的解釋一下MeasureSpec的作用,對本篇的理解會有幫助,但是關於View繪製的流程,本篇暫時不多做介紹了,對View的繪製流程還不是很熟悉的同學,請先通過一些書籍或者其他的部落格瞭解一下View繪製的流程。後期有時間的時候,我會整理一下View繪製的流程,然後發一篇部落格,雖然現在網上相關的內容也有不少,但是看了許多之後,自身感受就是要麼部落格寫的千篇一律,要麼就是涵蓋不全(雖然我是個渣渣,但是寶寶會努力的
MeasureSpec可以理解成是View測量的說明書吧,一個View的MeasureSpce受到本身的LayoutParams以及父View的MeasureSpec的影響。 MeasureSpce裡有兩個比較重要的屬性SpecMode和SpecSize,SpecMode可以理解成是View的測量模式,SpecSize代表的是View的測量大小。MeasureSpec很大程度上影響了一個View尺寸。
其中SpecMode有三類測量模式:
(1)UNSPECIFIED: 這個模式平時用的貌似不太多,查了一下資料,表示父容器不對View有任何限制,一般用於系統內部,其他的也就不再多提了。
(2)EXACTLY:精確模式,此時View的大小就是SpecSize的值,對應match_parent和具體的數值這兩種。
(3)AT_MOST:最大化模式,此時View的大小不能超過SpecSize的大小,對應於wrap_content。
好了下面開始正文:為什麼在直接繼承View來自定義控制元件時,需要重寫onMeasure()方法並設定wrap_content的大小
我們應該都會了解過,View繪製的流程是從父View傳遞到子View的,對於ViewGroup來說,除了完成自己的measure過程之外,還要完成其所有子View的measure過程,因為對於不同的子View來說,測量的細節也是不相同的,ViewGroup不能夠針對不同的子View做統一的measure,所以ViewGroup是一個抽象的類,把測量的過程交給了子View本身去測量,在ViewGroup中有一個measureChildren()方法來遍歷所有的子View,執行子View的measure來進行子View的測量。以下是measureChildren()方法的程式碼:
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
我們可以看到在measureChildren()方法中,去遍歷了每一個子View,並通過measureChild()方法來進行對子View的測量,以下是measureChild()方法的程式碼:
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
我們可以看到,在measureChild()方法中,呼叫了getChildMeasureSpec()方法來獲取width和height對應的MeasureSpec(稍後會再對getChildMeasureSpec()方法做介紹,在這個地方請先忽略,只知道是獲取MeasureSpec的方法就行了。),然後作為引數,呼叫子View的measure()方法,即child.measure(childWidthMeasureSpec , childHeightMeasureSpec)方法來進行子View的measure過程,以下是該方法的程式碼:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int oWidth = insets.left + insets.right;
int oHeight = insets.top + insets.bottom;
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}
// Suppress sign extension for the low bytes
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
// Optimize layout by avoiding an extra EXACTLY pass when the view is
// already measured as the correct size. In API 23 and below, this
// extra pass is required to make LinearLayout re-distribute weight.
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
if (forceLayout || needsLayout) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
該程式碼相對比較多,我們只看比較重要的部分,由以上程式碼我們可以看出measure()方法為final型別的方法,因此不被重寫,我們可以看到在以上程式碼的第38行,呼叫了onMeasure()方法來進行View的測量,下面是onMeasure()方法的相關程式碼:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
可以看到,在onMeasure()方法中呼叫了setMeasureDimension()方法,看這個方法的名字就就可以知道,setMeasureDimension()方法的作用是設定View測量後width和height的大小,這個方法的程式碼就不貼出來了,我們可以看到該方法的有兩個引數,兩個引數分別對代表View測量的width和height,兩個引數的值都是getDefaultSize()方法的返回值,下面我們來看下getDefaultSize()方法的原始碼:
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
在getDefaultSize()中,首先根據傳遞過來的MeasureSpec,來獲取對應的SpecMode和SpecSize,我們重點看下SpecMode的AT_MOST和EXACTLY模式,當View的SpecMode處於AT_MOST和EXACTLY這兩種模式下的時候,其返回值result都是SpecSize的值。(結論1)
到這裡,我先總結一下以上的流程:ViewGroup會以自身的MeasureSpec為引數,呼叫measureChildren()方法,遍歷每一個子View,並以子View和自身的MeasureSpec為引數,呼叫measureChild()方法,在measureChild()中,會以自身的MeasureSpec為引數,呼叫getChildMeasureSpec()方法來獲取子View的MeasureSpec,然後以獲得的子View的MeasureSpec為引數,呼叫子View的measure()方法來進行子View的測量。在子View的measure()方法中,會去呼叫onMeasure()進行View的測量,在onMeasure()中會呼叫setMeasureDimension()方法來設定View測量的寬和高,該寬和高的值為getDefaultSize()方法的返回值,在getDefaultSize()方法中,當子View的MeasureSpec的specMode為AT_MOST和EXACTLY時,getDefaultSize()方法的返回值是子View的MeasureSpec中SpecSize的值。
下面我們來看一下上文中提到但是沒有解釋的getChildMeasureSpec()方法。以下是該方法的程式碼:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
注意,該方法的第一個引數spec,即MeasureSpec,是父View的MeasureSpec,最後一個引數,childDimension對應的是wrap_content或者是match_parent或者是固定的值。
在該方法中,首先會獲得父View的MeasureSpec中對應的specMode和specSize,然後對父View的specMode做判斷,根據程式碼我們可以得出結論:不論父View的specMode是EXACTLY模式,還是AT_MOST模式,當childDimension == WRAP_CONTENT時,即我們設定View的layout_width或者layout_height為wrap_content時,最後得到的resultMode的結果都是AT_MOST模式,resultSize的值等於size的值,而size的值,請看上述程式碼的第5行,size = Math.max(0 , specSize - padding),即size的值是父View的specSize的值減去padding,也就是說,size的值是父View去掉padding之後剩餘空間的值,總結一下就是:當我們對View設定wrap_content的時候,最終獲得的View的MeasureSpec的值中,SpecMode的值是AT_MOST,SpecSize值是父View剩餘去掉padding之後剩餘空間的值,而從上述程式碼我們也可以看出,當childDimension == MATCH_PARENT時,最終獲得的SpecMode的值可能是AT_MOST,也可能是EXACTLY,而SpecSize的值都是size,也就是父View去掉padding之後剩餘空間的值,也就是說,不管我們對View設定為wrap_content還是match_parent,最終我們把子View的MeasureSpec傳遞到getDefaultSize()中之後,得到最後的測量後的大小,都是父View去掉padding之後剩餘空間的值,因為不管是wrap_content還是match_parent,最終得到的SpecMode不是AT_MOST就是EXACTLY。(結論2)(在這個地方有點暈的話請看結論1)。
因此,現在我們可以得出結論,在我們直接繼承View來自定義控制元件時,如果不重新onMeasure()方法,不對wrap_content做處理的話,最終對控制元件設定wrap_content和match_parent後,得到效果是一樣的。所以我們需要重寫onMeasure)()方法,設定wrap_content時的預設大小。
相關推薦
直接繼承View來自定義控制元件時,需要重寫onMeasure()方法並設定wrap_content時的大小 原理分析
之前在校學習的時候,一直沒有在網上找到比較靠譜的解釋,現在畢業了,程式設計能力也比之前有了不小的提高,就讀了一些原始碼,加上一些書上的解釋,現在算是大體知道原因了吧!如果哪裡說的不對,歡迎批評指正。 在開始本篇的正文之前,請允許我先粗略的解釋一
Android自定義控制元件系列:詳解onMeasure()方法中如何測量一個控制元件尺寸(一)
轉載請註明出處:http://blog.csdn.net/cyp331203/article/details/45027641 今天的任務就是詳細研究一下protected void onMeasure(int widthMeasureSpec, int he
繼承式自定義控制元件——滑動ScrollView,標題顏色漸變
MainActivity.java public class MainActivity extends AppCompatActivity { private ImageView mIvDetail; private ObservableScr
android 自定義控制元件邊框,顏色,線條,圓滑程度
1,在drawable資料夾中右鍵,new->drawableresource file,彈出一個視窗。 2,將selector改為shape,輸入,該xml的名字table_shape,點選確定,接下來就將原來的控制元件變成圓滑控制元件。(drawable/ tab
winform自定義控制元件之ComboBox簡單重寫
由於專案需要,現有的ComboBox控制元件滿足不了需求,需要重寫做一些小小的改變。要求ComboBox每一項前增加圖片顯示,使邊框顏色修改,及禁用滑鼠滾輪修改當前選項。 定義ComboBox選擇項類 using System; using System.Collectio
徹底搞懂自定義控制元件中的四個構造方法
在上一篇部落格動手實現餅圖控制元件寫完以後,有些小夥伴說講得不夠細,建議從最基本開始講起,比如建構函式都是什麼?我覺得說得很有道理,正好自己也不夠了解自定義控制元件中的4個構造方法的具體呼叫時機和它們各自的引數作用,今天終於有時間把這部分內容進行學習整理,順便分享給那
ASP.NET (VB) 載入使用者自定義控制元件 (ascx),提交會消失的解決方法
在ASP.NET裡動態新增自定義控制元件(ascx),按了Button控制元件,會消失;雖然用LoadControl放在IsPostBack外面,可以解決消失問題,但是要按2次Button,提交2次,才能把ascx裡的資料提交出去。 經過網上搜索,找到最終解決方法。 在
Android自定義控制元件BannerLayout,實現廣告輪播
Android自定義廣告輪播圖 自定義的BannerLayout,通過ViewPager來實現,配合Glide 實現本地以及網路圖片的載入。 效果: 專案結構: 在build.gradle中新增對Glide (圖片載入框架)的引用: compile 'c
[C#] (原創)一步一步教你自定義控制元件——02,ScrollBar(滾動條)
一、前言 技術沒有先進與落後,只有合適與不合適。 本篇的自定義控制元件是:滾動條(ScollBar)。 我們可以在網上看到很多自定義的滾動條控制元件,它們大都是使用UserControl去做,即至少使用一個Panel或其它控制元件作滑塊,使用UserControl本身或另一個控制元件作為背景條,而有的複雜的還
[C#] (原創)一步一步教你自定義控制元件——03,SwitchButton(開關按鈕)
一、前言 技術沒有先進與落後,只有合適與不合適。 本篇的自定義控制元件是:開關按鈕(SwitchButton)。 開關按鈕非常簡單,實現方式也多種多樣,比如常見的:使用兩張不同的按鈕圖片,代表開和關,然後在點選時切換這兩張圖片。 而本篇和前兩篇一脈相承,都是繼承Control,使用GDI+去實現。因為都是相同
[C#] (原創)一步一步教你自定義控制元件——04,ProgressBar(進度條)
一、前言 技術沒有先進與落後,只有合適與不合適。 本篇的自定義控制元件是:進度條(ProgressBar)。 進度條的實現方式多種多樣,主流的方式有:使用多張圖片去實現、使用1個或2個Panel放到UserControl上去實現、過載系統進度條去實現等等。 本次所實現的進度條仍是使用GDI+去實現。當然,如果
[C#] (原創)一步一步教你自定義控制元件——05,Label(原生控制元件)
一、前言 技術沒有先進與落後,只有合適與不合適。 自定義控制元件可以分為三類: 一類是“無中生有”。就如之前文章中的的那些控制元件,都是繼承基類Control,來實現特定的功能效果; 一類是“有則改之”。是對原生控制元件的改造,以達到特定的功能效果; 一類是“使用者控制元件”。是將多個控制元件進行組合,以實現
[C#] (原創)一步一步教你自定義控制元件——06,MaskLayer(遮罩層)
一、前言 技術沒有先進與落後,只有合適與不合適。 本篇的自定義控制元件是:遮罩層(MaskLayer)。 遮罩層對軟體的美觀與易用性上的提高是很大的,在日常使用過程中也會經常看到各種遮罩層,雖然WinForm本身沒有原生的遮罩層控制元件,但實現起來並不麻煩。 遮罩層的實現方式一般有兩種:一種是基於自定義控制元
解讀Google官方SwipeRefreshLayout控制元件原始碼,帶你揭祕Android下拉重新整理的實現原理
前言 想必大家也發現,時下的很多App都應用了這個Google出品的SwipeRefreshLayout下拉重新整理控制元件,它以Material Design風格、適用場景廣泛,簡單易用等特性而獨步江湖。但在我們使用的過程中,不可避免地會發現一些bug,或者
object物件重寫equals方法時為什麼需要重寫hashCode方法
在Java語言中,equals方法在使用時: 針對包裝物件,比較的是物件的值(包括 boolean,byte,char,short,int,long,float,double) 針對String物件,比較的也是String的值(因為String內部重寫了e
Android 自定義控制元件之繼承view
一.自定義控制元件的型別: 1.繼承view(自繪檢視:view中的內容是我們自己繪製出來的,需要重寫onDraw方法) 2.繼承已有原生控制元件 3.自定義組合控制元件(將系統原生的控制元件組合到一起) 本
繼承自View的自定義控制元件的warp_content和padding屬性處理
繼承自view的自定義控制元件的wrap_content和padding兩個屬性不會生效,解決方法/** * 自定義一個圓形 * @author luoshen * 2016年11月29日下午1:28:04 */ public class CircleView ex
Android自定義View--翻書控制元件(一)
0.前言 最近重看了一遍封神演義,感覺QQ閱讀那個翻書的效果挺好的,準備做一個。上週五下午用了兩個小時只寫了一部分功能,以後有時間再完善 1.分析 先看效果圖 這個空間,說簡單也簡單,說難也難,簡單就在於這個效果主要就是依賴canvas的clippath才見到部分canvas,難就難在裁
自定義控制元件01---簡單view的實現
對於每一個應用來說幾乎都會有一個Topbar,並且基本都是類似的那麼假如應用有好多個頁面的話,就要寫好多遍,可以在Topbar整合為一個控制元件來使用,針對於這個的學習,總結如下: 1 atts自定義屬性的定義 res–values-atts.xml <?xml versi
自定義控制元件View
先在佈局中寫 <com.luyao.dell.yuan.YurnTableView android:layout_width="match_parent" android:layout_height="match_parent" /> 然後在定義一個cl