Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 自定義控件_day01

自定義控件_day01

編輯:關於Android編程

簡介: 在自定義view的時候,其實很簡單,只需要知道3步驟: 1.測量——onMeasure():決定View的大小 2.布局——onLayout():決定View在ViewGroup中的位置 3.繪制——onDraw():如何繪制這個View。 而第3步的onDraw系統已經封裝的很好了,基本不用我們來操心,只需要專注到1,2兩個步驟就中好了。 第一步的測量,可以參考我之前的文章:(Android自定義視圖——onMeasure流程,MeasureSpec詳解) 而這篇文章就來談談第二步:“布局(Layout)”

知識點回顧

在談如何使用onLayout方法前,先簡單回憶一下知識點: View視圖結構: View視圖可以是單一的一個如TextView,也可以是一個視圖組(ViewGroup)如LinearLayout。 如圖:對於多View的視圖他的結構是樹形結構,最頂層是ViewGroup,ViewGroup下可能有多個ViewGroup或View。

 

\

 

這個樹的概念很重要,因為無論我們是在測量大小或是調整布局的時候都是從樹的頂端開始一層一層,一個分支一個分支的進行(樹形遞歸)。

Measure簡單回顧:

measure的作用就是為整個View樹計算實際的大小,而通過剛才對View樹的介紹知道,想計算整個View樹的大小,就需要遞歸的去計算每一

個子視圖的大小(Layout同理)。

對每一個視圖通過onMeasure方法的一系列測量流程後計算出實際的高(mMeasuredHeight)和寬(mMeasureWidth)傳入setMeasuredDimension()方法

完成單個View的測量,如果所測的視圖是ViewGroup則可以通過measureChild方法遞歸的計算其中的每一個子view。對於每個View的實際寬高

都是由父視圖和本身視圖決定的。

Layout(源碼分析)

Layout的作用就是為整個View樹計算實際的位置,而通過剛才對View樹的介紹知道,想計算整個View樹的位置,就需要遞歸的去計算每一個子視圖的位置(Measure同理)。

而確定這個位置很簡單,只需要mLeft,mTop,mRight,mBottom四個值(注意:這4個值是子View相對於父View的值,下面會詳細介紹)。

在代碼中如何設置這4個值呢?

首先,無論是系統提供的LinearLayout還是我們自定義的View視圖,他都需要繼承自ViewGroup類,之後必須要做的就是重寫onLayout方法(因為在onLayout

在ViewGroup中被定義為抽象方法)。

ViewGroup-onlayout:

@Override

protected abstract void onLayout(boolean changed, int l, int t, int r, int b);

onLayout被定義為抽象方法,所以在繼承ViewGroup時必須要重寫該方法(onMeasure不需要)。另外這個方法也被override標注,所以也是重寫的方法,他重

寫的是其父類view中的onLayout方法。

View-onlayout:

/**

* 當這個view和其子view被分配一個大小和位置時,被layout調用。

* @param changed 當前View的大小和位置改變了

* @param left 左部位置(相對於父視圖)

* @param top 頂部位置(相對於父視圖)

* @param right 右部位置(相對於父視圖)

* @param bottom 底部位置(相對於父視圖)

*/

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}

注解說:當這個view和其子view被分配一個大小和位置時,被layout調用。所以我們去看看layout中做了什麼。(注解沒有完全按照英文翻譯,並且有省略)

View-layout:

/**

* 給View和其所有子View分配大小和位置

*

* 這是布局的第二個階段(第一個階段是測量)。在這個階段中,每個父視圖需要去調用layout去為他所有的子視圖確定位置

* 派生的子類不應該重寫layout方法,應該重寫onLayout方法,在onlayout方法中應該去調用每一個view的layout

*/

public void layout(int l, int t, int r, int b) {

// 將當前視圖的左上右下記錄為old值(參數中傳入的為新的l,t,r,b值)

int oldL = mLeft;

int oldT = mTop;

int oldB = mBottom;

int oldR = mRight;

// setFrame方法的作用就是將新傳入的ltrb屬性賦值給View,然後判斷當前View大小和位置是否發生了變化並返回

boolean changed = setFrame(l, t, r, b);

if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {

// 調用onLayout回調方法,具體實現由重寫了onLayout方法的ViewGroup的子類去實現(後面詳細說明)

onLayout(changed, l, t, r, b);

mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

// 調用所有重寫了onLayoutChange監聽的方法,通知View大小和位置發生了改變

ListenerInfo li = mListenerInfo;

if (li != null && li.mOnLayoutChangeListeners != null) {

ArrayList listenersCopy =

(ArrayList)li.mOnLayoutChangeListeners.clone();

int numListeners = listenersCopy.size();

for (int i = 0; i < numListeners; ++i) {

listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);

}

}

}

mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;

}

在這段代碼中我們只要知道:如果視圖的大小和位置發生變化後,會調用我們前面分析過的onLayout方法。

對於onLayout方法的最終實現全部依靠我們在自定義ViewGroup類中重寫的onLayout去實現。

計算View位置:

在重寫的onLayout方法中,唯一的目的就是:

對當前視圖和其所有子View設置它們在父視圖中具體位置(確定這個位置就依靠mLeft,mTop,mRight,mBottom這四個值)

之前介紹過,mLeft,mTop,mRight,mBottom這四個值表示的是子view相對於父view的位置。下面我貼出我畫的圖看一下就明白了。

 

\

 

如圖,黃色區域是我們的父view,而中間的深色的區域就是我們的子view。

所以對於這個View來說,我列出它相對於父view的各個值是如何計算和相關函數:

mLeft,mTop,mRight,mBottom:

view.getLeft()——mLeft:子View左邊界到父view左邊界的距離

public final int getLeft() {

return mLeft;

}

view.getTop()——mTop:子View上邊界到父view上邊界的距離

view.getRight()——mRight:子View右邊界到父view左邊界的距離

view.getBottom()——mBottom:子View下邊距到父View上邊界的距離

視圖寬高:

視圖寬度 view.getWidth();子View的右邊界 - 子view的左邊界。

public final int getWidth() {

return mRight - mLeft;

}

視圖高度 view.getHeight() ;子View的下邊界 - 子view的上邊界。

public final int getHeight() {

return mBottom - mTop;

}

測量寬高:

view.getMeasuredWidth();measure過程中返回的mMeasuredWidth

public final int getMeasuredWidth() {

return mMeasuredWidth & MEASURED_SIZE_MASK;

}

view.getMeasuredHeight();measure過程中返回的mMeasuredHeight

public final int getMeasuredHeight() {

return mMeasuredHeight & MEASURED_SIZE_MASK;

}

最後介紹一下getWidth/Height和getMeasuredWidth/Height的區別:

getWidth,和getLeft等這些函數都是View相對於其父View的位置。而getMeasuredWidth,getMeasuredHeight是測量後該View的實際值(有點繞,下面摘錄一段jafsldkfj所寫的Blog中的解釋).

實際上在當屏幕可以包裹內容的時候,他們的值是相等的,只有當view超出屏幕後,才能看出他們的區別:

getMeasuredHeight()是實際View的大小,與屏幕無關,而getHeight的大小此時則是屏幕的大小。

當超出屏幕後,getMeasuredHeight()等於getHeight()加上屏幕之外沒有顯示的大小

在計算子View在父View中的位置時,主要就是應用上面這幾個函數。下面就來看看如何去重寫onLayout。

onLayout:

對於重寫onLayout的思路和重寫onMeasure相同:

如果只需要測量單個View,則單獨測量它自己就行。如果需要測量的View其下還有子View,則需要測量其所有的子View。

就以上面的View為例子,他最外面是一個黃色的父View,中間一個居中的深色子View。

我的思路如下:

如果想畫出一個View,就要計算它的l,t,r,b值。並傳遞到onlayout( l, t, r, b )中;

mRight = view.getWidth + mLeft;

mBottom = view.getHeight + mTop;

所以最後可以用如下形式傳入:onlayout( l, t, l+width, t+height );

剩下的任務就只需要知道它的mLeft值,mTop值,加上長、寬值就行了。

長寬值很簡單,使用getWidth/Height和getMeasuredWidth/Height都可以。

由於這個View需要居中顯示,剩下的問題就是如何計算該View的mLeft值和mTop值。我的思路如下:

r(父View的mRight) = mLeft + width + mLeft(因為左右間距一樣)

b(父View的mBottom) = mTop + height + mTop(因為上下間距一樣)

我的代碼如下:

[java] view plain copy print?

@Override

protected void onLayout(boolean changed, int l, int t, int r, int b) {

// 循環所有子View

for (int i=0; i

效果圖:

 

\
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved