一、View是什麼?
View是什麼了,每個人都有自己的理解。在Android的官方文檔中是這樣描述的:這個類表示了用戶界面的基本構建模塊。一個View佔用了屏幕上的一個矩形區域並且負責界面繪製和事件處理。View是用來構建用戶界面組件(Button,Textfields等等)的基類。ViewGroup子類是各種佈局的基類,它是個包含其他View(或其他ViewGroups)和定義這些View佈局參數的容器。
其實說白了,View就是一個矩形區域,我們可以在這個區域上定義自己的控件。
註明:有對系統回調不太瞭解的回頭看看回調,這樣有助於對文章的理解。
二、View創建的一個概述:
在API中對View的回調流程有以個詳細的描述,下面給出了原文翻譯:(翻譯有點倉促,大家多多包涵,有啥錯的地方麻煩告知下我,我好改過來)
1.Creation :創建
----Constructors(構造器)
There is a form of the constructor that arecalled when the view is created from code and a form that is called when theview is inflated from a layout file. The second form should parse and apply anyattributes defined in the layout file.在構造器中有個一個表單當View從代碼中創建和從Layout File 文件中創建時。第二個表單應該解析和應用一些在Layout File中定義的屬性。
---- onFinishInflate()
Called after a view and all of itschildren has been inflated from XML.當View和他的所有子View從XML中解析完成後調用。
2. Layout :佈局
----onMeasure(int, int)
Called to determine the size requirementsfor this view and all of its children. 確定View和它所有的子View要求的尺寸時調用
---- onLayout(boolean, int, int,int, int)
Calledwhen this view should assign a size and position to all of its children當這個View爲其所有的子View指派一個尺寸和位置時調用
---- onSizeChanged(int, int, int,int)
Calledwhen the size of this view has changed.當這個View的尺寸改變後調用
3. Drawing :繪製
---- onDraw(Canvas)
Calledwhen the view should render its content.當View給定其內容時調用
4.Event processing :事件流程
----onKeyDown(int, KeyEvent)
Calledwhen a new key event occurs.當一個新的鍵按下時
---- onKeyUp(int, KeyEvent)
Calledwhen a key up event occurs.當一個鍵彈起時
----onTrackballEvent(MotionEvent)
Calledwhen a trackball motion event occurs.當滾跡球事件發生時。
----onTouchEvent(MotionEvent)
Calledwhen a touch screen motion event occurs.當一個觸摸屏事件發生時。
5. Focus :焦點
---- onFocusChanged(boolean, int,Rect)
onFocusChanged(boolean,int, Rect)當View得到和失去焦點時調用
---- onWindowFocusChanged(boolean)
Called when the windowcontaining the view gains or loses focus.當Window包含的View得到或失去焦點時調用。
根據View裏面方法調用流程的概述,我們來重寫其中的幾個回調方法來直觀的瞭解下這個調用,具體代碼這裏就不貼了,代碼見測試包:DEMO_View調用流程.rar,調用的log顯示:
這樣大家就對View的調用有了個大概的認識,下面將針對View的標誌系統、View的的佈局參數系統等做一個簡單的描述。
三、View的標誌(Flag)系統
在一個系統中往往使用標誌來指示系統中的某些參數,這裏對View的標誌系統做一些簡單的介紹,這樣大家可以借鑑下,以後也可以用這種表示方法。
一般而言標誌都是成對出現的也就是表示相反兩個屬性,對於這種屬性的表示方法我們使用一位的0和1就可以表示。如果有多個成對屬性,如果每對屬性都用一個int值來標誌是不方便的。這種情況通常是用一個int的各個位來分別表示每個標誌,在處理器中有一個標誌位就是採用這種方式設計的。
我們先來看看位運算。位運算符包括: 與(&)、非(~)、或(|)、異或(^)
&: 當兩邊操作數的位同時爲1時,結果爲1,否則爲0。如1100&1010=1000
|: 當兩邊操作數的位有一邊爲1時,結果爲1,否則爲0。如1100|1010=1110
~: 0變1,1變0
^: 兩邊的位不同時,結果爲1,否則爲0.如1100^1010=0110
在View系統使用mViewFlags來表徵這些屬性,其設置的主要方法如下
void setFlag(int mask, int falg)
{
int old = mViewFlags;①
mViewFlags = (mViewFlags & ~mask) | (mask & falg);②
int changed = mViewFlags ^ old;// 獲取改變的位,方法是對改變的位置1③
... ...
}
其中mask指的是標誌位所在的位,falg表示的標誌位。下面舉個例子:
public static final int VISIBLE = 0x00000000;
public static final int INVISIBLE = 0x00000004;
public static final int GONE = 0x00000008;
static final int VISIBILITY_MASK = 0x0000000C;
其中VISIBLE和INVISIBLE和GONE就是標誌位,VISIBILITY_MASK是標誌位所在的位,也就有VISIBLE+INVISIBLE+GON=VISIBILITY_MASK。看不懂的把上面四個轉換爲二進制就看出來了。
爲什麼要使用VISIBILITY_MASK?會不會有些多餘呢?我們來看View中的計算公式:
mViewFlags = (mViewFlags & ~mask) | (mask & falg);
其中mViewFlags & ~mask是用來將mViewFlags中表示該標誌的位置零。mask & falg是用來獲得標誌位。舉個例子:
假設mViewFlags的二進制表示爲110000;flag爲INVISIBLE我們將上面的標誌位轉換爲二進制VISIBLE 0000、INVISIBLE 0100、GONE 1000、VISIBILITY_MASK 1100。
mViewFlags & ~mask=110000 & 0011 = 110000(上面所用的標誌位佔用的是最後四位,我們通過這個運算來將這個標誌位置零)。
mask & falg = 1100 & 0100 =0100(獲得標誌)。
110000 | 0100(通過或運算來計算出最後的標誌)。
一般而言:在多個同種類型的標誌中,通常使用0來作爲默認的標誌。關於上面的標誌系統的其他具體使用我們就不再深入,有興趣的可以自行深入,有啥好的想法在羣裏分享下。
四、MeasureSpec
在View系統中,指定寬和高,以及指定佈局的屬性,是由MeasureSpec來封裝的。下面是各個模式的標誌位表示。
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* Measure specification mode: The parent has determined an exact size
* for the child. The child is going to be given those bounds regardless
* of how big it wants to be.
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* Measure specification mode: The child can be as large as it wants up
* to the specified size.
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
在這個解析系統中是通過移位來存放更多的數據,現在每個數據標誌位都向左移動了30位。這樣表示一個View大小是很方便的,我們來看下面的方法:
public static int makeMeasureSpec(int size, int mode) {
return size + mode;
}
通過這個方法就可以製作一個含有兩個參數的int值,這個參數包含一個mode標誌和一個寬或高的表示。
我們通過如下方法來獲取到mode:
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
我們也可以用下面方法來獲取高或寬的數據表示:
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
五、幾個重要方法簡介
正如第二節寫的那個調用流程一樣,這幾個重要的方法是系統回調是調用的,同樣對於這幾個方法也是自定義組件的重要的方法。
在這節裏我們主要是瞭解這些方法的用途,以期在自定義組件時可以對這些方法得心應手。
5.1 onFinishInflate()
這個是當系統解析XML完成,並且將子View全部添加完成之後調用這個方法,我們通常重寫這個方法,在這個方法中查找並獲得子View引用,當然前提是這個View中有子View所以一般都是繼承ViewGroup時用這個方法比較多,比如抽屜效果中:
protected void onFinishInflate() {
mHandle = findViewById(mHandleId);
if (mHandle == null) {
throw new IllegalArgumentException("The handle attribute is must refer to an"
+ " existing child.");
}
mHandle.setOnClickListener(new DrawerToggler());
mContent = findViewById(mContentId);
if (mContent == null) {
throw new IllegalArgumentException("The content attribute is must refer to an"
+ " existing child.");
}
mContent.setVisibility(View.GONE);
}
通過重寫這個方法來獲取手柄的View和要顯示內容的View。
5.2 onMeasure(int, int)
測量這個View的高和寬。通過調用這個方法來設置View的測量後的高和寬,其最終調用的方法是:
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= MEASURED_DIMENSION_SET;
}
可見其最終是將高和寬保存在mMeasuredWidth、mMeasuredHeight這兩個參數中。
其實調用onMeasure(int, int)的方法的不是系統,而是
public final voidmeasure(int widthMeasureSpec, int heightMeasureSpec)
這個纔是系統回調的方法,然後通過這個方法調用onMeasure(int, int)方法,個人感覺這種設計就是把系統方法和用戶可以重寫的方法分離開,這樣避免一些不必要的錯誤。
在這個方法中主要是用來初始化各個子View的佈局參數,我們來看看抽屜中的實現:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
throw new RuntimeException("SlidingDrawer cannot have UNSPECIFIED dimensions");
}
final View handle = mHandle;
measureChild(handle, widthMeasureSpec, heightMeasureSpec);
if (mVertical) {
int height = heightSpecSize - handle.getMeasuredHeight() - mTopOffset;
mContent.measure(MeasureSpec.makeMeasureSpec(widthSpecSize, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
} else {
int width = widthSpecSize - handle.getMeasuredWidth() - mTopOffset;
mContent.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(heightSpecSize, MeasureSpec.EXACTLY));
}
setMeasuredDimension(widthSpecSize, heightSpecSize);
}
剛纔我們已經獲取到mHandle和mContent的引用,因爲onFinishInflate()方法調用在onMeasure(int, int)方法之前,所以這個不會出現nullPoint。我們可以看到在這個方法中主要就是爲mHandle和mContent指定了佈局參數。這裏用到了MeasureSpec。
5.3 onLayout(boolean, int, int,int, int)
onLayout是用來指定各個子View的位置,這個方法和上面方法類似,也不是真正的系統回調函數,真正的回調函數是Layout。這個方法的使用主要在ViewGroup中。這裏不再詳述。我們在ViewGroup講解時再去了解這個方法。
5.4 onSizeChanged(int, int, int,int)
這個是當View的大小改變時調用,這個也不再詳述,基本上用的也比較少。
5.5 onDraw(android.graphics.Canvas)
這個方法相信大家都不會陌生了,在我以前的博客裏也有這個方法的使用。當然那個比較入門,比較膚淺,呵呵。這裏我們深入進去,類似於onMeasure(int, int),其實這個方法是由draw(Canvas)方法調用的。在這個方法中有一個對這個方法的描述:
/*
* 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)
*/
我們可以看到:
首先是繪製背景
其次如果需要準備層之間的陰影
然後繪製內容(這個內容就是調用我們的onDraw方法)
再繪製children(dispatchDraw(canvas);)這個方法的調用主要實現在ViewGroup中,和繼承ViewGroup的組件中。
如果需要繪製層之間的陰影。
繪製裝飾,也就是scrollbars。
dispatchDraw(canvas);這也是一個重要的方法,用於繪製子組件用的。下面是抽屜中的實現方法。也比較簡單,大家自行閱讀下也就瞭解了。
protected void dispatchDraw(Canvas canvas) {
final long drawingTime = getDrawingTime();
final View handle = mHandle;
final boolean isVertical = mVertical;
drawChild(canvas, handle, drawingTime);
if (mTracking || mAnimating) {
final Bitmap cache = mContent.getDrawingCache();
if (cache != null) {
if (isVertical) {
canvas.drawBitmap(cache, 0, handle.getBottom(), null);
} else {
canvas.drawBitmap(cache, handle.getRight(), 0, null);
}
} else {
canvas.save();
canvas.translate(isVertical ? 0 : handle.getLeft() - mTopOffset,
isVertical ? handle.getTop() - mTopOffset : 0);
drawChild(canvas, mContent, drawingTime);
canvas.restore();
}
} else if (mExpanded) {
drawChild(canvas, mContent, drawingTime);
}
}
好了,這個就是View裏面的內容,關於事件監聽我們這裏就不再詳細描述,自定義組件的話,在寫完深入ViewGroup中會有一個專門的專題,而ViewGroup中也會去深化View中一些東西。
轉自:http://www.incoding.org/admin/archives/179.html