Android進階知識:繪製流程(上)

一、前言

以前寫過一篇Android進階知識:事件分發與滑動衝突,主要研究的是關於AndroidView事件分發與響應的流程。關於View除了事件傳遞流程還有一個很重要的就是View的繪製流程。一個Activity界面從啓動到繪製完成出如今眼前,這中間經歷了哪些過程。每個View的大小、位置、形狀是怎麼肯定的。這些也都是自定義View的必備知識,因此也是頗有必要來學習一下。由於要從Window初始化一直到子View繪製結束,涉及的內容有點多因此分紅幾篇來寫。這第一篇先是一些基礎知識,主要包括View基礎,Android中的座標系,MeasureSpec類和Window相關知識總結。bash

二、View基礎

2.1 View是什麼?

ViewAndroid中全部控件的基類,不管是TextViewImageView仍是ButtonCheckBox都是繼承自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 {
}
複製代碼

2.2 ViewGroup是什麼?

ViewGroup從名字就能夠看出來表示一組View,它能夠包含多個View。平時經常使用的LinearLayoutRelativeLayoutFrameLayout等都是繼承自ViewGroup,而且ViewGroup也是繼承自Viewide

//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 {
}
複製代碼

又由於不一樣的應用界面都由不一樣的ViewViewGroup組成,因此最終的結構會造成一個樹,以下圖。佈局

View樹

三、座標系

Android中有兩種座標系,一個Android座標系,一個是View座標系。Android座標系以屏幕左上角爲原點,向右爲x軸正方向,向下爲y軸正方向。View座標系以父View的左上角爲原點,一樣向右爲x軸正方向,向下爲y軸正方向。Android中也提供了獲取所在座標的方法。在View中,有mLeftmTopmRightmBottom四個成員變量而且提供對應的get獲取方法,這四個值就存儲了這個View相對於父佈局的所在位置座標。除此以外,在View的事件響應方法onTouchEvent中會返回一個MotionEvent對象,這個對象提供了兩對方法getXgetYgetRawXgetRawY,分別得到當前觸摸點在所在View內的座標和當前觸摸點在Android座標系內的座標。具體能夠看下面這張圖。post

座標軸

  • View中的方法:學習

    getLeft(): 獲取View的左邊到其父佈局左邊的距離。
    getTop(): 獲取View上邊到其父佈局上邊的距離。
    getRight(): 獲取View右邊到其父佈局左邊的距離。
    getBottom(): 獲取View下邊到其父佈局上邊的距離。ui

  • MotionEvent中的方法:this

    getX(): 獲取觸摸點距離所在View左邊的距離。
    getY(): 獲取觸摸點距離所在View上邊的距離。
    getRawX(): 獲取觸摸點到整個屏幕左邊的距離。
    getRawY(): 獲取觸摸點到整個屏幕頂邊的距離。spa

四、MeasureSpec類

MeasureSpecView類裏的一個內部類,表示測量規格,它的做用是封裝了從父佈局傳遞到子級的佈局需求。每一個MeasureSpec表明寬度或高度的要求。MeasureSpec由測量尺寸size和測量模式mode組成。3d

如上圖,MeasureSpec裏表明了一個32位的int類型。高兩位表示測量模式,低30位表示測量大小。其中測量模式分爲如下三種:

  • UNSPECIFIED 模式
    UNSPECIFIED模式下,父View不會約束子View大小,通常用於系統內部例如ListViewScrollView等。
  • 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 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是進行位運算的遮罩。
  • UNSPECIFIEDEXACTLYAT_MOST分別對應三種測量模式。

下圖是這幾個值的二進制表示。

接着來看makeMeasureSpecgetSizegetMode這幾個主要的方法。

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方法做用是根據sizemode建立一個MeasureSpec,能夠看到根據API等級不一樣,實現也不一樣,API17以前是直接將sizemode相加,API17以後是採用位運算的方式,位運算集體看下面這張圖。

makeMeasureSpec方法

public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }

        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }
複製代碼

getSizegetMode這倆方法分別是從測量規格MeasureSpec中獲取到對應測量大小和測量模式,具體計算看下圖。

getSize方法和getMode方法
關於 MeasureSpec的內容就是這些,具體的運用等到看到繪製流程時再說。

五、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。

由上面這張圖能夠清楚的看出應用界面的層級,其中 Window又由 WindowManager來管理,進而會經過 ViewRootImpl中的 IWindowSession進行 Binder通訊,最終經過 WMS把窗口 Surface進行繪製到屏幕上。下面這張圖簡單描述了這個邏輯。

六、總結

這一篇內容主要是梳理了一些繪製流程中要用到的基礎知識,比較簡單,爲的是以後在看具體流程代碼的時候更加順利。下一篇就開始看具體繪製流程了。

相關文章
相關標籤/搜索