自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Android自定義View

移動(dòng)開(kāi)發(fā) Android
Android自定義View的詳細(xì)步驟是我們每一個(gè)Android開(kāi)發(fā)人員都必須掌握的技能,因?yàn)樵陂_(kāi)發(fā)中總會(huì)遇到自定義View的需求。為了提高自己的技術(shù)水平,自己就系統(tǒng)的去研究了一下,在這里寫(xiě)下一點(diǎn)心得,有不足之處希望大家及時(shí)指出。

前言

Android自定義View的詳細(xì)步驟是我們每一個(gè)Android開(kāi)發(fā)人員都必須掌握的技能,因?yàn)樵陂_(kāi)發(fā)中總會(huì)遇到自定義View的需求。為了提高自己的技術(shù)水平,自己就系統(tǒng)的去研究了一下,在這里寫(xiě)下一點(diǎn)心得,有不足之處希望大家及時(shí)指出。

流程

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

 

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

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

measure

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

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

  1. int widthSize = MeasureSpec.getSize(widthMeasureSpec); 
  2. int heightSize = MeasureSpec.getSize(heightMeasureSpec); 
  3. int widthMode = MeasureSpec.getMode(widthMeasureSpec); 
  4. int heightMode = MeasureSpec.getMode(heightMeasureSpec);  

獲取到的mode種類(lèi)分為以下三種:

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

setMeasuredDimension

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

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

  1. @Override 
  2.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
  3.         int widthSize = MeasureSpec.getSize(widthMeasureSpec); 
  4.         int heightSize = MeasureSpec.getSize(heightMeasureSpec); 
  5.         int widthMode = MeasureSpec.getMode(widthMeasureSpec); 
  6.         int heightMode = MeasureSpec.getMode(heightMeasureSpec); 
  7.         if (widthMode == MeasureSpec.EXACTLY) { 
  8.             //直接獲取精確的寬度 
  9.             width = widthSize; 
  10.         } else if (widthMode == MeasureSpec.AT_MOST) { 
  11.             //計(jì)算出寬度(文本的寬度+padding的大小) 
  12.             width = bounds.width() + getPaddingLeft() + getPaddingRight(); 
  13.         } 
  14.         if (heightMode == MeasureSpec.EXACTLY) { 
  15.             //直接獲取精確的高度 
  16.             height = heightSize; 
  17.         } else if (heightMode == MeasureSpec.AT_MOST) { 
  18.             //計(jì)算出高度(文本的高度+padding的大小) 
  19.             height = bounds.height() + getPaddingBottom() + getPaddingTop(); 
  20.         } 
  21.         //設(shè)置獲取的寬高 
  22.         setMeasuredDimension(width, height); 
  23.     }  

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

measureChildren

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

  1. protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) { 
  2.         final int size = mChildrenCount; 
  3.         final View[] children = mChildren; 
  4.         for (int i = 0; i < size; ++i) { 
  5.             final View child = children[i]; 
  6.             if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { 
  7.                 measureChild(child, widthMeasureSpec, heightMeasureSpec); 
  8.             } 
  9.         } 
  10.     }  

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

  1. protected void measureChild(View child, int parentWidthMeasureSpec, 
  2.             int parentHeightMeasureSpec) { 
  3.         final LayoutParams lp = child.getLayoutParams(); 
  4.         final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, 
  5.                 mPaddingLeft + mPaddingRight, lp.width); 
  6.         final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, 
  7.                 mPaddingTop + mPaddingBottom, lp.height); 
  8.         child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 
  9.     }  

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

  1. public static int getChildMeasureSpec(int spec, int padding, int childDimension) { 
  2.         int specMode = MeasureSpec.getMode(spec); 
  3.         int specSize = MeasureSpec.getSize(spec); 
  4.   
  5.         int size = Math.max(0, specSize - padding); 
  6.   
  7.         int resultSize = 0; 
  8.         int resultMode = 0; 
  9.   
  10.         switch (specMode) { 
  11.         // Parent has imposed an exact size on us 
  12.         case MeasureSpec.EXACTLY: 
  13.             if (childDimension >= 0) { 
  14.                 resultSize = childDimension; 
  15.                 resultMode = MeasureSpec.EXACTLY; 
  16.             } else if (childDimension == LayoutParams.MATCH_PARENT) { 
  17.                 // Child wants to be our size. So be it. 
  18.                 resultSize = size
  19.                 resultMode = MeasureSpec.EXACTLY; 
  20.             } else if (childDimension == LayoutParams.WRAP_CONTENT) { 
  21.                 // Child wants to determine its own size. It can't be 
  22.                 // bigger than us. 
  23.                 resultSize = size
  24.                 resultMode = MeasureSpec.AT_MOST; 
  25.             } 
  26.             break; 
  27.   
  28.         // Parent has imposed a maximum size on us 
  29.         case MeasureSpec.AT_MOST: 
  30.             if (childDimension >= 0) { 
  31.                 // Child wants a specific size... so be it 
  32.                 resultSize = childDimension; 
  33.                 resultMode = MeasureSpec.EXACTLY; 
  34.             } else if (childDimension == LayoutParams.MATCH_PARENT) { 
  35.                 // Child wants to be our size, but our size is not fixed. 
  36.                 // Constrain child to not be bigger than us. 
  37.                 resultSize = size
  38.                 resultMode = MeasureSpec.AT_MOST; 
  39.             } else if (childDimension == LayoutParams.WRAP_CONTENT) { 
  40.                 // Child wants to determine its own size. It can't be 
  41.                 // bigger than us. 
  42.                 resultSize = size
  43.                 resultMode = MeasureSpec.AT_MOST; 
  44.             } 
  45.             break; 
  46.   
  47.         // Parent asked to see how big we want to be 
  48.         case MeasureSpec.UNSPECIFIED: 
  49.             if (childDimension >= 0) { 
  50.                 // Child wants a specific size... let him have it 
  51.                 resultSize = childDimension; 
  52.                 resultMode = MeasureSpec.EXACTLY; 
  53.             } else if (childDimension == LayoutParams.MATCH_PARENT) { 
  54.                 // Child wants to be our size... find out how big it should 
  55.                 // be 
  56.                 resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size
  57.                 resultMode = MeasureSpec.UNSPECIFIED; 
  58.             } else if (childDimension == LayoutParams.WRAP_CONTENT) { 
  59.                 // Child wants to determine its own size.... find out how 
  60.                 // big it should be 
  61.                 resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size
  62.                 resultMode = MeasureSpec.UNSPECIFIED; 
  63.             } 
  64.             break; 
  65.         } 
  66.         //noinspection ResourceType 
  67.         return MeasureSpec.makeMeasureSpec(resultSize, resultMode); 
  68.     }  

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

