以前寫過一篇Android進階知識:事件分發與滑動衝突,主要研究的是關於Android
中View
事件分發與響應的流程。關於View
除了事件傳遞流程還有一個很重要的就是View
的繪製流程。一個Activity
界面從啓動到繪製完成出如今眼前,這中間經歷了哪些過程。每個View
的大小、位置、形狀是怎麼肯定的。這些也都是自定義View
的必備知識,因此也是頗有必要來學習一下。由於要從Window
初始化一直到子View
繪製結束,涉及的內容有點多因此分紅幾篇來寫。這第一篇先是一些基礎知識,主要包括View
基礎,Android
中的座標系,MeasureSpec
類和Window
相關知識總結。bash
View
是Android
中全部控件的基類,不管是TextView
、ImageView
仍是Button
、CheckBox
都是繼承自View
,能夠說咱們的應用界面就是由各式各樣的View
組成的。app
//ImageView繼承了View
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
}
//ImageView繼承了View
public class ImageView extends View {
}
//ImageView繼承了TextView
public class Button extends TextView {
}
//ImageView繼承了CompoundButton
public class CheckBox extends CompoundButton {
}
//ImageView繼承了Button
public abstract class CompoundButton extends Button implements Checkable {
}
複製代碼
ViewGroup
從名字就能夠看出來表示一組View
,它能夠包含多個View
。平時經常使用的LinearLayout
、RelativeLayout
、FrameLayout
等都是繼承自ViewGroup
,而且ViewGroup
也是繼承自View
。ide
//LinearLayout繼承了ViewGroup
public class LinearLayout extends ViewGroup {
}
//RelativeLayout繼承了ViewGroup
public class RelativeLayout extends ViewGroup {
}
//FrameLayout繼承了ViewGroup
public class FrameLayout extends ViewGroup {
}
//ViewGroup繼承了View
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
}
複製代碼
又由於不一樣的應用界面都由不一樣的View
或ViewGroup
組成,因此最終的結構會造成一個樹,以下圖。佈局
Android
中有兩種座標系,一個Android
座標系,一個是View
座標系。Android
座標系以屏幕左上角爲原點,向右爲x軸正方向,向下爲y軸正方向。View
座標系以父View
的左上角爲原點,一樣向右爲x軸正方向,向下爲y軸正方向。Android
中也提供了獲取所在座標的方法。在View
中,有mLeft
、mTop
、mRight
、mBottom
四個成員變量而且提供對應的get
獲取方法,這四個值就存儲了這個View
相對於父佈局的所在位置座標。除此以外,在View
的事件響應方法onTouchEvent
中會返回一個MotionEvent
對象,這個對象提供了兩對方法getX
、getY
和getRawX
、getRawY
,分別得到當前觸摸點在所在View
內的座標和當前觸摸點在Android
座標系內的座標。具體能夠看下面這張圖。post
View中的方法:學習
getLeft(): 獲取View的左邊到其父佈局左邊的距離。
getTop(): 獲取View上邊到其父佈局上邊的距離。
getRight(): 獲取View右邊到其父佈局左邊的距離。
getBottom(): 獲取View下邊到其父佈局上邊的距離。ui
MotionEvent中的方法:this
getX(): 獲取觸摸點距離所在View左邊的距離。
getY(): 獲取觸摸點距離所在View上邊的距離。
getRawX(): 獲取觸摸點到整個屏幕左邊的距離。
getRawY(): 獲取觸摸點到整個屏幕頂邊的距離。spa
MeasureSpec
是View
類裏的一個內部類,表示測量規格,它的做用是封裝了從父佈局傳遞到子級的佈局需求。每一個MeasureSpec
表明寬度或高度的要求。MeasureSpec
由測量尺寸size
和測量模式mode
組成。3d
如上圖,MeasureSpec
裏表明了一個32位的int
類型。高兩位表示測量模式,低30位表示測量大小。其中測量模式分爲如下三種:
UNSPECIFIED
模式下,父View
不會約束子View
大小,通常用於系統內部例如ListView
、ScrollView
等。EXACTLY
模式下,父View
爲子View
測量出所須要的大小,通常對應match_parent
屬性,強制大小充滿父佈局和父佈局同樣大,或者具體數值,好比100dp
。AT_MOST
模式下,父View
爲子View
提供一個最大的尺寸大小,子View
大小能夠任意由本身決定,可是最大不能超過這個尺寸,通常對應wrap_content
屬性,自適應大小。MeasureSpec
類的源碼不是不少,具體以下。
public static class MeasureSpec {
//移位大小
private static final int MODE_SHIFT = 30;
//0x3二進制爲11,11 << 30 結果爲:11 00000000 00000000 00000000 000000 (30個0)用於後面作與運算
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
//UNSPECIFIED模式:父View沒有對子View施加任何約束。它能夠是任意大小。
//0 << 30 結果爲:00 00000000 00000000 00000000 000000
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
//EXACTLY模式:父View已經爲子View肯定了確切的大小。無論子View想要多大,他都會獲得這些界限。
//1 << 30 結果爲:01 00000000 00000000 00000000 000000
public static final int EXACTLY = 1 << MODE_SHIFT;
//AT_MOST模式:子View能夠任意大,但不能超過父View的大小
//2 << 30 結果爲:10 00000000 00000000 00000000 000000
public static final int AT_MOST = 2 << MODE_SHIFT;
/**
* 根據size和mode建立一個MeasureSpec
*/
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
//sUseBrokenMakeMeasureSpec的值API小於17位true大於17位false
if (sUseBrokenMakeMeasureSpec) {
//兼容API17之前的,經過size+mode得到一個32位int的MeasureSpec
return size + mode;
} else {
//API17之後更加嚴格,採用位運算,防止溢出
//例如: size:4 mode:AT_MOST
//~MODE_MASK爲: 00 11111111 11111111 11111111 111111
//size & ~MODE_MASK:00 00000000 00000000 00000000 000100
//mode : 10 00000000 00000000 00000000 000000
//MODE_MASK: 11 00000000 00000000 00000000 000000
//mode & MODE_MASK: 10 00000000 00000000 00000000 000000
//(size & ~MODE_MASK) | (mode & MODE_MASK) 最終結果:
// 10 00000000 00000000 00000000 000100
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
/**
* Like {@link #makeMeasureSpec(int, int)}, but any spec with a mode of UNSPECIFIED
* will automatically get a size of 0. Older apps expect this.
*
* @hide internal use only for compatibility with system widgets and older apps
*/
public static int makeSafeMeasureSpec(int size, int mode) {
if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
return 0;
}
return makeMeasureSpec(size, mode);
}
/**
* 從MeasureSpec中獲取mode
*/
@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
/**
* 從MeasureSpec中獲取size
*/
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
static int adjust(int measureSpec, int delta) {
final int mode = getMode(measureSpec);
int size = getSize(measureSpec);
if (mode == UNSPECIFIED) {
// No need to adjust size for UNSPECIFIED mode.
return makeMeasureSpec(size, UNSPECIFIED);
}
size += delta;
if (size < 0) {
Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
") spec: " + toString(measureSpec) + " delta: " + delta);
size = 0;
}
return makeMeasureSpec(size, mode);
}
/**
* Returns a String representation of the specified measure
* specification.
*
* @param measureSpec the measure specification to convert to a String
* @return a String with the following format: "MeasureSpec: MODE SIZE"
*/
public static String toString(int measureSpec) {
int mode = getMode(measureSpec);
int size = getSize(measureSpec);
StringBuilder sb = new StringBuilder("MeasureSpec: ");
if (mode == UNSPECIFIED)
sb.append("UNSPECIFIED ");
else if (mode == EXACTLY)
sb.append("EXACTLY ");
else if (mode == AT_MOST)
sb.append("AT_MOST ");
else
sb.append(mode).append(" ");
sb.append(size);
return sb.toString();
}
}
複製代碼
源碼裏已經加了註釋了,這裏再來講一下,首先是幾個成員變量。
MODE_SHIFT
表示移位大小。MODE_MASK
是進行位運算的遮罩。UNSPECIFIED
、EXACTLY
、AT_MOST
分別對應三種測量模式。下圖是這幾個值的二進制表示。
接着來看makeMeasureSpec
、getSize
、getMode
這幾個主要的方法。
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
//sUseBrokenMakeMeasureSpec的值API小於17位true大於17位false
if (sUseBrokenMakeMeasureSpec) {
//兼容API17之前的,經過size+mode得到一個32位int的MeasureSpec
return size + mode;
} else {
//API17之後更加嚴格,採用位運算,防止溢出
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
複製代碼
makeMeasureSpec
方法做用是根據size
和mode
建立一個MeasureSpec
,能夠看到根據API
等級不一樣,實現也不一樣,API17
以前是直接將size
和mode
相加,API17
以後是採用位運算的方式,位運算集體看下面這張圖。
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
複製代碼
getSize
、getMode
這倆方法分別是從測量規格MeasureSpec
中獲取到對應測量大小和測量模式,具體計算看下圖。
MeasureSpec
的內容就是這些,具體的運用等到看到繪製流程時再說。
Window
是一個抽象的窗體的概念,每一個Activity
初始化默認會建立一個Window
,界面上全部的View
都會添加到這個Window
上。Android
中的Window
類也是個抽象類,它的實現類是PhoneWindow
。關於Window
的知識點不少,這裏就簡單介紹下和Window
有關的概念,瞭解下與Window
有關的類的做用,主要是幫助理解後面View
加載到Window
過程。
與Window
相關的有這幾個類和他們的做用:
Window
又由
WindowManager
來管理,進而會經過
ViewRootImpl
中的
IWindowSession
進行
Binder
通訊,最終經過
WMS
把窗口
Surface
進行繪製到屏幕上。下面這張圖簡單描述了這個邏輯。
這一篇內容主要是梳理了一些繪製流程中要用到的基礎知識,比較簡單,爲的是以後在看具體流程代碼的時候更加順利。下一篇就開始看具體繪製流程了。