1. 程式人生 > >Android的控制元件架構與自定義控制元件原理

Android的控制元件架構與自定義控制元件原理

android控制元件架構

Android中的每個控制元件都會在介面上得到一塊矩形的區域,而在Android中,控制元件大致被分為兩類,即ViewGroup 控制元件和View控制元件。ViewGroup控制元件作為父控制元件可以包含多個View控制元件,並管理其包含的View控制元件,通過ViewGroup,介面上的控制元件形成一個樹形圖,如下圖.
這裡寫圖片描述
每顆控制元件樹的頂部,都有一個ViewParent物件,作為控制核心,來統一排程和分配所有的互動管理事件.
通過activity中的findViewById()方法,在控制元件樹中以樹的深度優先遍歷來查詢對應元素.並通過activity中的setContentView()來設定一個佈局並將其內容顯示出來.對於setContentView()具體做了什麼?首先看看android的介面架構 如下圖:
這裡寫圖片描述

每個Activity都包含一個Window物件,在Android中Window物件通常由PhoneWindow來實現的,PhoneWindow將一個DecorView設定為整個應用視窗的根View,即DecorView為整個Window介面的最頂層View。也可以說DecorView將要顯示的具體內容呈現在了PhoneWindow上,這裡所有的view的監聽事件,都由WindowManagerService來接收,並通過activity物件來回調相應的onClickListener.
上圖中的ContentView是一個ID為content的FrameLayout,通過DecorView可以建立一個檢視樹,如下圖
這裡寫圖片描述


在檢視樹中的第二層裝載了一個LinearLayout的ViewGroup,這一層的佈局結構根據不同的引數設定不同的佈局,比如圖中的佈局:上面顯示TitalBar 下面顯示Content,如果通過設定requestWindowFeature(Window.FEATURE_NO_TITLE)來全屏顯示,檢視樹中就只顯示Content了,這就是requestWindowFeature(Window.FEATURE_NO_TITLE)必須在setContentView之前呼叫才會生效的原因.在程式碼中,當程式呼叫onCreate中setContentView後,ActivityManagerService會回撥onResume方法,此時系統才將整個DecorView新增到PhoneWindow中,並讓其顯示出來,最終完成介面的繪製.
總結:
1 View表示的的螢幕上的某一塊矩形的區域,而且所有的View都是矩形的;
2 View是不能新增子View的,而ViewGroup是可以新增子View的。ViewGroup之所以能夠新增子View,是因為它實現了兩個介面:ViewParent 和 ViewManager;
3 Activity之所以能載入並且控制View,是因為它包含了一個Window,所有的圖形化介面都是由View顯示的而Service之所以稱之為沒有介面的activity是因為它不包含有Window,不能夠載入View;
4 一個View有且只能有一個父View;
5 在Android中Window物件通常由PhoneWindow來實現的,PhoneWindow將一個DecorView設定為整個應用視窗的根View,即DecorView為整個Window介面的最頂層View。也可以說DecorView將要顯示的具體內容呈現在了PhoneWindow上;
6 DecorView是FrameLayout的子類,它繼承了FrameLayout,即頂層的FrameLayout的實現類是Decorview,它是在phoneWindow裡面建立的;
7 頂層的FrameLayout的父view是Handler,Handler的作用除了執行緒之間的通訊以外,還可以跟WindowManagerService進行通訊;
8 windowManagerService是後臺的一個服務,它控制並且管理者螢幕;
9 一個應用可以有很多個window,其由windowManager來管理,而windowManager又由windowManagerService來管理;

自定義控制元件的繪製原理

繪製自定義控制元件的三步驟:1.測量measure, 2.佈局layout, 3.繪製draw。
這裡寫圖片描述
1、Measure測量一個View的大小
2、Layout擺放一個View的位置
3、Draw畫出View的顯示內容
其中measure和layout方法都是final的,無法重寫,雖然draw不是final的,但是也不建議重寫該方法。
這三個方法都已經寫好了View的邏輯,如果我們想實現自身的邏輯,而又不破壞View的工作流程,可以重寫onMeasure、onLayout、onDraw方法。下面來一一介紹這三個方法。
#View的測量
Android系統在繪製View之前,必須對View進行測量,即告訴系統該畫一個多大的View,這個過程在onMeasure()方法中進行。測量過程如下圖所示:
這裡寫圖片描述
主要方法有:
public final void measure(int widthMeasureSpec, int heightMeasureSpec)
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight)
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

measure呼叫onMeasure來測量寬度、高度,然後呼叫setMeasureDimension()儲存測量結果,其中measure,setMeasureDimension是final型別,view的子類不需要重寫,onMeasure在view的子類中重寫。

