Android中怎么自定義View

本篇文章給大家分享的是有關(guān)Android中怎么自定義View,小編覺得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。

成都創(chuàng)新互聯(lián)公司專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于網(wǎng)站設(shè)計(jì)制作、成都網(wǎng)站設(shè)計(jì)、臺(tái)州網(wǎng)絡(luò)推廣、成都小程序開發(fā)、臺(tái)州網(wǎng)絡(luò)營(yíng)銷、臺(tái)州企業(yè)策劃、臺(tái)州品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運(yùn)營(yíng)等,從售前售中售后,我們都將竭誠(chéng)為您服務(wù),您的肯定,是我們最大的嘉獎(jiǎng);成都創(chuàng)新互聯(lián)公司為所有大學(xué)生創(chuàng)業(yè)者提供臺(tái)州建站搭建服務(wù),24小時(shí)服務(wù)熱線:18982081108,官方網(wǎng)址:muchs.cn

流程

在Android中對(duì)于布局的請(qǐng)求繪制是在Android  framework層開始處理的。繪制是從根節(jié)點(diǎn)開始,對(duì)布局樹進(jìn)行measure與draw。在RootViewImpl中的performTraversals展開。它所做的就是對(duì)需要的視圖進(jìn)行measure(測(cè)量視圖大小)、layout(確定視圖的位置)與draw(繪制視圖)。下面的圖能很好的展現(xiàn)視圖的繪制流程:  

Android中怎么自定義View

當(dāng)用戶調(diào)用requestLayout時(shí),只會(huì)觸發(fā)measure與layout,但系統(tǒng)開始調(diào)用時(shí)還會(huì)觸發(fā)draw

下面來詳細(xì)介紹這幾個(gè)流程。

measure

measure是View中的final型方法不可以進(jìn)行重寫。它是對(duì)視圖的大小進(jìn)行測(cè)量計(jì)算,但它會(huì)回調(diào)onMeasure方法,所以我們?cè)谧远xView的時(shí)候可以重寫onMeasure方法來對(duì)View進(jìn)行我們所需要的測(cè)量。它有兩個(gè)參數(shù)widthMeasureSpec與heightMeasureSpec。其實(shí)這兩個(gè)參數(shù)都包含兩部分,分別為size與mode。size為測(cè)量的大小而mode為視圖布局的模式

我們可以通過以下代碼分別獲?。?/p>

int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec);

獲取到的mode種類分為以下三種:

MODEEXPLAIN
UNSPECIFiED父視圖不對(duì)子視圖進(jìn)行約束,子視圖大小可以是任意大小,一般是對(duì)ListView、ScrollView等進(jìn)行自定義,一般用不到
EXACTLY父視圖對(duì)子視圖設(shè)定了一個(gè)精確的尺寸,子視圖不超過該尺寸,一般為精確的值例如200dp或者使用了match_parent
AT_MOST父視圖對(duì)子視圖指定了一***的尺寸,確保子視圖的所以內(nèi)容都剛好能在該尺寸中顯示出來,一般為wrap_content,這種父視圖不能獲取子視圖的大小,只能由子視圖自己去計(jì)算尺寸,這也是我們測(cè)量要實(shí)現(xiàn)的邏輯情況

setMeasuredDimension

通過以上邏輯獲取視圖的寬高,***要調(diào)用setMeasuredDimension方法將測(cè)量好的寬高進(jìn)行傳遞出去。其實(shí)最終是調(diào)用setMeasuredDimensionRaw方法對(duì)傳過來的值進(jìn)行屬性賦值。調(diào)用super.onMeasure()的調(diào)用邏輯也是一樣的。

下面以自定義一個(gè)驗(yàn)證碼的View為例,它的onMeasure方法如下:

@Override     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {         int widthSize = MeasureSpec.getSize(widthMeasureSpec);         int heightSize = MeasureSpec.getSize(heightMeasureSpec);         int widthMode = MeasureSpec.getMode(widthMeasureSpec);         int heightMode = MeasureSpec.getMode(heightMeasureSpec);         if (widthMode == MeasureSpec.EXACTLY) {             //直接獲取精確的寬度             width = widthSize;         } else if (widthMode == MeasureSpec.AT_MOST) {             //計(jì)算出寬度(文本的寬度+padding的大小)             width = bounds.width() + getPaddingLeft() + getPaddingRight();         }         if (heightMode == MeasureSpec.EXACTLY) {             //直接獲取精確的高度             height = heightSize;         } else if (heightMode == MeasureSpec.AT_MOST) {             //計(jì)算出高度(文本的高度+padding的大小)             height = bounds.height() + getPaddingBottom() + getPaddingTop();         }         //設(shè)置獲取的寬高         setMeasuredDimension(width, height);     }

可以對(duì)自定義View的layout_width與layout_height進(jìn)行設(shè)置不同的屬性,達(dá)到不同的mode類型,就可以看到不同的效果

measureChildren

如果你是對(duì)繼承ViewGroup的自定義View那么在進(jìn)行測(cè)量自身的大小時(shí)還要測(cè)量子視圖的大小。一般通過measureChildren(int  widthMeasureSpec, int heightMeasureSpec)方法來測(cè)量子視圖的大小。

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {         final int size = mChildrenCount;         final View[] children = mChildren;         for (int i = 0; i < size; ++i) {             final View child = children[i];             if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {                 measureChild(child, widthMeasureSpec, heightMeasureSpec);             }         }     }

通過上面的源碼會(huì)發(fā)現(xiàn),它其實(shí)是遍歷每一個(gè)子視圖,如果該子視圖不是隱藏的就調(diào)用measureChild方法,那么來看下measureChild源碼:

protected void measureChild(View child, int parentWidthMeasureSpec,             int parentHeightMeasureSpec) {         final LayoutParams lp = child.getLayoutParams();         final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,                 mPaddingLeft + mPaddingRight, lp.width);         final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,                 mPaddingTop + mPaddingBottom, lp.height);         child.measure(childWidthMeasureSpec, childHeightMeasureSpec);     }

