Android进阶知识:绘制流程(上)
1、前言
之前寫過一篇Android進階知識:事件分發與滑動沖突,主要研究的是關于Android中View事件分發與響應的流程。關于View除了事件傳遞流程還有一個很重要的就是View的繪制流程。一個Activity界面從啟動到繪制完成出現在眼前,這中間經歷了哪些過程。每一個View的大小、位置、形狀是怎么確定的。這些也都是自定義View的必備知識,所以也是很有必要來學習一下。因為要從Window初始化一直到子View繪制結束,涉及的內容有點多所以分成幾篇來寫。這第一篇先是一些基礎知識,主要包括View基礎,Android中的坐標系,MeasureSpec類和Window相關知識總結。
2、View基礎
2.1 View是什么?
View是Android中所有控件的基類,無論是TextView、ImageView還是Button、CheckBox都是繼承自View,可以說我們的應用界面就是由各式各樣的View組成的。
//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 { } 復制代碼2.2 ViewGroup是什么?
ViewGroup從名字就可以看出來表示一組View,它可以包含多個View。平時常用的LinearLayout、RelativeLayout、FrameLayout等都是繼承自ViewGroup,并且ViewGroup也是繼承自View。
//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組成,所以最終的結構會形成一個樹,如下圖。
View樹3、坐標系
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坐標系內的坐標。具體可以看下面這張圖。
坐標軸-  View中的方法: getLeft(): 獲取View的左邊到其父布局左邊的距離。 
 getTop(): 獲取View上邊到其父布局上邊的距離。
 getRight(): 獲取View右邊到其父布局左邊的距離。
 getBottom(): 獲取View下邊到其父布局上邊的距離。
-  MotionEvent中的方法: getX(): 獲取觸摸點距離所在View左邊的距離。 
 getY(): 獲取觸摸點距離所在View上邊的距離。
 getRawX(): 獲取觸摸點到整個屏幕左邊的距離。
 getRawY(): 獲取觸摸點到整個屏幕頂邊的距離。
4、MeasureSpec類
MeasureSpec是View類里的一個內部類,表示測量規格,它的作用是封裝了從父布局傳遞到子級的布局需求。每個MeasureSpec代表寬度或高度的要求。MeasureSpec由測量尺寸size和測量模式mode組成。
如上圖,MeasureSpec里代表了一個32位的int類型。高兩位表示測量模式,低30位表示測量大小。其中測量模式分為以下三種:
- UNSPECIFIED 模式
 UNSPECIFIED模式下,父View不會約束子View大小,一般用于系統內部例如ListView、ScrollView等。
- EXACTLY 模式
 EXACTLY模式下,父View為子View測量出所需要的大小,一般對應match_parent屬性,強制大小充滿父布局和父布局一樣大,或者具體數值,比如100dp。
- AT_MOST 模式
 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 000000public static final int EXACTLY = 1 << MODE_SHIFT;//AT_MOST模式:子View可以任意大,但不能超過父View的大小//2 << 30 結果為:10 00000000 00000000 00000000 000000public 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位falseif (sUseBrokenMakeMeasureSpec) {//兼容API17以前的,通過size+mode獲得一個32位int的MeasureSpecreturn 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*/@MeasureSpecModepublic static int getMode(int measureSpec) {//noinspection ResourceTypereturn (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 ");elsesb.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位falseif (sUseBrokenMakeMeasureSpec) {//兼容API17以前的,通過size+mode獲得一個32位int的MeasureSpecreturn size + mode;} else {//API17以后更加嚴格,采用位運算,防止溢出return (size & ~MODE_MASK) | (mode & MODE_MASK);}} 復制代碼makeMeasureSpec方法作用是根據size和mode創建一個MeasureSpec,可以看到根據API等級不同,實現也不同,API17之前是直接將size和mode相加,API17之后是采用位運算的方式,位運算集體看下面這張圖。
makeMeasureSpec方法 public static int getMode(int measureSpec) {//noinspection ResourceTypereturn (measureSpec & MODE_MASK);}public static int getSize(int measureSpec) {return (measureSpec & ~MODE_MASK);} 復制代碼getSize、getMode這倆方法分別是從測量規格MeasureSpec中獲取到對應測量大小和測量模式,具體計算看下圖。
getSize方法和getMode方法 關于MeasureSpec的內容就是這些,具體的運用等到看到繪制流程時再說。5、Window相關
Window是一個抽象的窗體的概念,每個Activity初始化默認會創建一個Window,界面上所有的View都會添加到這個Window上。Android中的Window類也是個抽象類,它的實現類是PhoneWindow。關于Window的知識點很多,這里就簡單介紹下和Window有關的概念,了解下與Window有關的類的作用,主要是幫助理解后面View加載到Window過程。
與Window相關的有這幾個類和他們的作用:
- Window:窗體抽象類。
- PhoneWinow:Window的具體實現類,對View進行管理。
- WindowManager:是個接口,用來管理Window。
- WindowManagerImpl:WindowManager的實現類,包含對Window各種操作(添加、刪除、更新)的方法。
- WindowManagerService(WMS):WindowManager的管理者,負責對窗口的管理、Surface的管理等。
- ViewRootImpl:所有View的根,將Window和View聯系起來。
- DecorView:頂級View。
6、總結
這一篇內容主要是梳理了一些繪制流程中要用到的基礎知識,比較簡單,為的是之后在看具體流程代碼的時候更加順利。下一篇就開始看具體繪制流程了。
轉載于:https://juejin.im/post/5d2ad297e51d45590a445bdd
總結
以上是生活随笔為你收集整理的Android进阶知识:绘制流程(上)的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: hadoop学习记录
- 下一篇: 基于灰度变换的图像增强