layout

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

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

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

  1. @Override 
  2.     protected void onLayout(boolean changed, int l, int t, int r, int b) { 
  3.         int count = getChildCount(); 
  4.         MarginLayoutParams params; 
  5.          
  6.         int cl; 
  7.         int ct; 
  8.         int cr; 
  9.         int cb; 
  10.              
  11.         for (int i = 0; i < count; i++) { 
  12.             View child = getChildAt(i); 
  13.             params = (MarginLayoutParams) child.getLayoutParams(); 
  14.                  
  15.             if (i == 0) { 
  16.                 //左上角 
  17.                 cl = params.leftMargin; 
  18.                 ct = params.topMargin; 
  19.             } else if (i == 1) { 
  20.                 //右上角 
  21.                 cl = getMeasuredWidth() - params.rightMargin - child.getMeasuredWidth(); 
  22.                 ct = params.topMargin; 
  23.             } else if (i == 2) { 
  24.                 //左下角 
  25.                 cl = params.leftMargin; 
  26.                 ct = getMeasuredHeight() - params.bottomMargin - child.getMeasuredHeight() 
  27.                  - params.topMargin; 
  28.             } else { 
  29.                 //右下角 
  30.                 cl = getMeasuredWidth() - params.rightMargin - child.getMeasuredWidth(); 
  31.                 ct = getMeasuredHeight() - params.bottomMargin - child.getMeasuredHeight() 
  32.                  - params.topMargin; 
  33.             } 
  34.             cr = cl + child.getMeasuredWidth(); 
  35.             cb = ct + child.getMeasuredHeight(); 
  36.             //確定子視圖在父視圖中放置的位置 
  37.             child.layout(cl, ct, cr, cb); 
  38.         } 
  39.     }  

