對比自定義view和自定義viewgroup

前言

最近在研究view的整個事件過程,以及自定義view的繪制,今天突然發(fā)現還有dispatchDraw(),于是在官方api中找到了draw()方法。這兩個方法分別用于view繪制和viewgroup繪制,通常我們實現更多的是view繪制,而viewgroup繪制 默認是關閉狀態(tài),需要設置setWillNotDraw(false),什么意思呢?就是設置繪制為打開狀態(tài)。下面描述一下view定義以及viewgroup定義的流程,最后對比自定義view以及viewgroup有什么區(qū)別。

自定義view:

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

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

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

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

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

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

2. onDraw()
打一個比方,自定義view其實就像畫畫,需要畫布,畫筆,確定畫的位置。 onDraw()扮演著畫的動作,通常我們需要重寫這個方法,因為父view的onDraw()方法是個空方法當然它只提供畫布canvas,具體要畫什么圖案,畫多大,以及用什么樣的畫筆,不建議在這個方法做初始化工作,因為調用invalidate()方法會使這個方法重繪,我們可以在自定義view的構造函數寫一個init()方法,用來實現畫筆及一些繪制工作需要用到的“工具”的初始化工作,在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確定布局的意思是給內部子view設置layout,然后再去繪制,因為view是單獨存在的,不存在子view只存在內部需要繪制的元素,所以view和viewgroup的概念是不一樣的。而viewGroup可能存在多個子view,所以需要給每個子view來指定具體位置,這就是onLayout()所做的事了,同樣,測量也是用到onMeasure(),但是它和子view的onMeasure()不同,子view的測量只要測量這個view按照測量法則需要多大的空間,而viewgroup的測量則是根據子view的測量結果來進行統(tǒng)計,最終來確定整個viewgroup的widthsize和heightsize。viewgroup的繪制會用到dispatchDraw(),它主要是分發(fā)給子組件進行繪制,調用子組件的draw()方法,通常viewgroup是不用去繪制的,而且繪制功能默認關閉。

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); 
// 調用子組件的measure()方法 
child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }

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

2. onLayout()

這是一個抽象方法,如果繼承了viewGroup就需要去實現,上面也簡單的介紹了,其實這個方法就是確定子組件在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根據其寬和高,以及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樹的繪制來說,最先調用的還是draw()方法,這個方法必須在layout完成后調用,而這個方法有6步:

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

第一步 drawBackground(canvas); 這個方法主要是設置背景的邊框 setBackgroundBounds();
第二步和第五步不是必須的,沒有仔細去分析,就不誤人了
第三步 onDraw(canvas) 就是view的onDraw()方法,這肯定是內容部分
第四步 // Step 4, draw the children dispatchDraw(canvas); 繪制子組件看到沒有,就是這個方法
整個view樹外層是decorview,繪制過程是調用draw(),有背景就先繪制背景,對于viewGroup來說不需要實現onDraw()方法,所以會跳過這一步,來到了dispatchDraw(canvas);這個方法會調用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沒有背景就會調用dispatchDraw(canvas),有背景就調用draw(canvas); 所以這也是為什么在view這個類中 dispatchDraw(canvas)是個空函數,而在viewGroup中卻實現了這個函數,這個方法不建議重寫,有需要實現更多的邏輯的話,可以重載。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 99,559評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,442評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,581評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,922評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,096評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 49,639評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,374評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,591評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,789評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,322評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,554評論 2 379

推薦閱讀更多精彩內容