關於MeasureSpec類
1、MeasureSpe描述了父View對子View大小的期望。裡面包含了測量模式和大小。
2、MeasureSpe類把測量模式和大小組合到一個32位的int型的數值中,其中高2位表示模式,低30位表示大小而在計算中使用位運算的原因是為了提高並優化效率。
3、我們可以通過以下方式從MeasureSpec中提取模式和大小,該方法內部是採用位移計算。

int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

// 也可以通過MeasureSpec的靜態方法把大小和模式合成,該方法內部只是簡單的相加。
MeasureSpec.makeMeasureSpec(specSize,specMode);

其中測量模式有以下三種:
(1) UPSPECIFIED :父容器對於子容器沒有任何限制,因為它不指定其大小測量的模式,子容器想要多大就多大.通常情況下在繪製自定義View時才會使用。
(2) EXACTLY父容器已經為子容器設定了尺寸,子容器應當服從這些邊界,不論子容器想要多大的空間.
即精確值模式,當控制元件的layout_width屬性或layout_height屬性指定為具體數值時,例如android:layout_width=”100dp”,或者指定為match_parent屬性時,系統使用的是EXACTLY 模式。
(3) AT_MOST 子容器可以是宣告大小內的任意大小.
即最大值模式,當控制元件的layout_width屬性或layout_height屬性指定為warp_content時,控制元件大小一般隨著控制元件的子控制元件或者內容的變化而變化,此時控制元件的尺寸只要不超過父控制元件允許的最大尺寸即可。

View預設的onMeasure()方法只支援EXACTLY模式,所以如果在自定義控制元件的時候不重寫onMeasure()方法的話,就只能使用EXACTLY模式,且控制元件只可以響應你指定的具體寬高值或者是match_parent屬性。如果要讓自定義的View支援wrap_content屬性,那麼就必須重寫onMeasure()方法來指定wrap_content時的大小。
而通過上面介紹的MeasureSpec這個類,我們就可以獲取View的測量模式和View想要繪製的大小。
下圖是MeasureSpec判定規則,不同的測量模式,自定義View時會給出不同的測量值.
這裡寫圖片描述

#View佈局
佈局是用於設定檢視在螢幕中顯示的位置,其流程圖如下:
這裡寫圖片描述

View layout過程相關方法主要要三個:
public void layout(int l, int t, int r, int b)
protected boolean setFrame(int left, int top, int right, int bottom)
protected void onLayout(boolean changed, int left, int top, int right, int bottom)

layout通過呼叫setFrame(l,t,r,b),l,t,r,b即子檢視在父檢視中的具體位置,onLayout一般只會在自定義ViewGroup中才會使用
  • Layout

    ① Layout方法中接受四個引數l,t,r,b,是由父View提供,指定了子View在父View中的左、上、右、下的位置。父View在指定子View的位置時通常會根據子View在measure中測量的大小來決定。
    ② 子View的位置通常還受有其他屬性左右,例如父View的orientation,gravity,自身的margin等等,特別是RelativeLayout,影響佈局的因素非常多。
    ③ layout方法雖然可以被複寫,但是不建議去複寫,我們可以直接呼叫layout方法去確定自身的位置, 而且可以去複寫onLayout方法去確定子view的位置
    
  • setFrame

    ① setFrame方法是一個隱藏方法,該方法體內部通過比對本次的l、t、r、b四個值與上次是否相同來判斷自身的位置和大小是否發生了改變。如果發生了改變,將會呼叫invalidate請求重繪。
    ② 記錄本次的l、t、r、b,用於下次比對。
    ③  如果大小發生了變化,onSizeChanged方法,該方法在大多數View中都是空實現,我們可以重寫該方法用於監聽View大小發生變化的事件。
    
  • onLayout

    ① onLayout是ViewGroup用來決定子View擺放位置的,各種佈局的差異都在該方法中得到了體現。
    ② onLayout比layout多一個引數,changed,該引數是在setFrame通過比對上次的位置得出是否發生了變化,通常該引數沒有被使用的意義,因為父View位置和大小不變,並不能代表子View的位置和大小沒有發生改變。
    

    View的繪製

    draw過程主要用於利用前兩步得到的引數,將檢視顯示在螢幕上,到這裡也就完成了整個的檢視繪製工作。流程圖如下:
    這裡寫圖片描述

public void draw(Canvas canvas)
protected void onDraw(Canvas canvas)

