自定義View
一策肝、 View的繪制流程
onMeasure()
--測量View
的大小onLayout()
-- 確定子View
的布局onDraw()
--實際繪制內容
自定義View
主要實現onMeasure()隐绵、onDraw()
依许,自定義ViewGroup
主要實現onMeasure()、onLayout()
;
二峭跳、實現自定義
1. 流程圖
<img src="C:\Users\Administrator\Desktop\自定義View的流程.jpg" alt="自定義View的流程" style="zoom:50%;" />
2. MeasureSpec
MeasureSpec
是view中的內部類缺前,是一個32位的int值悬襟,高兩位表示Mode
低30位表示size
,MODE_SHIFT=30
表示移位。
UNSPPECIFIED
:不對View的大小做限制逝段,在系統(tǒng)中使用EXACTLY:
父容器已經得到自己View的確切大小AT_MOST:
父容器指定一個大小割捅,子view不可以超過這個值,如matchParent
3. onMeasure()
這個方法用于測量View
的寬高亿驾。android
中的View
是樹形結構巫糙,父View
包含子View
颊乘,子View
又包含自己的子View
,因此要想測量自己的寬高就需要遞歸的測量出子View
的寬高。
3.1 測量子View
的寬高
//layoutParams就是xml中的布局參數:layout_width,layout_height
LayoutParams childLP = childView.getLayoutParams();
//獲取子View的MeasureSpec
int childWidthSpec = getChildMeasureSpec(widthMeasureSpec, paddingRight + paddingLeft,
childLP.width);
int childHeightSpec = getChildMeasureSpec(heightMeasureSpec,paddingTop + paddingBottom,
childLP.height);
使用getChildMeasureSpec
方法測量出子View
的寬高浙值,其在這個方法中會計算出子View
的最終size
以及測量的mode
檩小。首先先獲取子View的父View也就是當前定義的這個View的寬高和測量模式。然后根據測量模式來判斷怎么樣給子View分配大小筐付,因為當前View的大小也是由這個View的父View所分配的阻肿。
-
EXACTLY
case MeasureSpec.EXACTLY: //子View大小是確定的,返回子view要的大小和Exactly if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; //子view要和父Vew一樣大较解,有多少要多少 } 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. //子View不確定要多少赴邻,返回父布局的有的,最大不能超過父布局的大小 resultSize = size; resultMode = MeasureSpec.AT_MOST; } break;
如果這個View的大小是確定的奸焙,則分配給子View的大小和模式分為三種。第一種与帆,子View的大小也是確定的,這種情況下就分配給子View所要求的大凶岵稹茶凳;第二種,子View的大小要求和父View一樣大就把父View的大小 分配給子View贮喧;第三種,子View不確定要多少辩恼,就把父View的大小分配給子View并把mode設為AT_MOST
谓形,即最大不能超過這個。
-
AT_MOST
// 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;
如果這個View的有一個最大的寬高聘萨,其分配給子view的寬高也分為三種情況童太。子View要求一個確切的值,就分配給子View這個大惺槭汀爆惧;如果子View要求和父View一樣的大小就把父View有的最大的寬高分配給子View;如果子View不確定要多少也把父View的最大值給子View检激。
UNSPECIFIED
通過上面的方法就能測量出子View
的MeasureSpec
,然后調用子View的measure
方法就能根據MeasureSpec
測量出寬高
//獲取度量之后的寬高
int childMeasuredWidth = childView.getMeasuredWidth();
int childMeasuredHeight = childView.getMeasuredHeight();
獲取到所有的子View組成的布局的最大寬度和高度之后就要和這個View的父View分配這個View的大小做比較,如果這個View的mode為EXACTLY
,這個View所計算出來的View
的最終寬度為 MeasureSpec
中獲得的寬高和所有子view組成的布局的最大寬高的最小值傲隶。
//判斷這個ViewGroup的父布局給它的模式是什么,如果是確切的那這個ViewGroup的寬度只能是父布局給的寬度
//不需要判斷AT_MOST的情況
int realWidth = widthMode == MeasureSpec.EXACTLY ? selfWidth : parentNeedWidth;
int realHeight = heightMode == MeasureSpec.EXACTLY ? selfHeight : parentNeedHeight;
最后保存測量出來的寬高值
setMeasuredDimension(realWidth, realHeight);
4. onLayout
//左邊界 起點
int curLeft = getPaddingLeft();
//上邊界起點
int curTop = getPaddingTop();
for (int i = 0; i < mAllLines.size(); i++) {
int lineHeight = mLineHeights.get(i);
List<View> lineViews = mAllLines.get(i);
for (View view : lineViews) {
int left = curLeft;
int top = curTop;
//注意:這里只能拿到測量的寬度和高度
// 不能使用getWidth和getHeight脖卖,這兩個方法只有在當前View.layout執(zhí)行后才會生效
int right = left + view.getMeasuredWidth();
int bottom = top + view.getMeasuredHeight();
view.layout(left, top, right, bottom);
curLeft = right + mHorizontalSpacing;
}
curLeft = getPaddingLeft();
curTop = curTop + lineHeight + mVerticalSpacing;
}
重寫這個方法用于布局子View,注意view只有在執(zhí)行了layout方法之后才能通過getWidth等方法拿到其寬高值沒在此之前只能通過getMeasuredWidth來獲取巧颈。
總結
自定義ViewGroup主要需要重寫onMeasure和onLayout
兩個方法,
onMeasure
的流程為
- 測量子View的
MeasureSpec
- 根據子View的
MeasureSpec
獲取子View測量到的寬高值十籍,并保存計算出所有子View所需要的最大寬度和高度- 判斷當前View的Mode是不是
EXACTLY
的如果是則將寬高值設置為所有子View所需的寬高值和這個View的父View分配給這個View的寬高值(通過MeasureSpec拿到)的最小值