Android 初級面試者拾遺(前臺界面篇)之 View 和 ViewGroup

View 和 ViewGroup

View 是 Android 中最基本的 UI 組件,在屏幕上繪製一塊矩形區域。
ViewGroup 是一種特殊的 View,它能夠包含多個子 View 和子 ViewGroup,用於放置、組織、管理視圖結構。java

經常使用控件和佈局的繼承結構:性能優化

ViewHierarchy

LinearLayout 和 RelativeLayout 性能對比

Android Project 默認生成的 avtivity_main.xml 佈局文件中,根結點使用 RelativeLayout,然而做爲頂級 View 的 DecorView 則是垂直方向的 LinearLayout,從上至下分爲標題欄、內容欄。經常使用的 setContentView() 方法就是爲內容欄設置佈局。工具

  • RelativeLayout 的子 View 須要兩次 onMeasure() 過程,而 LinearLayout 只需一次,可是當 LinearLayout 設置 weight 屬性後,一樣須要兩次 onMeasure() 過程。
  • 在不影響層級深度的狀況下,推薦使用 Linearlayout 而非 RelativeLayout。

DecorView 層級深度已知且固定,標題欄與內容欄,採用 RelativeLayout 並不會下降層級深度,所以使用 LinearLayout 效率更高。佈局

Project 默認使用 RelativeLayout 做爲根結點,是但願開發者可以儘可能減小 View 層級結構,避免使用 LinearLayout 多層嵌套完成佈局。性能

LayoutInflater

LayoutInflater inflate() 方法用於動態加載佈局,將 XML 佈局文件實例化爲其對應的 View 對象。優化

public View inflate(int resource, ViewGroup root)    // 方法一
public View inflate(int resource, ViewGroup root, boolean attachToRoot)    // 方法二

inflate() 方法參數詳解

  • resource(int): 須要加載的 XML 佈局資源的 ID
  • root(ViewGroup): 設置加載的佈局的父級層次結構
  • attachToRoot(boolean): 是否將加載的佈局附加到父級層次結構

狀況一: root 爲 null;spa

若是 root 爲 null,attachToRoot 參數將失去意義。code

無需將 resource 指定的佈局添加到 root 中,同時沒有任何 ViewGroup 容器來協助 resource 指定的佈局的根元素生成佈局參數 LayoutParams。orm

inflate1

狀況二: root 不爲 null,attachToRoot 爲 true; xml

將 resource 指定的佈局添加到 root 中,inflate() 方法返回結合後的 View,其根元素是 root。View 將會根據它的父 ViewGroup 容器的 LayoutParams 進行測量和放置。

使用方法一即未設置 attachToRoot 參數時,若是 root 不爲 null,attachToRoot 參數默認爲true。

inflate2

狀況三: root 不爲 null,attachToRoot 爲 false;

無需將 resource 指定的佈局添加到 root 中,inflate() 方法返回 resource 指定的佈局 View,根元素是自身的最外層,View 不存在父 ViewGroup,可是能夠根據 root 的 LayoutParams 進行測量和放置。

inflate3

狀況三不解之處在於,既然 attachToRoot 爲 false,無需將 resource 指定的佈局添加到 root 中,那麼爲何 root 仍然不爲 null?建立的 View 必然包含 layout 屬性,可是這些屬性須要在 ViewGroup 容器中才能生效,根據 ViewGroup 容器的 LayoutParams 進行測量和放置 View。
狀況三的意思是,無需將 View 添加到某個 ViewGroup 容器中,卻又能根據這個 ViewGroup 容器的 LayoutParams 進行測量和放置 View。

狀況一和狀況三依賴手動添加 View。

inflate4

多個參數版本的 inflate() 方法最終匯合調用:

public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)

解析 XML 格式數據

Pull 解析方式基於事件驅動方式,Android 中使用 XmlPullParser 對象,調用 setInput() 方法傳遞數據給解析器。開始循環解析,經過 getEventType()方法獲取當前解析事件,若是當前解析事件並不是 END_DOCUMENT,調用 next() 方法獲取下一個解析事件。針對不一樣事件處理,getName() 方法能夠得到當前節點名字(tag name),nextText() 方法能夠得到當前節點內的具體內容(TEXT)。

Events(事件):

  • START_DOCUMENT 開始文檔
  • START_TAG 開始標籤
  • TEXT 文本內容
  • END_TAG 結束標籤
  • END_DOCUMENT 結束文檔

XML(可擴展標記語言)組成部分:

  1. Declaration(聲明):XML 文件首行聲明一些文檔信息 <?xml version="1.0" encoding="UTF-8"?>
  2. Tag(標籤):以 < 開頭,以 > 結尾。包括:start-tag(<section>)、end-tag(</section>)、empty-element tag(<line-break />)。
  3. Text(文本):開始與結束 tag 之間的文本內容。
  4. Attribute(屬性):位於開始 tag 之中,以鍵值對形式補充說明 tag 。

視圖繪製流程

  • onMeasure() 決定 View 的大小
  • onLayout() 決定 View 在 ViewGroup 中的位置
  • onDraw() 繪製 View

onMeasure()

視圖大小的測量過程,是由父視圖、佈局文件、以及視圖自己共同完成的。

  • 父視圖提供參考大小(MeasureSpec: specSize, specMode)給子視圖

    • UNSPECIFIED 子視圖按照自身條件設置成任意的大小
    • EXACTLY 父視圖但願子視圖的大小應該由 specSize 來決定
    • AT MOST 子視圖最大隻能是 specSize 中指定的大小
  • 佈局文件中指定視圖的大小

    • MATCH_PARENT
    • WRAP_CONTENT
  • 視圖自己最終決定大小

onLayout()

根據測量出來的(onMeasure())寬度和高度肯定視圖的位置。關鍵方法:public void layout (int l, int t, int r, int b) 方法接收左、上、右、下的座標。

onDraw()

完成測量(onMeasure())和佈局操做(onLayout())以後,建立 Canvas 對象繪製視圖。

事件分發機制

重要方法:

  • dispatchTouchEvent()
  • onInterceptTouchEvent()
  • onTouchEvent()

事件分發機制

事件分發順序:由 Activity 開始先傳遞給 ViewGroup 再傳遞給 View。

Activity 層面

事件分發始於 Activity.dispatchTouchEvent() 方法,傳遞事件至 Window 的根視圖。

若最終沒有視圖消費事件則調用 Activity.onTouchEvent(event) 方法。

ViewGroup 層面

ViewGroup 中能夠經過 ViewGroup.onInterceptTouchEvent() 方法攔截事件傳遞,返回 true 表明同一事件列再也不向下傳遞給子 View,返回 false 表明事件繼續傳遞,默認返回 false。

事件遞歸傳遞至子 View 的 View.dispatchTouchEvent() 方法,若是事件被子 View 消費,則返回 true,ViewGroup 將沒法再處理事件。

若是沒有子 View 消費事件則判斷 ViewGroup 中是否存在已註冊的事件監聽器(mOnTouchListener),存在則調用它的 ViewGroup.OnTouchListener.onTouch() 方法,若是 onTouch() 方法返回 false 即未消費事件,則進一步去執行 ViewGroup.onTouchEvent(event) 方法。

View 層面

View.dispatchTouchEvent() 方法:首先判斷 View 中是否存在已註冊的事件監聽器(mOnTouchListener),存在則調用它的 View.OnTouchListener.onTouch() 方法,如若 onTouch() 方法返回 false 即未消費事件,則進一步去執行 View.onTouchEvent(event) 方法。

View 能夠註冊事件監聽器(Listener)實現 onClick(View v)、onTouch(View v, MotionEvent event) 方法。相比 onClick() 方法,onTouch() 方法可以作的事情更多,判斷手指按下、擡起、移動等事件。同時註冊二者事件傳遞順序,onTouch() 方法將會先於 onClick() 方法執行,而且 onTouch() 方法可能執行屢次(MotionEvent 事件:ACTION_DOWNACTION_UPACTION_MOVE)。如若設置 onTouch() 方法返回值爲 true,事件視爲被 onTouch() 方法消費,再也不繼續向下傳遞給 onClick() 方法。

佈局性能優化

優化佈局層級

每一個控件和佈局都須要通過初始化、佈局、繪製過程才能呈現出來。當使用多層嵌套的 LinearLayout 以至產生較深的視圖層級結構,更甚者在 LinearLayout 中使用 layout_weight 參數,致使子 View 須要兩次 onMeasure() 過程。如此反覆執行初始化、佈局、繪製過程容易形成性能問題。

須要開發者檢查佈局、修正佈局,能夠藉助 Lint 工具發現佈局文件中的視圖層級結構裏值得優化的地方,同時扁平化處理本來多層嵌套的佈局,例如使用 RelativeLayout 做爲根節點。

使用 <include/> 複用佈局

經過使用 <include/><merge/> 標籤,在當前佈局中嵌入另外一個較大的佈局做爲組件,從而複用完整的佈局的視圖層級結構。

<include/><merge/> 標籤的區別:

<include/> 標籤旨在重用佈局文件

layout1.xml:

<FrameLayout>
   <include layout="@layout/layout2"/>
</FrameLayout>

layout2.xml:

<FrameLayout>
   <TextView />
</FrameLayout>

最終佈局視圖層級結構:

<FrameLayout>
   <FrameLayout>
      <TextView />
   </FrameLayout>
</FrameLayout>

<merge/> 標籤旨在減小視圖層級

layout2.xml:

<merge>
   <TextView />
</merge>

最終佈局視圖層級結構:

<FrameLayout>
   <TextView />
</FrameLayout>

懶加載 View

有時佈局中包含不多使用的複雜視圖,能夠在須要時加載視圖,減小內存使用,加快渲染速度。ViewStub 是一個輕量級視圖,在構建視圖層級結構中消耗資源較小。可是實際項目使用中,開發者習慣切換視圖的 Visibility 而不是使用 ViewStub。

屏幕大小適配手段

使用 wrap_contentmatch_parent,佈局儘量自適應屏幕大小。

  • wrap_content 讓當前控件的大小可以恰好包含住裏面的內容。
  • match_parent 讓當前控件的大小和父佈局的大小同樣。

使用配置限定符,程序在運行時根據當前設備的配置自動加載合適的資源。

Android 常見的限定符:

屏幕特徵 限定符
大小 small, normal, large, xlarge
分辨率 ldpi, mdpi, hdpi, xhdpi, xxhdpi
方向 land, port

使用 Nine-Patch 圖片 ,從圖片資源角度,支持不一樣屏幕大小,Nine-Patch 圖片容許指定哪些區域能夠拉伸而哪些區域不能夠。

相關文章
相關標籤/搜索