通過呼叫draw函式進行檢視繪製,在View類中onDraw函式是個空函式,最終的繪製需求需要在自定義的onDraw函式中進行實現,比如ImageView完成圖片的繪製,如果自定義ViewGroup這個函式則不需要過載。
  • draw

    由ViewRoot的performTraversals方法發起,呼叫DecorView的draw方法,並把成員變數canvas傳給給draw方法。而在後面draw遍歷中,傳遞的都是同一個canvas。所以android的繪製是同一個window中的所有View都繪製在同一個畫布上。等繪製完成,將會通知WMS把canvas上的內容繪製到螢幕上。自定義View時一般不重寫該方法.
    
  • onDraw
    View用來繪製自身的實現方法,自定義View,通常需要過載該方法。比如TextView中在該方法中繪製文字、游標和CompoundDrawable;ImageView中在該方法中繪製圖片,
    所以當我們測量好了一個View之後,最終的繪製要在onDraw函式中進行實現,重寫onDraw()這個方法,並在指定的Canvas物件上來繪製所需要的圖形。在onDraw()中就有一個引數,該引數就是Canvas canvas物件,使用這個物件進行繪圖操作,並顯示出來.;
    而如果在其他地方想繪圖並通過onDraw方法顯示出來,通常需要使用程式碼建立一個Canvas物件,並傳入一個bitmap物件,

Canvas canvas = new Canvas(bitmap);

之所以要傳入一個bitmap,是因為傳進來的bitmap與通過這個bitmap建立的Canvas畫布是緊緊聯絡在一起的,這個過程稱之為裝載畫布。這個bitmap用來儲存所有繪製在Canvas上的畫素資訊,通過這種方式建立了Canvas物件後,後面呼叫的Canvas.drawXXX方法都發生在這個bitmap上.
例如:在View類的onDraw()方法中,通過下面的程式碼,讓canvas與bitmap發生直接的聯絡:

canvas.drawBitmap(bitmap, 0, 0, null);

然後將bitmap裝載到另外一個Canvas物件中:

Canvas mCanvas = new Canvas(bitmap);

通過mCanvas.drawXXX方法在裝載了bitmap的mCanvas物件上進行繪圖,從而mCanvas將繪製效果作用在了bitmap上,再通過invalidate()重新整理的時候,我們就會發現通過onDraw()方法畫出來的bitmap已經發生了改變。這是因為bitmap承載了mCanvas上所進行的繪圖操作.所以雖然沒有直接將圖形繪製在onDraw()方法指定的畫布上,但是通過改變bitmap.讓View重繪,從而顯示改變之後的bitmap.

  • dispatchDraw

    dispatchDraw的邏輯其實比較複雜,主要ViewGroup對子View進行繪製事件的派遣分發,ViewGroup已經處理好了,我們不必要過載該方法對。重寫時,不要註釋super.方法
    

相關推薦

Android群英傳》學習筆記之Android控制元件架構定義控制元件詳解

一、Android控制元件架構: 控制元件大致分為兩類:ViewGroup控制元件與View控制元件。View是繪製在螢幕上的使用者能與之互動的一個物件。而ViewGroup則是一個用於存放其他Vi

Android控制元件架構定義控制元件原理

android控制元件架構 Android中的每個控制元件都會在介面上得到一塊矩形的區域,而在Android中,控制元件大致被分為兩類,即ViewGroup 控制元件和View控制元件。ViewGroup控制元件作為父控制元件可以包含多個View控制元件,並管

閱讀徐宜生《Android群英傳》的筆記——第3章 Android控制元件架構定義控制元件詳解(3.6-3.8)

3.6 自定義 View 在自定義 View 時,我們通常會去重寫 onDraw() 方法來繪製 View 的顯示內容。如果該 View 還需要使用 wrap_content 屬性,那麼還必須重寫 onMeasure() 方法。另外,通過自定義 attr

Android屬性動畫定義控制元件畫圓球移動

com.bwei.administrator.yuekao; import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.TypeEvaluator; import and

Android群英傳讀書筆記---定義控制元件(-)

自定義控制元件 雖然我也寫過自定義控制元件,但是從沒有進行一個系統的總結,正好借這本書的內容,重新梳理一下, 通常情況下,常用的有三種方法: 對現有控制元件進行擴充套件 通過組合控制元件來實現新的控制元件 重寫view來實現全新的控制元件(最難) 1

Android定義控制元件佈局重新整理定義控制元件回到初始位置問題的解決

在闡述我所遇到的問題之前,先通過檢視Android原始碼發現這樣幾個程式碼樣例: 1.  grep extends\ ViewGroup.MarginLayoutParams ./core/java/android/widget/ -rn ./core/java/andr

