前言
一、View的繪製過程
首先View體系中有兩個概念,View和ViewGroup,View是所有UI組件的基類,ViewGroup也是View派生出來的子類。然後,我們再講三個東西,WindowManager、DecorView、ViewRoot。通過這三個東西,我們就能對View的底層原理有個大概的瞭解了
WindowManager:
DecorView:
ViewRoot:
最後,我們來說說View的繪製過程,
View的繪製過程是從ViewRoot的peformTraversals方法發起的,該方法會分別調用測量寬高、確認在父容器中的位置、和繪製在屏幕上這三個過程。
View的measure方法,完成對View寬高的測量,然後會調用onMeasure,對它所有的子控件進行寬高的測量
View的layout方法,確認控件在父容器中的擺放位置,會調用onLayout方法,對它所有的子控件進行layout過程
View的draw方法,最終將控件繪製在屏幕上,它也會對它所有的子控件進行繪製
經過上面的三個步驟,我們就完成了View的繪製過程。
該過程大致就是,通過解析得到的含測量模式的寬度和高度,該值由父容器的MeasureSpec和自身的LayoutParam來共同決定,根據測量模式的不同,對View的寬和高進行不同的賦值。
首先,解釋下幾種模式UNSPECIFIED、AT_MOST、EXACTLY :
如果是UNSPECIFIED模式,表示父容器沒有對該view進行任何限制,即要多大給多大
如果是AT_MOST模式(最大化),父容器指定了一個可用大小,View的大小不能大於這個值
如果是EXACTLY 模式(精確模式),父容器已經檢測出View所需要的精確大小。最終View大小就是這個值
View的測量模式和具體賦值可根據下表得來
當View採用固定寬高時,不管父容器的模式是什麼,View的MeasureSize都是精確模式,並且其大小遵循Layoutparams中的大小
當View的寬高是match_parent時,如果父容器的模式是精確模式,那麼View也是精確模式並且大小是父容器的剩餘空間,如果父容器
是最大化模式,那麼View也是最大化模式,並且大小不能超過父容器的剩餘空間
當View的寬高是wrap_content時,不管父容器的模式是精確還是最大化,View的模式總是最大化並且大小不能超過超過父容器的剩餘
空間
備註:UNSPECIFIED模式,主要用於系統內部多少Measure的情形,一般來說我們不需要關注此模式
該過程的作用就是確定View在父容器中的擺放位置,當ViewGroup確定了自己的擺放位置之後,會通過onMeasure方法依次確認遍歷所有子控件,並且通過layout方法確認子控件的擺放位置。但是需要注意的就是layout過程需要考慮View的屬性,比如如果是一個LinearLayout,就需要考慮是水平的還是豎直的,還有pading和margin值等。
View的draw過程(View的draw方法),就是講界面繪製在屏幕上面,它遵循以下幾步:
1、繪製背景 background.draws(canvas)
2、繪製自己 onDraw
3、繪製children dispathDraw
通過調用dispathDraw,依次對子控件進行draw
4、繪製裝飾 onDrawForeground
一個控件可能包括了滾動條等裝飾,該方法就是繪製View的裝飾物的。
如果我們需要自定義控件的話,就可以在draw過程中,通過Canvas(畫板)、Paint(畫筆)等進行繪製 。
二、自定義View的分類
A、繼承View重寫onDraw方法
三、View的自定義屬性
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="15dp"
android:text="Hello World"
android:textColor="#333333"
android:textSize="15sp" />
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CircleView">
<attr name="circle_color" format="color"/>
<attr name="fontColor" format="enum">
<enum name="blue" value="1"/>
<enum name="red" value="2"/>
</attr>
</declare-styleable>
</resources>
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
//第二個參數就是我們在styles.xml文件中的<declare-styleable>標籤
//即屬性集合的標籤,在R文件中名稱爲R.styleable+name
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
//第一個參數爲屬性集合裏面的屬性,R文件名稱:R.styleable+屬性集合名稱+下劃線+屬性名稱
//第二個參數爲,如果沒有設置這個屬性,則設置的默認的值
mFontColor = a.getColor(R.styleable.CircleView_circle_color, Color.RED);
//最後記得將TypedArray對象回收
a.recycle();
}
xmlns:app="http://schemas.android.com/apk/res-auto"
這樣我們才能知道去資源文件中找自定義好的屬性集合,然後就可以使用自定義屬性了<com.example.lenovo.myapplication.MyView
app:circle_color="#333333"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
四、Canvas的簡單使用
在自定義View的時候,我們可以通過draw方法獲取到Canvas對象,所以不需要自己new對象了
@Override
protected void onDraw(Canvas canvas) {
}
做進行canvas的使用前,首先我們要知道RectF是做什麼用的 RectF其實就是通過left、top、right、bottom四個座標點來確定一個矩形
@Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();//new一個畫筆對象,並且設置爲藍色
paint.setColor(Color.BLUE);
canvas.drawCircle(100,100,100,paint);//通過canvas畫圓
}
效果如下@Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setColor(Color.BLUE);
RectF rectF = new RectF(0,0,100,100);//確定一個100 * 100的矩形
canvas.drawArc(rectF,//在這個矩形範圍內畫一段圓弧
0,//開始角度
90,//掃過的角度
true,//是否使用中心
paint);
}
@Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setColor(Color.BLUE);
Path path = new Path();
path.moveTo(131,5);
path.lineTo(88.357f,63.725f);
path.lineTo(19,42);
path.lineTo(62.002f,100);
path.lineTo(19,159);
path.lineTo(88.357f,136.275f);
path.lineTo(131,195);
path.lineTo(131,122.419f);
path.lineTo(200,100);
path.lineTo(131,77.581f);
path.lineTo(131,5);
canvas.drawPath(path,paint);
}
效果如下五、View的事件分發體系
該方法用來進行事件的分發,如果該事件能傳遞到當前View,那麼此方法一定會被調用,返回結果受當前View的onTouchEvent和下級View的dispatch
public boolean onInterceptTouchEvent(MotionEvent event):
在上述方法內部調用,用來判斷是否攔截某個事件,如果當前View攔截了某個事件,那麼在同一個事件序列中,此方法不會被再次調用,返回結果表示
public boolean onTouchEvent(MotionEvent event):
在dispatichTouchEvent方法中調用,用來處理點擊事件,返回結果表示是否消耗當前事件,如果不消耗,則在同一個事件序列中,當前View無法再次接
public boolean dispatchTouchEvent(MotionEvent event) {
boolean consume = false;
if (onInterceptTouchEvent(event)){
consume = onTouchEvent(event);
}else {
consume = child.dispatchTouchEvent(event);
}
return consume;
}
事件傳遞的大致規則如下:對於一個根ViewGroup來說,點擊事件產生後,首先會傳給它,此時它的dispatchTouchEvent就會被調用,如果這個ViewGroup的onInterceptTouchEvent方法返回true,就表示它要攔截當前事件,接着事件就會交給這個ViewGroup處理,即它的onTouchEvent方法就會被調用;如
1、同一事件序列:是指從手指接觸屏幕的那一刻起,到手指離開屏幕的那一刻結束,在這個過程中所產生的一系列事件,這個事件序
2、ViewGroup默認不攔截任何事件,onInterceptTouchEvent默認返回false。
3、View沒有onInterceptTouchEvent方法,一旦有點擊事件傳遞給它,那麼它的onTouchEvent方法就會被調用。
4、事件的傳遞過程總是先傳遞給父元素,然後再由父元素分發給子View,通過requestDisallowInterceptTouchEvent方法,可以在子元素中干預父元素