對比自定義view和自定義viewgroup

前言

最近在研究view的整個事件過程慷暂,以及自定義view的繪制嚼锄,今天突然發(fā)現(xiàn)還有dispatchDraw()亡蓉,于是在官方api中找到了draw()方法录豺。這兩個方法分別用于view繪制和viewgroup繪制煤傍,通常我們實現(xiàn)更多的是view繪制盖文,而viewgroup繪制 默認是關(guān)閉狀態(tài),需要設(shè)置setWillNotDraw(false)蚯姆,什么意思呢五续?就是設(shè)置繪制為打開狀態(tài)。下面描述一下view定義以及viewgroup定義的流程龄恋,最后對比自定義view以及viewgroup有什么區(qū)別疙驾。

自定義view:

通常我們自定義一個view只需要先測量,然后確定要繪制的位置郭毕,位置確定了就可以開始繪制了它碎,測量只需要用onMeasure(), 設(shè)置view內(nèi)部元素(后面直接稱之為元素)的位置可以寫在onSizechange()方法里(盡管元素可以在onMeasure()方法中設(shè)置Rect 的left,top铣卡,right链韭,bottom來確定繪制的區(qū)域,但是onMeasure()方法主要功能是測量)煮落,繪制操作對應(yīng)onDraw()根據(jù)需要繪制元素來進行繪制,所以自定義一個view一般會涉及到onMeasure(),onDraw(),以及onSizeChange() ( 這個方法是計算過程中Size改變會回調(diào) )這幾個方法敞峭,當然自定義view通常也涉及事件處理,只需要重寫onTouchEvent()蝉仇,下面我介紹下我對這幾個方法的理解:

1. onMeasure()
顧名思義這是一個測量方法旋讹,用來測量view的大小,如果不重寫這個方法轿衔,該view的父view會在onMeasure()方法中調(diào)用

  setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), 
  widthMeasureSpec)
  getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));

這是系統(tǒng)給的默認測量方式沉迹,并且默認方式的測量還和背景圖片有關(guān),如果背景足夠大的話是會影響view的大小。當然我們也可以根據(jù)自己的需要去重寫setMeasuredDimension(widthsize,heightsize)害驹,測量方式通常由自己設(shè)置的LayoutParams和外層父ViewGroup的測量模式共同決定鞭呕,至于為什么和父布局有關(guān),這是因為viewGroup會被內(nèi)部view提供測量模式宛官,后面會在ViewGroup的measure()方法中會提到葫松。

protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); }

我們在使用的時候可以根據(jù)自定義的邏輯要求瓦糕,對其中的bitmap,text等等寬高進行計算,來確定最終要傳給
setMeasuredDimension(widthsize,heightsize)的寬高腋么。

2. onDraw()
打一個比方咕娄,自定義view其實就像畫畫,需要畫布珊擂,畫筆圣勒,確定畫的位置。 onDraw()扮演著畫的動作摧扇,通常我們需要重寫這個方法圣贸,因為父view的onDraw()方法是個空方法當然它只提供畫布canvas,具體要畫什么圖案扛稽,畫多大旁趟,以及用什么樣的畫筆,不建議在這個方法做初始化工作庇绽,因為調(diào)用invalidate()方法會使這個方法重繪,我們可以在自定義view的構(gòu)造函數(shù)寫一個init()方法橙困,用來實現(xiàn)畫筆及一些繪制工作需要用到的“工具”的初始化工作瞧掺,在onMeasure()中測量畫布大小,onSizeChange()里確定我們畫的圖案位置(Rect)凡傅,最后我們只要在canvas.drawbitmap(), canvas.drawcircle()…..等等可以畫出我們想繪制的圖案辟狈。

/***Implement this to do your drawing. 
  * * @param canvas the canvas on which the background will be drawn */ 
       protected void onDraw(Canvas canvas) { }
自定義viewGroup:

