View繪製流程簡述
在view的繪製過程中,一般會分如下三個過程:
measure() —— 測量view的大小
layout() —— 計算view在父view中的位置
draw() —— 繪製view
measure方法總呼叫了onMeasure方法,layout方法中呼叫了onLayout方法,draw中呼叫了onDraw方法,一般我們自定義view只需要實現這三個方法就可以。(measure在view中是final型別,不能被子類覆蓋)
首先,我們來看onMeasure方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
1
2
在此方法中,最重要的就是呼叫setMeasuredDimension方法給變數mMeasuredWidth和mMeasuredHeight賦值。onMeasure方法在引數widthMeasureSpec和heightMeasureSpec是父view的測量規格,其中包括父view的size和mode,一般我們會根據父view的測量規格來計運算元view的大小。平時我們使用getMeasureWidth()和getMeasureHeight()方法就是獲得mMeasuredWidth和mMeasuredHeight的值。上邊的程式碼是View類中onMeasure的預設實現,自定義view時,我們需要根據自己的需求去測量子View的大小。如果是自定義ViewGroup,則還需呼叫measureChild方法去測量子View的大小。
再來看onLayout方法:
View類中:
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
1
2
ViewGroup類中:
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
1
2
可以看到,在View中onLayout是一個空方法,而在ViewGroup中則是一個虛方法,也就是說自定義的ViewGroup必須實現此方法。那麼onLayout的作用到底是什麼呢?
其實onLayout就是計算mLeft,mTop,mRight,mBottom這四個變數的值,這四個變數就決定了view在父View中的位置。這四個變數是相對父View左上點的位置(不理解的請參考:http://blog.csdn.net/yanbober/article/details/50419117)。其中changed表示view的位置是否變化了,而l, t, r, b分別是父View的left, top, right, bottom, 我們就是根據父View的這幾個變數來計運算元view的位置。
onDraw方法:
這個方法一般如果沒有什麼特殊需求的話,不需要重寫,使用預設的就可以,因為通過onMeasure和onLayout方法已經確定了view的大小和位置,所以onDraw中只需按照引數繪製即可。
下面我們自己自定義一個ViewGroup:
package com.example.testmeasure;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
public class MyViewGroup extends ViewGroup {
public MyViewGroup(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
public MyViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public MyViewGroup(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (changed) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
// 讓子View垂直排列
int height = getPaddingTop();
if (i > 0) {
for (int j = 0; j <= i - 1; j++) {
height += getChildAt(j).getMeasuredHeight();
}
}
child.layout(getPaddingLeft(), height, child.getMeasuredWidth()
+ getPaddingLeft(), child.getMeasuredHeight() + height);
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int mode = MeasureSpec.getMode(heightMeasureSpec);
int size = MeasureSpec.getSize(heightMeasureSpec);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
在onMeasure方法中,我們首先呼叫super.onMeasure()方法去設定MyViewGroup的mMeasuredWidth和mMeasuredHeight的值,然後迴圈呼叫measureChild方法去測量子View的大小。
在onLayout方法中,我們首先判斷changed是否為true,然後讓子View以垂直的方法排列顯示,實際上就是計算每個子View的mTop的值。當然,我們也可以任意的設定子View的排列方式。
在XML檔案中使用MyViewGroup:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/id_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="5dp" >
<com.example.testmeasure.MyViewGroup
android:id="@+id/id_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/darker_gray"
android:paddingBottom="5dp"
android:paddingLeft="5dp"
android:paddingRight="10dp"
android:paddingTop="5dp" >
<TextView
android:id="@+id/id_text"
android:layout_width="100dp"
android:layout_height="40dp"
android:background="@android:color/holo_green_dark"
android:text="@string/hello_world" />
<TextView
android:id="@+id/id_text2"
android:layout_width="100dp"
android:layout_height="40dp"
android:background="@android:color/holo_blue_light"
android:text="你好,世界!" />
</com.example.testmeasure.MyViewGroup>
</RelativeLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
效果圖如下:
灰色的背景為MyViewGroup區域,綠色為子View1,藍色為子View2,我們還可以看到四周的一些白色背景,這個是MyViewGroup的父View。
如果細心,我們可以發現一個問題,就是在XML檔案中,MyViewGroup的layout_height屬性設定的是wrap_content, 但是我們實際看到的效果並不是適應內容,而好像是match_parent的效果,這是為什麼?
還記得我們在onMeasure中呼叫super.onMeasure方法去設定MyViewGroup的mMeasuredWidth和mMeasuredHeight的值嗎?我們說過,onMeasure中的兩個引數實際上是父View的測量規格,這個規格中包括了父View的size和mode,在onMeasure中我們並沒有根據子View的高度去計算MyViewGroup的高度,而是直接將父的大小設定給MyViewGroup,所以MyViewGroup的layout_width和layout_height屬性不管你設定match_parent還是wrap_content,都會之父View的大小。
接下來,我們就去更改onMeasure方法,使其可以自適應View的高度
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int mode = MeasureSpec.getMode(heightMeasureSpec);
int size = MeasureSpec.getSize(heightMeasureSpec);
int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
LayoutParams lp = getLayoutParams();
// 讓父的height適應子view的高度(就是讓wrap_content起作用)
int height = 0;
if (lp.height == LayoutParams.MATCH_PARENT){
height = size;
}else if (lp.height == LayoutParams.WRAP_CONTENT){
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
height += child.getMeasuredHeight();
}
height += getPaddingTop();
height += getPaddingBottom();
}else {
height = lp.height;
}
setMeasuredDimension(widthMeasureSpec, height);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
我們在測量完子View的大小後,然後根據每個子View測量的高度去計算MyViewGroup的 高度,最後呼叫setMeasureDimension方法用我計算出來的高度去設定mMeasuredHeight的值。再看效果圖:
可以看到,這次MyViewGroup的高度和子View的高度一樣。可能有人還問,上下不是還能看到一部分灰色背景嗎?這個是因為我們設定了paddingTop和paddingBottom的值,而在計算高度時,我們將這兩個值也加了進去。
---------------------
作者:xingxing_yan
來源:CSDN
原文:https://blog.csdn.net/xingxing_yan/article/details/50573507
版權宣告:本文為博主原創文章,轉載請附上博文連結!