android定義控制元件_完全定義控制元件定義開關)

前面總結到自定義控制元件分為 組合控制元件 繼承已有控制元件 比如自定義SmartImageView繼承ImageView 完全自定義控制元件 上一篇寫了自定義控制元件的自定義屬性深入理解點選連結檢視,是自定控制元件比較難以理解的地方,但是是很重

強大的ASP.NET控制元件----使用者控制元件對戰定義控制元件

使用者控制元件:給特定程式使用 舉例:使用者控制元件之登陸 在VS中建立程式,如下 開啟userControl.ascx,拖入如下控制元件: 開啟UserControl.ascx下的UserCont

[WPF定義控制元件] 開始一個定義控制元件庫專案

1. 目標 我實現了一個自定義控制元件庫,並且打算用這個控制元件庫作例子寫一些部落格。這個控制元件庫主要目標是用於教學,希望通過這些部落格初學者可以學會為自己或公司建立自定義控制元件,並且對WPF有更深入的瞭解。 控制元件庫已放在Github上,並且也以釋出到NuGet。 現階段我的目標是實現一些簡單的控

Android 傳送系統廣播定義廣播

android系統會發送許多系統級別的廣播,比如螢幕關閉,電池電量低等廣播。同樣應用可以發起自定義“由開發者定義的”廣播。 (1)傳送自定義的廣播package com.hmkcode.android;import android.os.Bundle;import andr

Android Matrix的使用定義動畫

變形矩陣的原理 Android對圖形的處理通過矩陣,每個畫素點都有其X,Y座標資訊,圖形變換矩陣是一個3X3的矩陣,通過變換矩陣與位置矩陣相乘得到新的位置矩陣,從而可以通過不同的變換矩陣實現不同的變換效果。 圖形變換主要有以下四個基本的變換: Tra

iview的table元件中渲染定義vue元件

自定義了一個條形展示百分比的vue元件scalebar。程式碼如下: <style> .intoDiv { border-radius: 2px; box-shadow: 1px 1px 3px #c5c5c5; } </style> <templat

Android定義控制元件——ListView的下拉重新整理上拉載入

1.簡介        無疑,在Android開發中,ListView是使用非常頻繁的控制元件之一,ListView提供一個列表的容易,允許我們以列表的形式將資料展示到介面上,但是Google給我們提供的原生ListView的控制元件,雖然在功能上很強大,但是在使用者體

我的新書《Android定義控制元件入門實戰》出版啦

 前言:當你回首往事時,不以虛度年華而悔恨,不以碌碌無為而羞恥,那你就可以驕傲的跟自己講,你不負此生 念念碎: 兩年前,為了深入研究自定義控制元件,堅持在CSDN上,以每週或每兩週更新一篇部落格的頻率,出版了《Android自定義控制元件三部曲》系列,中間因為業務太忙

Android中引入佈局和和定義控制元件

首先是引入佈局: 1.我們自己新建一個layout,就是一個標題欄。 2.然後在我們的mainactivity_layout中使用一個語句就可以實現。 <?xml version="1.0" encoding="utf-8"?> <LinearLayout

android-定義控制元件

自定義控制元件兩種方式 1、繼承ViewGroup 例如:ViewGroup , LinearLayout, FrameLayout, RelativeLayout等。 2、繼承View 例如:View, TextView, ImageView, Button等。 View的測量

Android 定義控制元件-星級評分

在學習自定義控制元件時需要一些例子來練練手,本文這個控制元件就是在這種環境下產生的(可能有BUG); 這個控制元件設計的特點: 1,可以任意修改星星數量 2,可以星星大小會隨控制元件大小而縮小,在控制元件足夠大的情況可以任意設定星星大小 3,滑動監聽,根據滑動距離選擇星級 4,可

[Android定義控制元件] Android定義控制元件

轉載自: http://blog.163.com/[email protected]/blog/static/103242241201382210910473/ 開發自定義控制元件的步驟: 1、瞭解View的工作原理  2、

Android 定義控制元件釋義

由於經常在android的開發過過程中與控制元件打交道,所以有些android控制元件並不能滿足我們的需求, 進而需要自定義一 些為我們所用,但是本文只是一些名詞解析,具體實現可以網路搜尋獲得答案,在此只是個人的筆錄 1:自定義控制元件方式: 1.1:繼承view,button,

Android定義控制元件】炫酷的底部導航欄

https://github.com/WakeHao/NavBar 基本使用 使用這個控制元件,只需要簡單的幾部 引入該控制元件到你的專案中 compile 'com.chen.wakehao.library:bottom-navigation-bar:1.0.0'