同樣自定義嘛,還是老規(guī)矩夏跷,先測量再確定布局哼转,這里確定布局和view的確定布局不是一個意思,view確定布局是給view要繪制的元素確定繪制“地點”槽华,而viewgroup確定布局的意思是給內(nèi)部子view設(shè)置layout壹蔓,然后再去繪制,因為view是單獨存在的猫态,不存在子view只存在內(nèi)部需要繪制的元素佣蓉,所以view和viewgroup的概念是不一樣的。而viewGroup可能存在多個子view亲雪,所以需要給每個子view來指定具體位置勇凭,這就是onLayout()所做的事了,同樣义辕,測量也是用到onMeasure()虾标,但是它和子view的onMeasure()不同,子view的測量只要測量這個view按照測量法則需要多大的空間灌砖,而viewgroup的測量則是根據(jù)子view的測量結(jié)果來進行統(tǒng)計璧函,最終來確定整個viewgroup的widthsize和heightsize傀蚌。viewgroup的繪制會用到dispatchDraw(),它主要是分發(fā)給子組件進行繪制柳譬,調(diào)用子組件的draw()方法喳张,通常viewgroup是不用去繪制的,而且繪制功能默認關(guān)閉美澳。

1. onMeasure()
這里用到了measureChildren()方法销部,這個方法是對所有的子view進行測量。

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 
 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 
 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 
 measureChildren(widthMeasureSpec, heightMeasureSpec);
 .....此處省略
 setMeasuredDimension(widthSize,heightSize);
} 

/** *這是一個遍歷去測量子組件的方法制跟,主要子組件不是Gone類型都會被測量 */

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); 
    }
  }
}

接下來我們看下measureChild()這個方法舅桩,顯然是去測量子組件的大小的。官方文檔的代碼如下:

protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
 //獲取子組件的LayoutParams 
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); 
final int childHeightMeasureSpec =   getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); 
// 調(diào)用子組件的measure()方法 
child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }

對于getChildMeasureSpec()這個方法雨膨,就是根據(jù)父類的測量法則和 子組件的LayoutParams 來確定子組件的測量法則擂涛,然后傳給子組件,其實就是上面介紹 onMeasure(int widthMeasureSpec, int heightMeasureSpec)的兩個參數(shù)聊记,因為child.measure()這個方法最終是會調(diào)用view.onMeasure()的撒妈。

2. onLayout()

這是一個抽象方法,如果繼承了viewGroup就需要去實現(xiàn)排监,上面也簡單的介紹了狰右,其實這個方法就是確定子組件在viewGroup的位置,并且是通過遍歷所有的childview,然后通過childview.layout()這個方法來給子組件確定position具體是通過setFrame(l, t, r, b)來確定左上右下.下面是一個onLayout()方法的重寫舆床。

//這個方法是我在看了鴻洋大神的博客寫的一個例子
 @Override protected void onLayout(boolean changed, int l, int t, int r, int b) {
 int cCount = getChildCount(); 
 int cHeight, cWidth; MarginLayoutParams cParams; 
 /** * 遍歷所有childView根據(jù)其寬和高棋蚌,以及margin進行布局 */
 for (int i = 0; i < cCount; i++) 
{ 
 View childView = getChildAt(i);
 cWidth = childView.getMeasuredWidth();
 cHeight = childView.getMeasuredHeight();
 cParams = (MarginLayoutParams) childView.getLayoutParams(); 
 int cl = 0, ct = 0, cr = 0, cb = 0; 
 switch (i){ 
 case 0: 
     cl = cParams.leftMargin + getPaddingLeft();
     ct = cParams.topMargin +getPaddingTop(); 
     break; 
case 1:
     ct = cParams.topMargin+getPaddingTop();
     cl = getWidth() - cParams.rightMargin - cWidth -getPaddingRight();
     break; 
case 2: 
     cl = cParams.leftMargin + getPaddingLeft(); 
     ct = getHeight() - cParams.bottomMargin - cHeight -  getPaddingBottom();
     break; 
case 3: 
     cl = getWidth()- cParams.rightMargin - cWidth - getPaddingRight(); 
     ct = getHeight() - cParams.bottomMargin - cHeight -       getPaddingBottom();
     break; 
    }  
     cr = cl + cWidth; cb = ct + cHeight; childView.layout(cl,ct,cr,cb); 
  } 
}

