Android自定义控件_View的绘制流程
每一個(gè)View/ViewGroup的顯示都會(huì)經(jīng)過(guò)三個(gè)過(guò)程:
1、measure過(guò)程(測(cè)量View顯示的大小,位置);
2、layout過(guò)程(布局view的位置);
3、draw過(guò)程(上一篇文章說(shuō)到的通過(guò)canvas繪制到界面上顯示,形成了各色的View)
下面分析一下各個(gè)過(guò)程:
measure過(guò)程:
因?yàn)镈ecorView實(shí)際上是派生自FrameLayout的類,也即一個(gè)ViewGroup實(shí)例,該ViewGroup內(nèi)部的ContentViews又是一個(gè)ViewGroup實(shí)例,依次內(nèi)嵌View或ViewGroup形成一個(gè)View樹。所以measure函數(shù)的作用是為整個(gè)View樹計(jì)算實(shí)際的大小,設(shè)置每個(gè)View對(duì)象的布局大小(“窗口”大小)。實(shí)際對(duì)應(yīng)屬性就是View中的mMeasuredHeight(高)和mMeasureWidth(寬)。
View在Measure過(guò)程中,最主要的三個(gè)方法:
public final void measure(int widthMeasureSpec, int heightMeasureSpec)
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight)
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight)。(此方法在onMesare方法里面調(diào)用,如果沒(méi)有調(diào)用需要手動(dòng)調(diào)用)
前面兩個(gè)函數(shù)都是final類型的,不能重載,為此在ViewGroup派生的非抽象類中我們必須重載onMeasure函數(shù),實(shí)現(xiàn)measure的原理是:假如View還有子View,則measure子View,直到所有的子View完成measure操作之后,再measure自己。ViewGroup中提供的measureChild或measureChildWithMargins就是實(shí)現(xiàn)這個(gè)功能的。各個(gè)View/ViewGroup最終調(diào)用setMeasuredDimension方法,設(shè)置View的大小,如果沒(méi)有調(diào)用它,測(cè)量是不起作用的。
在具體介紹測(cè)量原理之前還是先了解些基礎(chǔ)知識(shí),即measure函數(shù)的參數(shù)由類measureSpec的makeMeasureSpec函數(shù)方法生成的一個(gè)32位整數(shù),該整數(shù)的高兩位表示模式(Mode),低30位則是具體的尺寸大小(specSize)。
MeasureSpec有三種模式分別是UNSPECIFIED, EXACTLY和AT_MOST,各表示的意義如下:
如果是AT_MOST,specSize代表的是最大可獲得的尺寸;
如果是EXACTLY,specSize代表的是精確的尺寸;
如果是UNSPECIFIED,對(duì)于控件尺寸來(lái)說(shuō),沒(méi)有任何參考意義。
那么對(duì)于一個(gè)View的上述Mode和specSize值默認(rèn)是怎么獲取的呢,他們是根據(jù)View的LayoutParams參數(shù)來(lái)獲取的:
參數(shù)為fill_parent/match_parent時(shí),Mode為EXACTLY,specSize為剩余的所有空間;
參數(shù)為具體的數(shù)值,比如像素值(px或dp),Mode為EXACTLY,specSize為傳入的值;
參數(shù)為wrap_content,Mode為AT_MOST,specSize運(yùn)行時(shí)決定
具體測(cè)量原理
上面提供的Mode和specSize只是程序?qū)iew的一個(gè)期望尺寸,最終一個(gè)View對(duì)象能從父視圖得到多大的允許尺寸則由子視圖期望尺寸和父視圖能力尺寸(可提供的尺寸)兩方面決定。關(guān)于期望尺寸的設(shè)定,可以通過(guò)在布局資源文件中定義的android:layout_width和android:layout_height來(lái)設(shè)定,也可以通過(guò)代碼在addView函數(shù)調(diào)用時(shí)傳入的LayoutParams參數(shù)來(lái)設(shè)定。父View的能力尺寸歸根到最后就是DecorView尺寸,這個(gè)尺寸是全屏,由手機(jī)的分辨率決定。期望尺寸、能力尺寸和最終允許尺寸的關(guān)系,我們可以通過(guò)閱讀measureChild或measureChildWithMargins都會(huì)調(diào)用的getChildMeasureSpec函數(shù)的源碼來(lái)獲得。
根據(jù)源碼View的OnMeasure函數(shù)調(diào)用的getDefaultSize函數(shù)獲知,默認(rèn)情況下,控件都有一個(gè)最小尺寸,該值可以通過(guò)設(shè)置android:minHeight和android:minWidth來(lái)設(shè)置(無(wú)設(shè)置時(shí)缺省為0);在設(shè)置了背景的情況下,背景drawable的最小尺寸與前面設(shè)置的最小尺寸比較,兩者取大者,作為控件的最小尺寸。在UNSPECIFIED情況下就選用這個(gè)最小尺寸,其它情況則根據(jù)允許尺寸來(lái)。
layout過(guò)程:
上述measure過(guò)程達(dá)到的結(jié)果是設(shè)定了視圖的高和寬,layout過(guò)程的作用就是設(shè)定視圖在父視圖中的四個(gè)點(diǎn)(分別對(duì)應(yīng)View四個(gè)成員變量mLeft,mTop,mRight,mBottom)。同樣layout也是被fianl修飾符限定為不能重載,不過(guò)在ViewGroup中onLayout函數(shù)被abstract修飾,即所有派生自ViewGroup的類必須實(shí)現(xiàn)onLayout函數(shù),從而實(shí)現(xiàn)對(duì)其包含的所有子視圖的布局設(shè)定。
我們來(lái)看任意一個(gè)Layout布局,此就用LinearLayout為例,onLayout(layoutVertical方法)方法部分源碼:
void layoutVertical() {final int paddingLeft = mPaddingLeft;int childTop;int childLeft;// Where right end of child should gofinal int width = mRight - mLeft;int childRight = width - mPaddingRight;// Space available for childint childSpace = width - paddingLeft - mPaddingRight;final int count = getVirtualChildCount();final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;switch (majorGravity) {case Gravity.BOTTOM:// mTotalLength contains the padding alreadychildTop = mPaddingTop + mBottom - mTop - mTotalLength;break;// mTotalLength contains the padding alreadycase Gravity.CENTER_VERTICAL:childTop = mPaddingTop + (mBottom - mTop - mTotalLength) / 2;break;case Gravity.TOP:default:childTop = mPaddingTop;break;}for (int i = 0; i < count; i++) {final View child = getVirtualChildAt(i);if (child == null) {childTop += measureNullChild(i);} else if (child.getVisibility() != GONE) {final int childWidth = child.getMeasuredWidth();final int childHeight = child.getMeasuredHeight();final LinearLayout.LayoutParams lp =(LinearLayout.LayoutParams) child.getLayoutParams();int gravity = lp.gravity;if (gravity < 0) {gravity = minorGravity;}final int layoutDirection = getResolvedLayoutDirection();final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {case Gravity.CENTER_HORIZONTAL:childLeft = paddingLeft + ((childSpace - childWidth) / 2)+ lp.leftMargin - lp.rightMargin;break;case Gravity.RIGHT:childLeft = childRight - childWidth - lp.rightMargin;break;case Gravity.LEFT:default:childLeft = paddingLeft + lp.leftMargin;break;}if (hasDividerBeforeChildAt(i)) {childTop += mDividerHeight;}childTop += lp.topMargin;setChildFrame(child, childLeft, childTop + getLocationOffset(child),childWidth, childHeight);childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);i += getChildrenSkipCount(child, i);}}} View Code從代碼顯然可知具體layout布局時(shí),就是根據(jù)measure過(guò)程設(shè)置的高和寬,結(jié)合視圖在父視圖中的起始位置,再外加視圖的layoutgravity屬性來(lái)設(shè)置四個(gè)點(diǎn)的具體位置(在LinearLayout中還會(huì)增加對(duì)layoutweight屬性的考慮)。這個(gè)過(guò)程相對(duì)沒(méi)有measure那么復(fù)雜。我們?cè)趯懣丶臅r(shí)候,我們可以根據(jù)自己的需要利用measure計(jì)算的結(jié)果來(lái)控制各個(gè)控件的位置,實(shí)現(xiàn)各種絢麗的動(dòng)畫交互。
這是我在項(xiàng)目中寫的一個(gè)利用measure+layout寫的一個(gè)LineLayout,實(shí)現(xiàn)顯示一行,左邊顯示文字,右邊顯示Icon;當(dāng)文字照過(guò)一屏的時(shí)候,自動(dòng)截取打省略號(hào),右邊Icon需要一直顯示;當(dāng)然還有諸多缺陷與待改進(jìn)的地方,期望指出與改進(jìn)。
http://download.csdn.net/detail/huangbiao86/7521721
?
轉(zhuǎn)載于:https://www.cnblogs.com/PDW-Android/p/3796748.html
總結(jié)
以上是生活随笔為你收集整理的Android自定义控件_View的绘制流程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Android加载大图片(压缩)
- 下一篇: IOS客户端rtmp