android 自定义 child,Android自定义View
前言
Android自定義View的詳細(xì)步驟是我們每一個Android開發(fā)人員都必須掌握的技能,因?yàn)樵陂_發(fā)中總會遇到自定義View的需求。為了提高自己的技術(shù)水平,自己就系統(tǒng)的去研究了一下,在這里寫下一點(diǎn)心得,有不足之處希望大家及時指出。
流程
在Android中對于布局的請求繪制是在Android framework層開始處理的。繪制是從根節(jié)點(diǎn)開始,對布局樹進(jìn)行measure與draw。在RootViewImpl中的performTraversals展開。它所做的就是對需要的視圖進(jìn)行measure(測量視圖大小)、layout(確定視圖的位置)與draw(繪制視圖)。下面的圖能很好的展現(xiàn)視圖的繪制流程:
當(dāng)用戶調(diào)用requestLayout時,只會觸發(fā)measure與layout,但系統(tǒng)開始調(diào)用時還會觸發(fā)draw
下面來詳細(xì)介紹這幾個流程。
measure
measure是View中的final型方法不可以進(jìn)行重寫。它是對視圖的大小進(jìn)行測量計(jì)算,但它會回調(diào)onMeasure方法,所以我們在自定義View的時候可以重寫onMeasure方法來對View進(jìn)行我們所需要的測量。它有兩個參數(shù)widthMeasureSpec與heightMeasureSpec。其實(shí)這兩個參數(shù)都包含兩部分,分別為size與mode。size為測量的大小而mode為視圖布局的模式
我們可以通過以下代碼分別獲取:
intwidthSize?=?MeasureSpec.getSize(widthMeasureSpec);
intheightSize?=?MeasureSpec.getSize(heightMeasureSpec);
intwidthMode?=?MeasureSpec.getMode(widthMeasureSpec);
intheightMode?=?MeasureSpec.getMode(heightMeasureSpec);
獲取到的mode種類分為以下三種:
MODE
EXPLAIN
UNSPECIFiED
父視圖不對子視圖進(jìn)行約束,子視圖大小可以是任意大小,一般是對ListView、ScrollView等進(jìn)行自定義,一般用不到
EXACTLY
父視圖對子視圖設(shè)定了一個精確的尺寸,子視圖不超過該尺寸,一般為精確的值例如200dp或者使用了match_parent
AT_MOST
父視圖對子視圖指定了一***的尺寸,確保子視圖的所以內(nèi)容都剛好能在該尺寸中顯示出來,一般為wrap_content,這種父視圖不能獲取子視圖的大小,只能由子視圖自己去計(jì)算尺寸,這也是我們測量要實(shí)現(xiàn)的邏輯情況
setMeasuredDimension
通過以上邏輯獲取視圖的寬高,***要調(diào)用setMeasuredDimension方法將測量好的寬高進(jìn)行傳遞出去。其實(shí)最終是調(diào)用setMeasuredDimensionRaw方法對傳過來的值進(jìn)行屬性賦值。調(diào)用super.onMeasure()的調(diào)用邏輯也是一樣的。
下面以自定義一個驗(yàn)證碼的View為例,它的onMeasure方法如下:
@Override
protected?void?onMeasure(intwidthMeasureSpec,intheightMeasureSpec)?{
intwidthSize?=?MeasureSpec.getSize(widthMeasureSpec);
intheightSize?=?MeasureSpec.getSize(heightMeasureSpec);
intwidthMode?=?MeasureSpec.getMode(widthMeasureSpec);
intheightMode?=?MeasureSpec.getMode(heightMeasureSpec);
if?(widthMode?==?MeasureSpec.EXACTLY)?{
//直接獲取精確的寬度
width?=?widthSize;
}?elseif?(widthMode?==?MeasureSpec.AT_MOST)?{
//計(jì)算出寬度(文本的寬度+padding的大小)
width?=?bounds.width()?+?getPaddingLeft()?+?getPaddingRight();
}
if?(heightMode?==?MeasureSpec.EXACTLY)?{
//直接獲取精確的高度
height?=?heightSize;
}?elseif?(heightMode?==?MeasureSpec.AT_MOST)?{
//計(jì)算出高度(文本的高度+padding的大小)
height?=?bounds.height()?+?getPaddingBottom()?+?getPaddingTop();
}
//設(shè)置獲取的寬高
setMeasuredDimension(width,?height);
}
可以對自定義View的layout_width與layout_height進(jìn)行設(shè)置不同的屬性,達(dá)到不同的mode類型,就可以看到不同的效果
measureChildren
如果你是對繼承ViewGroup的自定義View那么在進(jìn)行測量自身的大小時還要測量子視圖的大小。一般通過measureChildren(int widthMeasureSpec, int heightMeasureSpec)方法來測量子視圖的大小。
protected?void?measureChildren(intwidthMeasureSpec,intheightMeasureSpec)?{
final?intsize=?mChildrenCount;
final?View[]?children?=?mChildren;
for(inti?=?0;?i?
final?Viewchild?=?children[i];
if?((child.mViewFlags?&?VISIBILITY_MASK)?!=?GONE)?{
measureChild(child,?widthMeasureSpec,?heightMeasureSpec);
}
}
}
通過上面的源碼會發(fā)現(xiàn),它其實(shí)是遍歷每一個子視圖,如果該子視圖不是隱藏的就調(diào)用measureChild方法,那么來看下measureChild源碼:
protected?void?measureChild(Viewchild,intparentWidthMeasureSpec,
intparentHeightMeasureSpec)?{
final?LayoutParams?lp?=?child.getLayoutParams();
final?intchildWidthMeasureSpec?=?getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft?+?mPaddingRight,?lp.width);
final?intchildHeightMeasureSpec?=?getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop?+?mPaddingBottom,?lp.height);
child.measure(childWidthMeasureSpec,?childHeightMeasureSpec);
}
會發(fā)現(xiàn)它首先調(diào)用了getChildMeasureSpec方法來分別獲取寬高,***再調(diào)用的就是View的measure方法,而通過前面的分析我們已經(jīng)知道它做的就是對視圖大小的計(jì)算。而對于measure中的參數(shù)是通過getChildMeasureSpec獲取,再來看下其源碼:
publicstaticintgetChildMeasureSpec(intspec,intpadding,intchildDimension)?{
intspecMode?=?MeasureSpec.getMode(spec);
intspecSize?=?MeasureSpec.getSize(spec);
intsize=?Math.max(0,?specSize?-?padding);
intresultSize?=?0;
intresultMode?=?0;
switch?(specMode)?{
//?Parent?has?imposed?an?exact?sizeonus
caseMeasureSpec.EXACTLY:
if?(childDimension?>=?0)?{
resultSize?=?childDimension;
resultMode?=?MeasureSpec.EXACTLY;
}?elseif?(childDimension?==?LayoutParams.MATCH_PARENT)?{
//?Child?wants?tobe?oursize.?So?be?it.
resultSize?=?size;
resultMode?=?MeasureSpec.EXACTLY;
}?elseif?(childDimension?==?LayoutParams.WRAP_CONTENT)?{
//?Child?wants?todetermine?its?ownsize.?It?can't?be
//?bigger?than?us.
resultSize?=?size;
resultMode?=?MeasureSpec.AT_MOST;
}
break;
//?Parent?has?imposed?a?maximum?sizeonus
caseMeasureSpec.AT_MOST:
if?(childDimension?>=?0)?{
//?Child?wants?a?specific?size...?so?be?it
resultSize?=?childDimension;
resultMode?=?MeasureSpec.EXACTLY;
}?elseif?(childDimension?==?LayoutParams.MATCH_PARENT)?{
//?Child?wants?tobe?oursize,?but?oursizeisnotfixed.
//?Constrain?child?tonotbe?bigger?than?us.
resultSize?=?size;
resultMode?=?MeasureSpec.AT_MOST;
}?elseif?(childDimension?==?LayoutParams.WRAP_CONTENT)?{
//?Child?wants?todetermine?its?ownsize.?It?can't?be
//?bigger?than?us.
resultSize?=?size;
resultMode?=?MeasureSpec.AT_MOST;
}
break;
//?Parent?asked?tosee?how?big?we?wanttobe
caseMeasureSpec.UNSPECIFIED:
if?(childDimension?>=?0)?{
//?Child?wants?a?specific?size...?let?him?have?it
resultSize?=?childDimension;
resultMode?=?MeasureSpec.EXACTLY;
}?elseif?(childDimension?==?LayoutParams.MATCH_PARENT)?{
//?Child?wants?tobe?oursize...?findouthow?big?it?should
//?be
resultSize?=?View.sUseZeroUnspecifiedMeasureSpec???0?:size;
resultMode?=?MeasureSpec.UNSPECIFIED;
}?elseif?(childDimension?==?LayoutParams.WRAP_CONTENT)?{
//?Child?wants?todetermine?its?ownsize....?findouthow
//?big?it?should?be
resultSize?=?View.sUseZeroUnspecifiedMeasureSpec???0?:size;
resultMode?=?MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection?ResourceType
returnMeasureSpec.makeMeasureSpec(resultSize,?resultMode);
}
是不是容易理解了點(diǎn)呢。它做的就是前面所說的根據(jù)mode的類型,獲取相應(yīng)的size。根據(jù)父視圖的mode類型與子視圖的LayoutParams類型來決定子視圖所屬的mode,***再將獲取的size與mode通過MeasureSpec.makeMeasureSpec方法整合返回。***傳遞到measure中,這就是前面所說的widthMeasureSpec與heightMeasureSpec中包含的兩部分的值。整個過程為measureChildren->measureChild->getChildMeasureSpec->measure->onMeasure->setMeasuredDimension,所以通過measureChildren就可以對子視圖進(jìn)行測量計(jì)算。
layout
layout也是一樣的內(nèi)部會回調(diào)onLayout方法,該方法是用來確定子視圖的繪制位置,但這個方法在ViewGroup中是個抽象方法,所以如果要自定義的View是繼承ViewGroup的話就必須實(shí)現(xiàn)該方法。但如果是繼承View的話就不需要了,View中有一個空實(shí)現(xiàn)。而對子視圖位置的設(shè)置是通過View的layout方法通過傳遞計(jì)算出來的left、top、right與bottom值,而這些值一般都要借助View的寬高來計(jì)算,視圖的寬高則可以通過getMeasureWidth與getMeasureHeight方法獲取,這兩個方法獲取的值就是上面onMeasure中setMeasuredDimension傳遞的值,即子視圖測量的寬高。
getWidth、getHeight與getMeasureWidth、getMeasureHeight是不同的,前者是在onLayout之后才能獲取到的值,分別為left-right與top-bottom;而后者是在onMeasure之后才能獲取到的值。只不過這兩種獲取的值一般都是相同的,所以要注意調(diào)用的時機(jī)。
下面以定義一個把子視圖放置于父視圖的四個角的View為例:
@Override
protected?void?onLayout(boolean?changed,?intl,intt,intr,intb)?{
intcount=?getChildCount();
MarginLayoutParams?params;
intcl;
intct;
intcr;
intcb;
for(inti?=?0;?i?
Viewchild?=?getChildAt(i);
params?=?(MarginLayoutParams)?child.getLayoutParams();
if?(i?==?0)?{
//左上角
cl?=?params.leftMargin;
ct?=?params.topMargin;
}?elseif?(i?==?1)?{
//右上角
cl?=?getMeasuredWidth()?-?params.rightMargin?-?child.getMeasuredWidth();
ct?=?params.topMargin;
}?elseif?(i?==?2)?{
//左下角
cl?=?params.leftMargin;
ct?=?getMeasuredHeight()?-?params.bottomMargin?-?child.getMeasuredHeight()
-?params.topMargin;
}?else{
//右下角
cl?=?getMeasuredWidth()?-?params.rightMargin?-?child.getMeasuredWidth();
ct?=?getMeasuredHeight()?-?params.bottomMargin?-?child.getMeasuredHeight()
-?params.topMargin;
}
cr?=?cl?+?child.getMeasuredWidth();
cb?=?ct?+?child.getMeasuredHeight();
//確定子視圖在父視圖中放置的位置
child.layout(cl,?ct,?cr,?cb);
}
}
至于onMeasure的實(shí)現(xiàn)源碼我后面會給鏈接,如果要看效果圖的話,我后面也會貼出來,前面的那個驗(yàn)證碼的也是一樣
draw
draw是由dispatchDraw發(fā)動的,dispatchDraw是ViewGroup中的方法,在View是空實(shí)現(xiàn)。自定義View時不需要去管理該方法。而draw方法只在View中存在,ViewGoup做的只是在dispatchDraw中調(diào)用drawChild方法,而drawChild中調(diào)用的就是View的draw方法。那么我們來看下draw的源碼:
publicvoid?draw(Canvas?canvas)?{
final?intprivateFlags?=?mPrivateFlags;
final?boolean?dirtyOpaque?=?(privateFlags?&?PFLAG_DIRTY_MASK)?==?PFLAG_DIRTY_OPAQUE?&&
(mAttachInfo?==?null||?!mAttachInfo.mIgnoreDirtyState);
mPrivateFlags?=?(privateFlags?&?~PFLAG_DIRTY_MASK)?|?PFLAG_DRAWN;
/*
*?Draw?traversal?performs?several?drawing?steps?which?must?be?executed
*?inthe?appropriateorder:
*
*??????1.?Draw?the?background
*??????2.?If?necessary,?save?the?canvas'?layers?toprepareforfading
*??????3.?Draw?view's?content
*??????4.?Draw?children
*??????5.?If?necessary,?draw?the?fading?edges?andrestore?layers
*??????6.?Draw?decorations?(scrollbars?forinstance)
*/
//?Step?1,?draw?the?background,?if?needed
intsaveCount;
if?(!dirtyOpaque)?{
drawBackground(canvas);
}
//?skip?step?2?&?5?if?possible?(common?case)
final?intviewFlags?=?mViewFlags;
boolean?horizontalEdges?=?(viewFlags?&?FADING_EDGE_HORIZONTAL)?!=?0;
boolean?verticalEdges?=?(viewFlags?&?FADING_EDGE_VERTICAL)?!=?0;
if?(!verticalEdges?&&?!horizontalEdges)?{
//?Step?3,?draw?the?content
if?(!dirtyOpaque)?onDraw(canvas);
//?Step?4,?draw?the?children
dispatchDraw(canvas);
//?Overlay?ispartofthe?contentanddraws?beneath?Foreground
if?(mOverlay?!=?null&&?!mOverlay.isEmpty())?{
mOverlay.getOverlayView().dispatchDraw(canvas);
}
//?Step?6,?draw?decorations?(foreground,?scrollbars)
onDrawForeground(canvas);
//?we're?done...
return;
}
//省略2&5的情況
....
}
源碼已經(jīng)非常清晰了draw總共分為6步;
繪制背景
如果需要的話,保存layers
繪制自身文本
繪制子視圖
如果需要的話,繪制fading edges
繪制scrollbars
其中 第2步與第5步不是必須的。在第3步調(diào)用了onDraw方法來繪制自身的內(nèi)容,在View中是空實(shí)現(xiàn),這就是我們?yōu)槭裁丛谧远xView時必須要重寫該方法。而第4步調(diào)用了dispatchDraw對子視圖進(jìn)行繪制。還是以驗(yàn)證碼為例:
@Override
protected?void?onDraw(Canvas?canvas)?{
//繪制背景
mPaint.setColor(getResources().getColor(R.color.autoCodeBg));
canvas.drawRect(0,?0,?getMeasuredWidth(),?getMeasuredHeight(),?mPaint);
mPaint.getTextBounds(autoText,?0,?autoText.length(),?bounds);
//繪制文本
for(inti?=?0;?i?
mPaint.setColor(getResources().getColor(colorRes[random.nextInt(6)]));
canvas.drawText(autoText,?i,?i?+?1,?getWidth()?/?2?-?bounds.width()?/?2?+?i?*?bounds.width()?/?autoNum
,?bounds.height()?+?random.nextInt(getHeight()?-?bounds.height())
,?mPaint);
}
//繪制干擾點(diǎn)
for(intj?=?0;?j?
canvas.drawPoint(random.nextInt(getWidth()),?random.nextInt(getHeight()),?pointPaint);
}
//繪制干擾線
for(intk?=?0;?k?
intstartX?=?random.nextInt(getWidth());
intstartY?=?random.nextInt(getHeight());
intstopX?=?startX?+?random.nextInt(getWidth()?-?startX);
intstopY?=?startY?+?random.nextInt(getHeight()?-?startY);
linePaint.setColor(getResources().getColor(colorRes[random.nextInt(6)]));
canvas.drawLine(startX,?startY,?stopX,?stopY,?linePaint);
}
}
其實(shí)很簡單,就是一些繪制的業(yè)務(wù)邏輯。好了基本就到這里了,下面上傳一張示例的效果圖,與源碼鏈接
示例圖
對了還有自定義屬性,這里簡單說一下。自定義View時一般都要自定義屬性,所以都會在res/values/attr.xml中定義attr與declare-styleable,***在自定義View中通過TypedArray獲取。
【編輯推薦】
【責(zé)任編輯:枯木 TEL:(010)68476606】
點(diǎn)贊 0
總結(jié)
以上是生活随笔為你收集整理的android 自定义 child,Android自定义View的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c语言通讯录以及写入文件,学C三个月了,
- 下一篇: android scrollview 底