這是效果圖


QQ截圖20160426171159.png

**3. dispatchDraw() **
對于view樹的繪制來說,最先調(diào)用的還是draw()方法挨队,這個方法必須在layout完成后調(diào)用谷暮,而這個方法有6步:

  1. Draw the background // 畫背景
  2. If necessary, save the canvas’ layers to prepare for fading // 不是必需的,為后面第5步繪制做準備工作
  3. Draw view’s content // 畫內(nèi)容部分
  4. Draw children //繪制子組件部分
  5. If necessary, draw the fading edges and restore layers //繪制fade效果
  6. Draw decorations (scrollbars for instance) //對應(yīng)方法onDrawForeground(canvas); 和scrollbar有關(guān)盛垦,譬如listview的scrollbar湿弦,該層是在最外面的一層,也是最后繪制的情臭。

第一步 drawBackground(canvas); 這個方法主要是設(shè)置背景的邊框 setBackgroundBounds();
第二步和第五步不是必須的省撑,沒有仔細去分析,就不誤人了
第三步 onDraw(canvas) 就是view的onDraw()方法俯在,這肯定是內(nèi)容部分
第四步 // Step 4, draw the children dispatchDraw(canvas); 繪制子組件看到?jīng)]有竟秫,就是這個方法
整個view樹外層是decorview,繪制過程是調(diào)用draw()跷乐,有背景就先繪制背景肥败,對于viewGroup來說不需要實現(xiàn)onDraw()方法,所以會跳過這一步,來到了dispatchDraw(canvas);這個方法會調(diào)用drawChild(canvas, transientChild, drawingTime);來為每個子view進行繪制馒稍,而在子viewdrawchild()方法中有這一段代碼
// Fast path for layouts with no backgrounds if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW){ mPrivateFlags &= ~PFLAG_DIRTY_MASK; dispatchDraw(canvas); } else { draw(canvas); }
如果子view沒有背景就會調(diào)用dispatchDraw(canvas)皿哨,有背景就調(diào)用draw(canvas); 所以這也是為什么在view這個類中 dispatchDraw(canvas)是個空函數(shù),而在viewGroup中卻實現(xiàn)了這個函數(shù)纽谒,這個方法不建議重寫证膨,有需要實現(xiàn)更多的邏輯的話,可以重載鼓黔。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末央勒,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子澳化,更是在濱河造成了極大的恐慌崔步,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缎谷,死亡現(xiàn)場離奇詭異井濒,居然都是意外死亡,警方通過查閱死者的電腦和手機列林,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門瑞你,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人希痴,你說我怎么就攤上這事捏悬。” “怎么了润梯?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長甥厦。 經(jīng)常有香客問我纺铭,道長,這世上最難降的妖魔是什么刀疙? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任舶赔,我火速辦了婚禮,結(jié)果婚禮上谦秧,老公的妹妹穿的比我還像新娘竟纳。我一直安慰自己,他們只是感情好疚鲤,可當我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布锥累。 她就那樣靜靜地躺著,像睡著了一般集歇。 火紅的嫁衣襯著肌膚如雪桶略。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天,我揣著相機與錄音际歼,去河邊找鬼惶翻。 笑死,一個胖子當著我的面吹牛鹅心,可吹牛的內(nèi)容都是我干的吕粗。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼旭愧,長吁一口氣:“原來是場噩夢啊……” “哼颅筋!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起榕茧,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤垃沦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后用押,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肢簿,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年蜻拨,在試婚紗的時候發(fā)現(xiàn)自己被綠了池充。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡缎讼,死狀恐怖收夸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情血崭,我是刑警寧澤卧惜,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站夹纫,受9級特大地震影響咽瓷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜舰讹,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一茅姜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧月匣,春花似錦钻洒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至萍悴,卻和暖如春糯钙,著一層夾襖步出監(jiān)牢的瞬間粪狼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工任岸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留再榄,地道東北人。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓享潜,卻偏偏與公主長得像困鸥,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子剑按,可洞房花燭夜當晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容