至于onMeasure的實(shí)現(xiàn)源碼我后面會(huì)給鏈接,如果要看效果圖的話(huà),我后面也會(huì)貼出來(lái),前面的那個(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方法。那么我們來(lái)看下draw的源碼:

  1. public void draw(Canvas canvas) { 
  2.         final int privateFlags = mPrivateFlags; 
  3.         final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE && 
  4.                 (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState); 
  5.         mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN; 
  6.           
  7.         /* 
  8.          * Draw traversal performs several drawing steps which must be executed 
  9.          * in the appropriate order
  10.          * 
  11.          *      1. Draw the background 
  12.          *      2. If necessary, save the canvas' layers to prepare for fading 
  13.          *      3. Draw view's content 
  14.          *      4. Draw children 
  15.          *      5. If necessary, draw the fading edges and restore layers 
  16.          *      6. Draw decorations (scrollbars for instance) 
  17.          */ 
  18.            
  19.         // Step 1, draw the background, if needed 
  20.         int saveCount; 
  21.   
  22.         if (!dirtyOpaque) { 
  23.             drawBackground(canvas); 
  24.         } 
  25.           
  26.         // skip step 2 & 5 if possible (common case
  27.         final int viewFlags = mViewFlags; 
  28.         boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0; 
  29.         boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0; 
  30.         if (!verticalEdges && !horizontalEdges) { 
  31.             // Step 3, draw the content 
  32.             if (!dirtyOpaque) onDraw(canvas); 
  33.               
  34.             // Step 4, draw the children 
  35.             dispatchDraw(canvas); 
  36.               
  37.             // Overlay is part of the content and draws beneath Foreground 
  38.             if (mOverlay != null && !mOverlay.isEmpty()) { 
  39.                             mOverlay.getOverlayView().dispatchDraw(canvas); 
  40.             } 
  41.                           
  42.             // Step 6, draw decorations (foreground, scrollbars) 
  43.             onDrawForeground(canvas); 
  44.                         
  45.             // we're done... 
  46.             return
  47.         } 
  48.         //省略2&5的情況 
  49.         .... 
  50. }     

源碼已經(jīng)非常清晰了draw總共分為6步;

  • 繪制背景
  • 如果需要的話(huà),保存layers
  • 繪制自身文本
  • 繪制子視圖
  • 如果需要的話(huà),繪制fading edges
  • 繪制scrollbars

其中 第2步與第5步不是必須的。在第3步調(diào)用了onDraw方法來(lái)繪制自身的內(nèi)容,在View中是空實(shí)現(xiàn),這就是我們?yōu)槭裁丛谧远xView時(shí)必須要重寫(xiě)該方法。而第4步調(diào)用了dispatchDraw對(duì)子視圖進(jìn)行繪制。還是以驗(yàn)證碼為例:

  1. @Override 
  2.     protected void onDraw(Canvas canvas) { 
  3.         //繪制背景 
  4.         mPaint.setColor(getResources().getColor(R.color.autoCodeBg)); 
  5.         canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint); 
  6.  
  7.         mPaint.getTextBounds(autoText, 0, autoText.length(), bounds); 
  8.         //繪制文本 
  9.         for (int i = 0; i < autoText.length(); i++) { 
  10.              mPaint.setColor(getResources().getColor(colorRes[random.nextInt(6)])); 
  11.             canvas.drawText(autoText, i, i + 1, getWidth() / 2 - bounds.width() / 2 + i * bounds.width() / autoNum 
  12.                     , bounds.height() + random.nextInt(getHeight() - bounds.height()) 
  13.                     , mPaint); 
  14.         } 
  15.   
  16.         //繪制干擾點(diǎn) 
  17.         for (int j = 0; j < 250; j++) { 
  18.              canvas.drawPoint(random.nextInt(getWidth()), random.nextInt(getHeight()), pointPaint); 
  19.         } 
  20.   
  21.         //繪制干擾線(xiàn) 
  22.         for (int k = 0; k < 20; k++) { 
  23.             int startX = random.nextInt(getWidth()); 
  24.             int startY = random.nextInt(getHeight()); 
  25.             int stopX = startX + random.nextInt(getWidth() - startX); 
  26.             int stopY = startY + random.nextInt(getHeight() - startY); 
  27.              linePaint.setColor(getResources().getColor(colorRes[random.nextInt(6)])); 
  28.             canvas.drawLine(startX, startY, stopX, stopY, linePaint); 
  29.         } 
  30.     }  

其實(shí)很簡(jiǎn)單,就是一些繪制的業(yè)務(wù)邏輯。好了基本就到這里了,下面上傳一張示例的效果圖,與源碼鏈接

示例圖 

 

 

對(duì)了還有自定義屬性,這里簡(jiǎn)單說(shuō)一下。自定義View時(shí)一般都要自定義屬性,所以都會(huì)在res/values/attr.xml中定義attr與declare-styleable,***在自定義View中通過(guò)TypedArray獲取。

責(zé)任編輯:龐桂玉 來(lái)源: segmentfault
相關(guān)推薦

2016-12-26 15:25:59

Android自定義View

2016-04-12 10:07:55

AndroidViewList

2017-03-02 13:33:19

Android自定義View

2012-05-18 10:52:20

TitaniumAndroid模塊自定義View模塊

2013-05-20 17:33:44

Android游戲開(kāi)發(fā)自定義View

2013-01-06 10:43:54

Android開(kāi)發(fā)View特效

2017-03-14 15:09:18

AndroidView圓形進(jìn)度條

2021-10-26 10:07:02

鴻蒙HarmonyOS應(yīng)用

2011-08-02 11:17:13

iOS開(kāi)發(fā) View

2013-04-01 14:35:10

Android開(kāi)發(fā)Android自定義x

2017-05-19 10:03:31

AndroidBaseAdapter實(shí)踐

2015-02-12 15:33:43

微信SDK

2010-02-07 14:02:16

Android 界面

2017-05-18 12:36:16

android萬(wàn)能適配器列表視圖

2013-01-09 17:22:38

Android開(kāi)發(fā)Camera

2015-02-12 15:38:26

微信SDK

2011-08-18 17:32:55

iPhone開(kāi)發(fā)Table Cell

2013-05-02 14:08:18

2014-12-10 10:37:45

Android自定義布局

2015-02-11 17:49:35

Android源碼自定義控件
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)