會(huì)發(fā)現(xiàn)它首先調(diào)用了getChildMeasureSpec方法來分別獲取寬高,***再調(diào)用的就是View的measure方法,而通過前面的分析我們已經(jīng)知道它做的就是對(duì)視圖大小的計(jì)算。而對(duì)于measure中的參數(shù)是通過getChildMeasureSpec獲取,再來看下其源碼:

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {         int specMode = MeasureSpec.getMode(spec);         int specSize = MeasureSpec.getSize(spec);           int size = Math.max(0, specSize - padding);           int resultSize = 0;         int resultMode = 0;           switch (specMode) {         // Parent has imposed an exact size on us         case MeasureSpec.EXACTLY:             if (childDimension >= 0) {                 resultSize = childDimension;                 resultMode = MeasureSpec.EXACTLY;             } 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.                 resultSize = size;                 resultMode = MeasureSpec.AT_MOST;             }             break;           // 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;           // Parent asked to see how big we want to be         case MeasureSpec.UNSPECIFIED:             if (childDimension >= 0) {                 // Child wants a specific size... let him have it                 resultSize = childDimension;                 resultMode = MeasureSpec.EXACTLY;             } else if (childDimension == LayoutParams.MATCH_PARENT) {                 // Child wants to be our size... find out how big it should                 // be                 resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;                 resultMode = MeasureSpec.UNSPECIFIED;             } else if (childDimension == LayoutParams.WRAP_CONTENT) {                 // Child wants to determine its own size.... find out how                 // big it should be                 resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;                 resultMode = MeasureSpec.UNSPECIFIED;             }             break;         }         //noinspection ResourceType         return MeasureSpec.makeMeasureSpec(resultSize, resultMode);     }

是不是容易理解了點(diǎn)呢。它做的就是前面所說的根據(jù)mode的類型,獲取相應(yīng)的size。根據(jù)父視圖的mode類型與子視圖的LayoutParams類型來決定子視圖所屬的mode,***再將獲取的size與mode通過MeasureSpec.makeMeasureSpec方法整合返回。***傳遞到measure中,這就是前面所說的widthMeasureSpec與heightMeasureSpec中包含的兩部分的值。整個(gè)過程為measureChildren->measureChild->getChildMeasureSpec->measure->onMeasure->setMeasuredDimension,所以通過measureChildren就可以對(duì)子視圖進(jìn)行測(cè)量計(jì)算。

layout

layout也是一樣的內(nèi)部會(huì)回調(diào)onLayout方法,該方法是用來確定子視圖的繪制位置,但這個(gè)方法在ViewGroup中是個(gè)抽象方法,所以如果要自定義的View是繼承ViewGroup的話就必須實(shí)現(xiàn)該方法。但如果是繼承View的話就不需要了,View中有一個(gè)空實(shí)現(xiàn)。而對(duì)子視圖位置的設(shè)置是通過View的layout方法通過傳遞計(jì)算出來的left、top、right與bottom值,而這些值一般都要借助View的寬高來計(jì)算,視圖的寬高則可以通過getMeasureWidth與getMeasureHeight方法獲取,這兩個(gè)方法獲取的值就是上面onMeasure中setMeasuredDimension傳遞的值,即子視圖測(cè)量的寬高。

getWidth、getHeight與getMeasureWidth、getMeasureHeight是不同的,前者是在onLayout之后才能獲取到的值,分別為left-right與top-bottom;而后者是在onMeasure之后才能獲取到的值。只不過這兩種獲取的值一般都是相同的,所以要注意調(diào)用的時(shí)機(jī)。

下面以定義一個(gè)把子視圖放置于父視圖的四個(gè)角的View為例:

@Override     protected void onLayout(boolean changed, int l, int t, int r, int b) {         int count = getChildCount();         MarginLayoutParams params;                  int cl;         int ct;         int cr;         int cb;                      for (int i = 0; i < count; i++) {             View child = getChildAt(i);             params = (MarginLayoutParams) child.getLayoutParams();                              if (i == 0) {                 //左上角                 cl = params.leftMargin;                 ct = params.topMargin;             } else if (i == 1) {                 //右上角                 cl = getMeasuredWidth() - params.rightMargin - child.getMeasuredWidth();                 ct = params.topMargin;             } else if (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)源碼我后面會(huì)給鏈接,如果要看效果圖的話,我后面也會(huì)貼出來,前面的那個(gè)驗(yàn)證碼的也是一樣

draw

draw是由dispatchDraw發(fā)動(dòng)的,dispatchDraw是ViewGroup中的方法,在View是空實(shí)現(xiàn)。自定義View時(shí)不需要去管理該方法。而draw方法只在View中存在,ViewGoup做的只是在dispatchDraw中調(diào)用drawChild方法,而drawChild中調(diào)用的就是View的draw方法。那么我們來看下draw的源碼:

public void draw(Canvas canvas) {         final int privateFlags = 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          * in the appropriate order:          *          *      1. Draw the background          *      2. If necessary, save the canvas' layers to prepare for fading          *      3. Draw view's content          *      4. Draw children          *      5. If necessary, draw the fading edges and restore layers          *      6. Draw decorations (scrollbars for instance)          */                    // Step 1, draw the background, if needed         int saveCount;           if (!dirtyOpaque) {             drawBackground(canvas);         }                   // skip step 2 & 5 if possible (common case)         final int viewFlags = 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 is part of the content and draws 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時(shí)必須要重寫該方法。而第4步調(diào)用了dispatchDraw對(duì)子視圖進(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 (int i = 0; i < autoText.length(); 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 (int j = 0; j < 250; j++) {              canvas.drawPoint(random.nextInt(getWidth()), random.nextInt(getHeight()), pointPaint);         }           //繪制干擾線         for (int k = 0; k < 20; k++) {             int startX = random.nextInt(getWidth());             int startY = random.nextInt(getHeight());             int stopX = startX + random.nextInt(getWidth() - startX);             int stopY = startY + random.nextInt(getHeight() - startY);              linePaint.setColor(getResources().getColor(colorRes[random.nextInt(6)]));             canvas.drawLine(startX, startY, stopX, stopY, linePaint);         }     }

以上就是Android中怎么自定義View,小編相信有部分知識(shí)點(diǎn)可能是我們?nèi)粘9ぷ鲿?huì)見到或用到的。希望你能通過這篇文章學(xué)到更多知識(shí)。更多詳情敬請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。

網(wǎng)站欄目:Android中怎么自定義View
標(biāo)題鏈接:http://muchs.cn/article22/jcggcc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站排名、用戶體驗(yàn)、面包屑導(dǎo)航、云服務(wù)器、軟件開發(fā)、營(yíng)銷型網(wǎng)站建設(shè)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

h5響應(yīng)式網(wǎng)站建設(shè)