Android自定義控件常用方法總結
inflate
inflate方法常常用來解析一個xml布局檔案,在自定義組合式控件中常常使用,使用的姿勢包括:
View.inflate(context, resource, root)
LayoutInflater.from(getContext()).inflate(resource, root);
而View.inflate其實還是調用的LayoutInflater去解析一個xml:
public static View inflate(Context context, int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
是以這兩種姿勢沒啥差別,這裡來讨論一下inflate(resouce, root)的傳回值,參數resource是布局資源,root是傳入的一個根節點。如果root傳入一個null,inflate就會解析resource對應的xml,傳回這個xml中的根節點,如果root傳入不為null,inflate會解析這個xml布局并且添加到根節點root下,然後傳回根節點root。
另外還有帶三個參數的inflate方法:
inflate(int resource, ViewGroup root, boolean attachToRoot)
這裡多了一個參數attachToRoot,如果root為null,則傳回解析後的xml布局中的根節點;如果root不為null,attachToRoot為true,inflate會解析這個xml布局并且添加到根節點root下,然後傳回根節點root;如果root不為null,attachToRoot為false,inflate會解析這個xml布局但不會添加到根節點root下,然後傳回解析後的xml布局中的根節點,這時候root的作用隻是為xml中的根節點提供布局參數的屬性,因為xml中的根節點不知道自己的父容器是誰,是以如果沒有人給它提供的話,它的布局參數就會失效。
onFinishInflate
onFinishInflate是當所有的孩子都解析完後的一個調用。比如我們自定義一個ViewGroup,想要去找到孩子做一些設定,這時候如果在自定義ViewGroup的構造函數去findViewById的話,會傳回一個null,因為此時孩子還沒有解析好,也就是還沒有生出來。這時候我們可以去覆寫onFinishInflate,當孩子解析好後再去find。
requestLayout
關于requestLayout的介紹比較多,requestLayout()方法會觸發measure過程和layout過程,不會調用draw過程,也不會重新繪制任何View包括該調用者本身
onSizeChange(int w, int h, int oldw, int oldh)
onSizeChange是控件的大小發生變化的時候的調用,它的調用軌迹是layout->setFrame->sizeChange->onSizeChange。當控件第一次布局時肯定會被調用到,我們覆寫該方法可以擷取到控件的大小。是以這個方法通常被用來在裡面初始化跟控件大小相關的成員變量。
invalidate
invalidate使用的非常頻繁,它會觸發View的重新繪制,也就是繪制流程的draw過程,但不會調用測量和布局過程
postInvalidate
我們都知道Android的UI是單線程模型,隻能在主線程更新UI,是以我們隻能在主線程調用invalidate,如果想要在子線程更新ui,可以使用handler發送一個msg到主線程,然後在處理msg的時候去調用invalidate。另外,我們可以直接調用postInvalidate去在子線程更新UI,postInvalidate内部實作也是使用handler來做發送msg到主線程然後調用invalidate。
setWillNotDraw
自定義ViewGroup通常是不會去繪制自己的,如果大家重寫ViewGroup裡面的draw方法或者onDraw方法會發現它們根本就不會被調用到。但是如果給你的ViewGroup設定一個背景,就會發現draw方法和onDraw方法又都會走了。
我們知道ViewGroup本身是一個View,它的繪制是被其父容器發起的,具體的位置是在ViewGroup中的drawChild方法:
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
注意這裡的draw方法是帶三個參數的,與我們通常講的帶一個參數的draw方法不一樣。在View類中找到帶三個參數的draw方法,發現裡面有這麼一段代碼:
if (!hasDisplayList) {
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
} else {
draw(canvas);
}
}
從這裡我們可以看出一點端倪,通常一個ViewGroup預設是會跳過繪制的,也即 (mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW 會傳回一個true,那麼會直接走dispatchDraw方法去畫它自己的孩子去了,并不會調用帶一個參數的 draw(canvas) ,但是當這個ViewGroup有背景或者setWillNotDraw(false)是,就會走 draw(canvas) 方法。
是以如果我們自定義一個ViewGroup并且想要實作它本身的繪制的話,就可以給它設定一個背景或者調用 setWillNotDraw(false)
onAttachedToWindow
onAttachedToWindow是當一個View綁定到window上時的調用,根據View類裡面的對這個方法的注釋,onAttachedToWindow肯定會在onDraw方法之前調用。
在自定義控件裡面,我們可以在onAttachedToWindow注冊一些廣播接收器,觀察者或者開啟一些任務,大家可以參考TextClock的裡面的實作。
onDetachedFromWindow
onDetachedFromWindow對應于onAttachedToWindow,是當一個View從window上移除時的一個調用。如果在onAttachedWindow裡面注冊了一些監聽,那麼通常就要在onDetachedFromWindow裡面反注冊。
ViewTreeObserver
ViewTreeObserver是視圖樹的觀察者,監聽一些視圖樹的全局變化,這些全局變化包括整個視圖樹的布局,開始繪制,觸摸模式的變化等。我們不能直接初始化ViewTreeObserver的對象,需要通過getViewTreeObserver()去擷取。
ViewTreeObserver.OnGlobalLayoutListener
當在一個視圖樹中全局布局發生改變或者視圖樹中的某個視圖的可視狀态發生改變的監聽器,一般的使用姿勢是:
getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
getViewTreeObserver().removeGlobalOnLayoutListener(this);
//do something you like
//for example, get view width or height height
}
});
ViewTreeObserver.OnPreDrawListener
當一個視圖樹将要繪制時的監聽器,一般的使用姿勢是:
getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
//do something before draw
//for example, request a new layout
return true;
}
});