在前面一個(gè)系列的文章中,我們以窗口為單位,分析了WindowManagerService服務(wù)的實(shí)現(xiàn)。同時(shí),在再前面一個(gè)系列的文章中,我們又分析了窗口的組成。簡(jiǎn)單來(lái)說(shuō),窗口就是由一系列的視圖按照一定的布局組織起來(lái)的。實(shí)際上,每一個(gè)視圖都是一個(gè)控件,這些控制可以將自己的UI繪制在窗口的繪圖表面上,同時(shí)還可以與用戶(hù)進(jìn)行交互,即獲得用戶(hù)的鍵盤(pán)或者觸摸屏輸入。在本文中,我們就詳細(xì)分析窗口控件的上述實(shí)現(xiàn)原理。
由于android系統(tǒng)提供的控件比較多,因此我們只能挑一個(gè)比較有代表的控件進(jìn)行分析。這個(gè)比較有代表性的控件便是TextView,其它的一些基礎(chǔ)控件,例如Button、EditText和CheckBox等,都是直接或者間接地以它為父類(lèi)的。每一個(gè)控件的實(shí)現(xiàn)都是相當(dāng)復(fù)雜的,不過(guò)基本上都是一些細(xì)節(jié)問(wèn)題,而且不同的控件有不同的實(shí)現(xiàn)細(xì)節(jié),因此,本文并不打算詳細(xì)地分析TextView的具體實(shí)現(xiàn),而是從所有控件為了實(shí)現(xiàn)自己的功能而需要的東西出發(fā),去分析TextView的實(shí)現(xiàn)框架。
那么,控件為了實(shí)現(xiàn)自己的功能而需要的東西是什么呢?有兩個(gè)材料是必不可少的。第一個(gè)材料是畫(huà)布,第二個(gè)材料是用戶(hù)輸入。有畫(huà)布才能繪制UI,而有用戶(hù)輸入才能與用戶(hù)進(jìn)行交互。因此,接下來(lái)我們主要分析TextView的繪制流程,以及它獲得用戶(hù)輸入的過(guò)程。用戶(hù)輸入主要包括鍵盤(pán)輸入以及觸摸屏輸入,本文主要關(guān)注的是鍵盤(pán)輸入。觸摸屏輸入與鍵盤(pán)輸入的獲取過(guò)程是類(lèi)似的,讀者如果有興趣的話,可以參照本文的內(nèi)容來(lái)自己研究一下。
從前面[Android應(yīng)用程序窗口(Activity)實(shí)現(xiàn)框架簡(jiǎn)要介紹和學(xué)習(xí)計(jì)劃]這個(gè)系列的文章可以知道,應(yīng)用程序窗口,即Activity窗口,是由一個(gè)PhoneWindow對(duì)象,一個(gè)DecorView對(duì)象,以及一個(gè)ViewRoot對(duì)象來(lái)描述的。其中,PhoneWindow對(duì)象用來(lái)描述窗口對(duì)象,DecorView對(duì)象用來(lái)描述窗口的頂層視圖,ViewRoot對(duì)象除了用來(lái)與WindowManagerService服務(wù)通信之外,還用來(lái)接收用戶(hù)輸入。窗口控件本身也是一個(gè)視圖,即一個(gè)View對(duì)象,它們是以樹(shù)形結(jié)構(gòu)組織在一起形成整個(gè)窗口的UI的。為了簡(jiǎn)單起見(jiàn),本文假設(shè)要分析的TextView控件是直接以窗口的頂層視圖為父視圖的,即以DecorView為父視圖,如圖1所示:

圖1 窗口結(jié)構(gòu)示意圖以及DecorView、TextView的類(lèi)關(guān)系圖
圖1顯示的是一個(gè)包含了TextView控件的Activity窗口的結(jié)構(gòu)示意圖以及DecorView、TextView的簡(jiǎn)單類(lèi)關(guān)系圖,從中可以看出:
1. 用戶(hù)輸入首先是由ViewRoot接收,然后再分發(fā)給TextView處理;
2. DecorView是一個(gè)視圖容器,因此,它是從ViewGroup繼承下來(lái),而ViewGroup本身又是從View繼承下來(lái)的;
3. TextView是一個(gè)簡(jiǎn)單視圖,因此,它是直接繼承了View。
接下來(lái),我們就以圖1所示的Activity窗口為例,來(lái)分析TextView控件的UI繪制框架及其獲得鍵盤(pán)輸入的過(guò)程。
一. TextView控件的UI繪制框架
從前面[Android應(yīng)用程序窗口(Activity)的測(cè)量(Measure)、布局(Layout)和繪制(Draw)過(guò)程分析]一文可以知道,Activity窗口的UI繪制操作分為三步來(lái)走,分別是測(cè)量、布局和繪制。
1. 測(cè)量
為了能告訴父視圖自己的所占據(jù)的空間的大小,所有控件都必須要重寫(xiě)父類(lèi)View的成員函數(shù)onMeasure。
TextView類(lèi)的成員函數(shù)onMeasure的實(shí)現(xiàn)如下所示:
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
......
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
//計(jì)算TextView控件的寬度和高度
......
setMeasuredDimension(width, height);
}
......
}
這個(gè)函數(shù)定義在文件frameworks/base/core/[Java](http://lib.csdn.net/base/java)/android/widget/TextView.java中。
參數(shù)widthMeasureSpec和heightMeasureSpec分別用來(lái)描述寬度測(cè)量規(guī)范和高度測(cè)量規(guī)范。測(cè)量規(guī)范使用一個(gè)int值來(lái)表法,這個(gè)int值包含了兩個(gè)分量。
第一個(gè)是mode分量,使用最高2位來(lái)表示。測(cè)量模式有三種,分別是MeasureSpec.UNSPECIFIED(0)、MeasureSpec.EXACTLY(1)、和MeasureSpec.AT_MOST(2)。
第二個(gè)是size分量,使用低30位來(lái)表示。當(dāng)mode分量等于MeasureSpec.EXACTLY時(shí),size分量的值就是父視圖要求當(dāng)前控件要設(shè)置的寬度或者高度;當(dāng)mode分量等于MeasureSpec.AT_MOST時(shí),size分量的值就是父視圖限定當(dāng)前控件可以設(shè)置的最大寬度或者高度;當(dāng)mode分量等于MeasureSpec.UNSPECIFIED時(shí),父視圖不限定當(dāng)前控件所設(shè)置的寬度或者高度,這時(shí)候當(dāng)前控件一般就按照實(shí)際需求來(lái)設(shè)置自己的寬度和高度。
TextView類(lèi)的成員函數(shù)onMeasure根據(jù)上述規(guī)則計(jì)算好自己的寬度wdith和高度height之后,必須要調(diào)用從父類(lèi)View繼承下來(lái)的成員函數(shù)setMeasuredDimension來(lái)通知父視圖它所要設(shè)置的寬度和高度,否則的話,該函數(shù)調(diào)用結(jié)束之后,就會(huì)拋出一個(gè)類(lèi)型為IllegalStateException的異常。
2. 布局
前面的測(cè)量工作實(shí)際上是確定了控件的大小,但是控件的位置還未確定。控件的位置是通過(guò)布局這個(gè)操作來(lái)完成的。
我們知道,控件是按照樹(shù)形結(jié)構(gòu)組織在一起的,其中,子控件的位置由父控件來(lái)設(shè)置,也就是說(shuō),只有容器類(lèi)控件才需要執(zhí)行布局操作,這是通過(guò)重寫(xiě)父類(lèi)View的成員函數(shù)onLayout來(lái)實(shí)現(xiàn)的。從Activity窗口的結(jié)構(gòu)可以知道,它的頂層視圖是一個(gè)DecorView,這是一個(gè)容器類(lèi)控件。Activity窗口的布局操作就是從其頂層視圖開(kāi)始執(zhí)行的,每碰到一個(gè)容器類(lèi)的子控件,就調(diào)用它的成員函數(shù)onLayout來(lái)讓它有機(jī)會(huì)對(duì)自己的子控件的位置進(jìn)行設(shè)置,依次類(lèi)推。
我們常見(jiàn)的FrameLayout、LinearLayout、RelativeLayout、TableLayout和AbsoluteLayout,都是屬于容器類(lèi)控件,因此,它們都需要重寫(xiě)父類(lèi)View的成員函數(shù)onLayout。由于TextView控件不是容器類(lèi)控件,因此,它可以不重寫(xiě)父類(lèi)View的成員函數(shù)onLayout。
3. 繪制
有了前面兩個(gè)操作之后,控件的位置的大小就確定下來(lái)了,接下來(lái)就可以對(duì)它們的UI進(jìn)行繪制了??丶榱四軌蚶L制自己的UI,必須要重寫(xiě)父類(lèi)View的成員函數(shù)onDraw。
TextView類(lèi)的成員函數(shù)onDraw的實(shí)現(xiàn)如下所示:
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
......
@Override
protected void onDraw(Canvas canvas) {
//在畫(huà)布canvas上繪制UI
......
}
......
}
這個(gè)函數(shù)定義在文件frameworks/base/core/java/android/widget/TextView.java中。
參數(shù)canvas描述的是一塊畫(huà)布,控件的UI就是繪制在這塊畫(huà)布上面的。畫(huà)布提供了豐富的接口來(lái)繪制UI,例如畫(huà)線(drawLine)、畫(huà)圓(drawCircle)和貼圖(drawBitmap)等等。有了這些UI畫(huà)圖接口之后,就可以隨心所欲地繪制控件的UI了。
從前面[Android應(yīng)用程序窗口(Activity)的測(cè)量(Measure)、布局(Layout)和繪制(Draw)過(guò)程分析](http://blog.csdn.net/luoshengyang/article/details/8372924)一文可以知道,Java層的Canvas實(shí)際上是封裝了C++層的SkCanvas。C++層的SkCanvas內(nèi)部有一塊圖形緩沖區(qū),這塊圖形緩沖區(qū)就是窗口的繪圖表面(Surface)里面的那塊圖形緩沖區(qū)。
從前面[Android應(yīng)用程序與SurfaceFlinger服務(wù)的關(guān)系概述和學(xué)習(xí)計(jì)劃](http://blog.csdn.net/luoshengyang/article/details/7846923)一文可以知道,窗口的繪圖表面里面的那塊圖形緩沖區(qū)實(shí)際上是一塊匿名共享內(nèi)存,它是SurfaceFlinger服務(wù)負(fù)責(zé)創(chuàng)建的。SurfaceFlinger服務(wù)創(chuàng)建完成這塊匿名共享內(nèi)存之后,就會(huì)將其返回給窗口所運(yùn)行在的進(jìn)程。窗口所運(yùn)行在的進(jìn)程獲得了這塊匿名共享內(nèi)存之后,就會(huì)映射到自己的進(jìn)程空間來(lái),因此,窗口的控件就可以在本進(jìn)程內(nèi)訪問(wèn)這塊匿名共享內(nèi)存了,實(shí)際上就是往這塊匿名共享內(nèi)存填入U(xiǎn)I數(shù)據(jù)。注意,這個(gè)過(guò)程執(zhí)行完成之后,控件的UI還沒(méi)有反映到屏幕上來(lái),因?yàn)檫@時(shí)候?qū)⒖丶腢I數(shù)據(jù)填入到圖形緩沖區(qū)而已。
從前面[Android窗口管理服務(wù)WindowManagerService的簡(jiǎn)要介紹和學(xué)習(xí)計(jì)劃](http://blog.csdn.net/luoshengyang/article/details/8462738)一文可以知道,窗口的UI的顯示是WindowManagerService服務(wù)來(lái)控制的。因此,當(dāng)窗口的所有控件都繪制完成自己的UI之后,窗口就會(huì)向WindowManagerService服務(wù)發(fā)送一個(gè)Binder進(jìn)程間程通信請(qǐng)求。WindowManagerService服務(wù)接收到這個(gè)Binder進(jìn)程間程通信請(qǐng)求之后,就會(huì)請(qǐng)求SurfaceFlinger服務(wù)刷新相應(yīng)的窗口的UI。SurfaceFlinger服務(wù)刷新窗口UI的過(guò)程可以參考前面[Android系統(tǒng)Surface機(jī)制的SurfaceFlinger服務(wù)渲染應(yīng)用程序UI的過(guò)程分析](http://blog.csdn.net/luoshengyang/article/details/8079456)一文。
從上面的描述就可以看出,控件的UI雖然是在一塊簡(jiǎn)單的畫(huà)布進(jìn)行繪制,但是其中蘊(yùn)含了豐富的知識(shí)點(diǎn),并且需要應(yīng)用程序進(jìn)程、WindowManagerService服務(wù)和SurfaceFlinger服務(wù)三方緊密而有序的配合。
如果我們仔細(xì)閱讀[Android應(yīng)用程序窗口(Activity)的測(cè)量(Measure)、布局(Layout)和繪制(Draw)過(guò)程分析](http://blog.csdn.net/luoshengyang/article/details/8372924)一文,還可以得出以下兩個(gè)結(jié)論:
(1). 一個(gè)窗口的所有控件的UI都是繪制在窗口的繪圖表面上的,也就是說(shuō),一個(gè)窗口的所有控件的UI數(shù)據(jù)都是填寫(xiě)在同一塊圖形緩沖區(qū)中;
(2). 一個(gè)窗口的所有控件的UI的繪制操作是在主線程中執(zhí)行的,事實(shí)上,所有與UI相關(guān)的操作都是必須是要在主線程中執(zhí)行,否則的話,就會(huì)拋出一個(gè)類(lèi)型為CalledFromWrongThreadException的異常來(lái)。
為什么要規(guī)定所有與UI相關(guān)的操作都必須在主線程中執(zhí)行呢?我們知道,這些與UI相關(guān)的操作都涉及到大量的控件內(nèi)部狀態(tài)以及需要訪問(wèn)窗口的繪圖表面,也就是說(shuō),要大量地訪問(wèn)控件類(lèi)的成員變量以及窗口繪圖表面里面的圖形緩沖區(qū),因此,如果不將這些與UI相關(guān)的操作限定在同一個(gè)線程中執(zhí)行的話,那么就會(huì)涉及到線程同步問(wèn)題。線程同步的開(kāi)銷(xiāo)是很大的,因此,就要保證那些與UI相關(guān)的操作都在同一個(gè)線程中執(zhí)行。這個(gè)負(fù)責(zé)執(zhí)行UI相關(guān)操作的線程便是應(yīng)用程序進(jìn)程的主線程,因此我們也將應(yīng)用程序進(jìn)程的主線程稱(chēng)為UI線程。
我們知道,應(yīng)用程序進(jìn)程的主線程除了負(fù)責(zé)執(zhí)行與UI相關(guān)的操作之外,還負(fù)責(zé)響應(yīng)用戶(hù)的輸入,因此,我們就要盡量地避免執(zhí)行很耗時(shí)的UI操作,否則的話,系統(tǒng)就會(huì)由于應(yīng)用程序進(jìn)程的主線程無(wú)法及時(shí)響應(yīng)用戶(hù)輸入而彈出ANR對(duì)話框。
那么,有沒(méi)有辦法讓某一個(gè)控件的UI享有獨(dú)立的圖形緩沖區(qū)呢?也就是這個(gè)控件不將自己的UI數(shù)據(jù)填入到它的宿主窗口的繪圖表面的圖形緩沖區(qū)里面去。如果可以的話,那么我們就可以在另外一個(gè)獨(dú)立的線程中繪制該控件的UI。這樣做的好處是顯而易見(jiàn)——可以在這個(gè)獨(dú)立的線程執(zhí)行相對(duì)比較耗時(shí)的UI繪制操作而不會(huì)導(dǎo)致主線程無(wú)法及時(shí)響應(yīng)用戶(hù)輸入。答案是肯定的,在接下來(lái)的一篇文章中,我們就分析一個(gè)可以具有獨(dú)立圖形緩沖區(qū)的控件——SurfaceView。
二. TextView控件獲取鍵盤(pán)輸入的過(guò)程分析
從前面[Android應(yīng)用程序鍵盤(pán)(Keyboard)消息處理機(jī)制分析](http://blog.csdn.net/luoshengyang/article/details/6882903)一文可以知道,每一個(gè)窗口的創(chuàng)建的時(shí)候,都會(huì)與系統(tǒng)的輸入管理器建立一個(gè)用戶(hù)輸入接收通道。輸入管理器在啟動(dòng)兩個(gè)線程,其中一個(gè)用來(lái)監(jiān)控用戶(hù)輸入,即監(jiān)控用戶(hù)是否按下或者放開(kāi)了鍵盤(pán)按鍵,或者是否觸摸了屏幕,另外一個(gè)用來(lái)將監(jiān)控到的用戶(hù)輸入事件分發(fā)給當(dāng)前激活的窗口來(lái)處理,而這個(gè)分發(fā)過(guò)程就是通過(guò)前面建立的通道來(lái)進(jìn)行的。
當(dāng)前激活的窗口接收到輸入管理器分發(fā)過(guò)來(lái)的用戶(hù)輸入事件之后,就會(huì)該事件封裝成一個(gè)消息發(fā)送到當(dāng)前激活的窗口所運(yùn)行在的應(yīng)用程序進(jìn)程的主線程的消息隊(duì)列中去。等到這個(gè)消息被處理的時(shí)候,就會(huì)調(diào)用與當(dāng)前激活的窗口所關(guān)聯(lián)的一個(gè)ViewRoot對(duì)象的成員函數(shù)deliverKeyEvent或者deliverPointerEvent來(lái)將前面接收到的用戶(hù)輸入分發(fā)給合適的控件。其中,ViewRoot類(lèi)的成員函數(shù)deliverKeyEvent負(fù)責(zé)分發(fā)鍵盤(pán)輸入事件,而ViewRoot類(lèi)的成員函數(shù)deliverPointerEvent負(fù)責(zé)分發(fā)觸摸屏輸入事件。
接下來(lái),我們就從ViewRoot類(lèi)的成員函數(shù)deliverKeyEvent開(kāi)始,分析一個(gè)TextView控件獲得鍵盤(pán)輸入的過(guò)程(獲得觸摸屏輸入的過(guò)程是類(lèi)似的),如圖2所示:

圖2 TextView控件獲得鍵盤(pán)輸入的過(guò)程
這個(gè)過(guò)程可以分為14個(gè)步驟,接下來(lái)我們就詳細(xì)分析每一個(gè)步驟。
Step 1. ViewRoot.deliverKeyEvent
public final class ViewRoot extends Handler implements ViewParent,
View.AttachInfo.Callbacks {
......
private void deliverKeyEvent(KeyEvent event, boolean sendDone) {
// If mView is null, we just consume the key event because it doesn't
// make sense to do anything else with it.
boolean handled = mView != null
? mView.dispatchKeyEventPreIme(event) : true;
if (handled) {
if (sendDone) {
finishInputEvent();
}
return;
}
// If it is possible for this window to interact with the input
// method window, then we want to first dispatch our key events
// to the input method.
if (mLastWasImTarget) {
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null && mView != null) {
int seq = enqueuePendingEvent(event, sendDone);
......
imm.dispatchKeyEvent(mView.getContext(), seq, event,
mInputMethodCallback);
return;
}
}
deliverKeyEventToViewHierarchy(event, sendDone);
}
......
}
這個(gè)函數(shù)定義在文件frameworks/base/core/java/android/view/ViewRoot.java中。
參數(shù)event描述的是窗口接收到的鍵盤(pán)事件,另外一個(gè)參數(shù)sendDone表示該鍵盤(pán)事件處理完成后,是否需要向系統(tǒng)的輸入管理器發(fā)送一個(gè)通知。
ViewRoot類(lèi)的成員變量mView描述的是窗口的頂層視圖,即它指向的是一個(gè)DecorView對(duì)象,ViewRoot類(lèi)的成員函數(shù)deliverKeyEvent首先是調(diào)用它的成員函數(shù)dispatchKeyEventPreIme來(lái)讓它優(yōu)先于輸入法處理參數(shù)event所描述的鍵盤(pán)事件。如果這個(gè)DecorView對(duì)象的成員函數(shù)dispatchKeyEventPreIme的返回值handled等于true,那么就說(shuō)明參數(shù)event所描述的鍵盤(pán)事件已經(jīng)處理完畢,即ViewRoot類(lèi)的成員函數(shù)deliverKeyEvent不用往下執(zhí)行了。在這種情況下,如果參數(shù)sendDone的值等于true,那么ViewRoot類(lèi)的成員函數(shù)deliverKeyEvent在返回之前,還會(huì)調(diào)用成員函數(shù)finishInputEvent來(lái)通知系統(tǒng)的輸入管理器,當(dāng)前激活的窗口已經(jīng)處理完成剛剛發(fā)生的鍵盤(pán)事件了。在接下來(lái)的Step 2到Step 4中,我們?cè)僭敿?xì)分析鍵盤(pán)事件優(yōu)先于輸入法分發(fā)給窗口處理的過(guò)程。
假設(shè)窗口不在輸入法前面攔截參數(shù)event所描述的鍵盤(pán)事件,接下來(lái)ViewRoot類(lèi)的成員函數(shù)deliverKeyEvent就會(huì)將該鍵盤(pán)事件分發(fā)給輸入法處理,這個(gè)分發(fā)過(guò)程如下所示:
1. 調(diào)用InputMethodManager類(lèi)的靜態(tài)成員函數(shù)peekInstance獲得一個(gè)類(lèi)型為InputMethodManager輸入法管理器imm;
2. 調(diào)用ViewRoot類(lèi)的成員函數(shù)enqueuePendingEvent將參數(shù)event所描述的鍵盤(pán)事件緩存起來(lái),等到輸入法處理完成該鍵盤(pán)事件之后,再繼續(xù)對(duì)它進(jìn)行處理;
3. 調(diào)用第1步獲得的輸入法管理器imm的成員函數(shù)dispatchKeyEvent來(lái)將參數(shù)event所描述的鍵盤(pán)事件分發(fā)給輸入法處理。
這里有兩個(gè)地方是需要注意的。第一個(gè)地方是只有當(dāng)前窗口正在顯示輸入法的情況下,ViewRoot類(lèi)的成員函數(shù)deliverKeyEvent才會(huì)將參數(shù)event所描述的鍵盤(pán)事件分發(fā)給輸入法處理,這是通過(guò)檢查ViewRoot類(lèi)的成員變量mLastWasImTarget的值是否等于true來(lái)確定的。第二個(gè)地方是在將參數(shù)event所描述的鍵盤(pán)事件分發(fā)給輸入法處理時(shí),ViewRoot類(lèi)的成員函數(shù)deliverKeyEvent會(huì)同時(shí)傳遞一個(gè)類(lèi)型為InputMethodCallback的回調(diào)接口給輸入法,以便輸入法處理完成參數(shù)event所描述的鍵盤(pán)事件之后,可以調(diào)用這個(gè)回調(diào)接口的成員函數(shù)finishedEvent來(lái)向窗口發(fā)送一個(gè)鍵盤(pán)事件處理完成通知。這個(gè)類(lèi)型為InputMethodCallback的回調(diào)接口就保存在ViewRoot類(lèi)的成員變量mInputMethodCallback中,當(dāng)它的成員函數(shù)finishedEvent被調(diào)用的時(shí)候,它就會(huì)調(diào)用ViewRoot類(lèi)的成員函數(shù)deliverKeyEventToViewHierarchy來(lái)繼續(xù)將參數(shù)event所描述的鍵盤(pán)事件分發(fā)給窗口處理。
如果窗口當(dāng)前不需要與輸入法交互,即ViewRoot類(lèi)的成員變量mLastWasImTarget的值等于false,那么ViewRoot類(lèi)的成員函數(shù)deliverKeyEvent就會(huì)直接調(diào)用成員函數(shù)deliverKeyEventToViewHierarchy來(lái)將參數(shù)event所描述的鍵盤(pán)事件分發(fā)給窗口處理。
接下來(lái),我們就先析窗口在輸入法之前處理鍵盤(pán)輸入的過(guò)程,接著再分析窗口在輸入法之后處理鍵盤(pán)輸入的過(guò)程。
從前面的分析可以知道,ViewRoot類(lèi)的成員函數(shù)deliverKeyEvent是通過(guò)調(diào)用DecorView類(lèi)的成員函數(shù)dispatchKeyEventPreIme來(lái)將獲得的鍵盤(pán)輸入優(yōu)先于輸入法分發(fā)給窗口處理的。DecorView類(lèi)的成員函數(shù)dispatchKeyEventPreIme是從父類(lèi)ViewGroup繼承下來(lái)的,因此,接下來(lái)我們就繼續(xù)分析ViewGroup類(lèi)的成員函數(shù)dispatchKeyEventPreIme的實(shí)現(xiàn)。
Step 2. ViewGroup.dispatchKeyEventPreIme
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
......
// The view contained within this ViewGroup that has or contains focus.
private View mFocused;
......
@Override
public boolean dispatchKeyEventPreIme(KeyEvent event) {
if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
return super.dispatchKeyEventPreIme(event);
} else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
return mFocused.dispatchKeyEventPreIme(event);
}
return false;
}
......
}
這個(gè)函數(shù)定義在文件frameworks/base/core/java/android/view/ViewGroup.java中。
ViewGroup類(lèi)的成員函數(shù)dispatchKeyEventPreIme首先是檢查當(dāng)前正在處理的視圖容器是否能夠獲得焦點(diǎn)。如果能夠獲得焦點(diǎn)的話,那么ViewGroup類(lèi)的成員變量mPrivateFlags的FOCUSED位就會(huì)等于1。在當(dāng)前正在處理的視圖容器能夠獲得焦點(diǎn)的情況下,還要檢查正在處理的視圖容器是否已經(jīng)計(jì)算過(guò)大小了,即檢查ViewGroup類(lèi)的成員變量mPrivateFlags的HAS_BOUNDS位是否等于1。只有在已經(jīng)計(jì)算過(guò)大小并且能夠獲得焦點(diǎn)的情況下,那么正在處理的視圖容器才有資格處理參數(shù)event所描述的鍵盤(pán)事件。注意,正在處理的視圖容器是通過(guò)調(diào)用其父類(lèi)View的成員函數(shù)dispatchKeyEventPreIme來(lái)處理參數(shù)event所描述的鍵盤(pán)事件的。
如果當(dāng)前正在處理的視圖容器沒(méi)有資格處理參數(shù)event所描述的鍵盤(pán)事件,但是它有一個(gè)能夠獲得焦點(diǎn)的子視圖,并且這個(gè)子視圖的大小也是已經(jīng)計(jì)算好了的,那么ViewGroup類(lèi)的成員函數(shù)dispatchKeyEventPreIme就會(huì)將參數(shù)event所描述的鍵盤(pán)事件分發(fā)給該子視圖處理。當(dāng)前正在處理的視圖容器能夠獲得焦點(diǎn)的子視圖是通過(guò)ViewGroup類(lèi)的成員變量mFocused來(lái)描述的,通過(guò)調(diào)用這個(gè)成員變量所描述的一個(gè)View對(duì)象的成員函數(shù)dispatchKeyEventPreIme即可將參數(shù)event所描述的鍵盤(pán)事件分發(fā)給夠獲得焦點(diǎn)的子視圖處理。
一個(gè)視圖容器是如何知道它的焦點(diǎn)子視圖的呢?我們知道,當(dāng)我們?cè)谄聊簧嫌|摸一個(gè)窗口時(shí),就會(huì)發(fā)生一個(gè)Pointer事件。這個(gè)Pointer事件關(guān)聯(lián)有一個(gè)觸摸點(diǎn),通過(guò)檢查這個(gè)觸摸點(diǎn)當(dāng)前是包含在窗口頂層視圖的哪一個(gè)子視圖里面,就可以知道哪一個(gè)子視圖是焦點(diǎn)子視圖了。
從上面的分析可以知道,無(wú)論是當(dāng)前正在處理的視圖容器獲得焦點(diǎn),還是它的子視圖獲得焦點(diǎn),最終都是通過(guò)調(diào)用View類(lèi)的成員函數(shù)dispatchKeyEventPreIme來(lái)在輸入法之前處理參數(shù)event所描述的鍵盤(pán)事件,因此,接下來(lái)我們就繼續(xù)分析View類(lèi)的成員函數(shù)dispatchKeyEventPreIme的實(shí)現(xiàn)。
Step 3. View.dispatchKeyEventPreIme
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
......
public boolean dispatchKeyEventPreIme(KeyEvent event) {
return onKeyPreIme(event.getKeyCode(), event);
}
......
}
這個(gè)函數(shù)定義在文件frameworks/base/core/java/android/view/View.java中。
View類(lèi)的成員函數(shù)dispatchKeyEventPreIme的實(shí)現(xiàn)很簡(jiǎn)單,它只是通過(guò)調(diào)用另外一個(gè)成員函數(shù)onKeyPreIme來(lái)在輸入法之前處理參數(shù)event所描述的鍵盤(pán)事件。
Step 4. View.onKeyPreIme
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
......
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
return false;
}
......
}
這個(gè)函數(shù)定義在文件frameworks/base/core/java/android/view/View.java中。
View類(lèi)的成員函數(shù)onKeyPreIme默認(rèn)是不會(huì)在輸入法之前處理參數(shù)event所描述的鍵盤(pán)事件的,因此,我們?cè)趯?shí)現(xiàn)自己的控件的時(shí)候,如果需要在輸入法之前處理鍵盤(pán)輸入,那么就必須重寫(xiě)父類(lèi)View的成員函數(shù)onKeyPreIme。在重寫(xiě)父類(lèi)View的成員函數(shù)onKeyPreIme來(lái)處理一個(gè)鍵盤(pán)事件的時(shí)候,如果不希望這個(gè)鍵盤(pán)事件分發(fā)給輸入法處理,那么就返回一個(gè)true值,否則的話,就返回一個(gè)false值。
我們假設(shè)當(dāng)前獲得焦點(diǎn)的是圖1所示的TextView控件,但是由于TextView類(lèi)沒(méi)有重寫(xiě)其父類(lèi)View的成員函數(shù)onKeyPreIme,因此,參數(shù)event所描述的鍵盤(pán)事件接下來(lái)就會(huì)繼續(xù)分發(fā)給輸入法或者當(dāng)前激活的窗口處理。
這一步執(zhí)行完成之后,回到前面的Step 1中,即ViewRoot類(lèi)的成員函數(shù)deliverKeyEvent中,無(wú)論接下來(lái)是否需要先將一個(gè)鍵盤(pán)事件分發(fā)給輸入法處理,最終都會(huì)調(diào)用到ViewRoot類(lèi)的成員函數(shù)deliverKeyEventToViewHierarchy來(lái)繼續(xù)將該鍵盤(pán)事件分發(fā)給當(dāng)前激活的窗口處理。
Step 5. ViewRoot.deliverKeyEventToViewHierarchy
public final class ViewRoot extends Handler implements ViewParent,
View.AttachInfo.Callbacks {
......
private void deliverKeyEventToViewHierarchy(KeyEvent event, boolean sendDone) {
try {
if (mView != null && mAdded) {
final int action = event.getAction();
boolean isDown = (action == KeyEvent.ACTION_DOWN);
......
boolean keyHandled = mView.dispatchKeyEvent(event);
if (!keyHandled && isDown) {
int direction = 0;
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_DPAD_LEFT:
direction = View.FOCUS_LEFT;
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
direction = View.FOCUS_RIGHT;
break;
case KeyEvent.KEYCODE_DPAD_UP:
direction = View.FOCUS_UP;
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
direction = View.FOCUS_DOWN;
break;
}
if (direction != 0) {
View focused = mView != null ? mView.findFocus() : null;
if (focused != null) {
View v = focused.focusSearch(direction);
......
if (v != null && v != focused) {
......
focusPassed = v.requestFocus(direction, mTempRect);
}
......
}
}
}
}
} finally {
if (sendDone) {
finishInputEvent();
}
......
}
}
......
}
這個(gè)函數(shù)定義在文件frameworks/base/core/java/android/view/ViewRoot.java中。
ViewRoot類(lèi)的成員函數(shù)deliverKeyEventToViewHierarchy首先將參數(shù)event所描述的鍵盤(pán)事件交給當(dāng)前激活的窗口的頂層視圖來(lái)處理,這是通過(guò)調(diào)用ViewRoot類(lèi)的成員變量mView所描述的一個(gè)DecorView對(duì)象的成員函數(shù)dispatchKeyEvent來(lái)實(shí)現(xiàn)的。
如果當(dāng)前激活的窗口的頂層視圖在處理完成參數(shù)event所描述的鍵盤(pán)事件之后,希望該鍵盤(pán)事件還能繼續(xù)被ViewRoot類(lèi)的成員函數(shù)deliverKeyEventToViewHierarchy處理,那么前面調(diào)用DecorView類(lèi)的成員函數(shù)dispatchKeyEvent得到的返回值keyHandled的值就會(huì)等于false。在這種情況下,如果參數(shù)event描述的是一個(gè)按下的鍵盤(pán)事件,即變量isDown的值等于true,那么ViewRoot類(lèi)的成員函數(shù)deliverKeyEventToViewHierarchy就會(huì)繼續(xù)檢查參數(shù)event描述的是否是一個(gè)DPAD事件。如果是的話,那么就可能需要改變窗口當(dāng)前的焦點(diǎn)子視圖。
如果參數(shù)event描述的是一個(gè)DPAD事件,那么最終得到的變量direction的值就不會(huì)等于0,并且它描述的是當(dāng)前按下的是哪一個(gè)方向的DPAD鍵。假設(shè)這時(shí)候窗口已經(jīng)有一個(gè)焦點(diǎn)子視圖,即調(diào)用ViewRoot類(lèi)的成員變量mView所描述的一個(gè)DecorView對(duì)象的成員函數(shù)findFocus的返回值focused不等于null,那么接下來(lái)就要根據(jù)變量direction的值來(lái)決定下一個(gè)焦點(diǎn)子視圖是誰(shuí)。例如,假設(shè)變量direction的值等于View.FOCUS_LEFT,那么就表示在當(dāng)前的焦點(diǎn)子視圖focused的左邊查找一個(gè)最靠近的子視圖作為下一個(gè)焦點(diǎn)子視圖,這是通過(guò)調(diào)用當(dāng)前焦點(diǎn)子視圖focused的成員函數(shù)focusSearch來(lái)實(shí)現(xiàn)的。
一旦找到了下一個(gè)焦點(diǎn)子視圖v,并且該子視圖不是當(dāng)前的焦點(diǎn)子視圖focused,那么ViewRoot類(lèi)的成員函數(shù)deliverKeyEventToViewHierarchy就需要將子視圖v設(shè)置為焦點(diǎn)子視圖,這是通過(guò)調(diào)用變量v所描述的一個(gè)View對(duì)象的成員函數(shù)requestFocus來(lái)實(shí)現(xiàn)的。
通過(guò)前面的操作,參數(shù)event所描述的鍵盤(pán)事件就處理完成了。如果這時(shí)候參數(shù)sendDone的值等于true,那么就表示需要通知系統(tǒng)的輸入管理器,參數(shù)event所描述的鍵盤(pán)事件已經(jīng)處理完成了,這是通過(guò)調(diào)用ViewRoot類(lèi)的成員函數(shù)finishInputEvent來(lái)實(shí)現(xiàn)的。
接下來(lái),我們就繼續(xù)分析DecorView類(lèi)的成員函數(shù)dispatchKeyEvent的實(shí)現(xiàn),以便可以了解窗口的頂層視圖分發(fā)鍵盤(pán)事件的過(guò)程。
Step 6. DecorView.dispatchKeyEvent
public class PhoneWindow extends Window implements MenuBuilder.Callback {
......
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
......
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
final int keyCode = event.getKeyCode();
final boolean isDown = event.getAction() == KeyEvent.ACTION_DOWN;
......
final Callback cb = getCallback();
final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
: super.dispatchKeyEvent(event);
if (handled) {
return true;
}
return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event)
: PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event);
}
......
}
......
}
這個(gè)函數(shù)定義在文件frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java中。
PhoneWindow類(lèi)的成員函數(shù)getCallback是從父類(lèi)Window繼承下來(lái)的,它返回的是一個(gè)Window.Callback接口。每一個(gè)Activity組件都會(huì)實(shí)現(xiàn)一個(gè)Window.Callback接口,并且將這個(gè)Window.Callback接口設(shè)置到與它所關(guān)聯(lián)的一個(gè)PhoneWindow對(duì)象的內(nèi)部去,這樣當(dāng)該P(yáng)honeWindow對(duì)象接收到鍵盤(pán)事件的時(shí)候,就可以該鍵盤(pán)事件分發(fā)給與它所關(guān)聯(lián)的Activity組件處理。
DecorView類(lèi)的成員變量mFeatureId用來(lái)描述當(dāng)前正在處理的DecorView對(duì)象的特征,當(dāng)它的值小于0的時(shí)候,就表示當(dāng)前正在處理的一個(gè)DecorView對(duì)象是用來(lái)描述一個(gè)Activity組件窗口的頂層視圖的。
因此,當(dāng)當(dāng)前正在處理的DecorView對(duì)象描述的是一個(gè)Activity組件窗口的頂層視圖,并且這個(gè)Activity組件實(shí)現(xiàn)有一個(gè)Window.Callback接口時(shí),DecorView類(lèi)的成員函數(shù)dispatchKeyEvent就會(huì)調(diào)用該Window.Callback接口的成員函數(shù)dispatchKeyEvent來(lái)通知對(duì)應(yīng)的Activity組件,它接收到一個(gè)鍵盤(pán)事件了。否則的話,參數(shù)event所描述的鍵盤(pán)事件就會(huì)被分發(fā)給當(dāng)前正在處理的DecorView對(duì)象的父對(duì)象來(lái)處理,這是通過(guò)調(diào)用DecorView類(lèi)的父類(lèi)View的成員函數(shù)dispatchKeyEvent來(lái)實(shí)現(xiàn)的。
我們假設(shè)當(dāng)前正在處理的DecorView對(duì)象描述的是一個(gè)Activity組件窗口的頂層視圖,并且這個(gè)Activity組件實(shí)現(xiàn)有一個(gè)Window.Callback接口,那么參數(shù)event所描述的鍵盤(pán)事件接下來(lái)就會(huì)分給該Activity組件處理。如果該Activity組件在處理完成這個(gè)鍵盤(pán)事件之后,希望該鍵盤(pán)事件還能繼續(xù)分發(fā)下去給其它對(duì)象處理,那么它所實(shí)現(xiàn)的Window.Callback接口的成員函數(shù)dispatchKeyEvent的返回值handled就會(huì)等于false,這時(shí)候DecorView類(lèi)的成員函數(shù)dispatchKeyEvent就會(huì)將該鍵盤(pán)事件分發(fā)給與當(dāng)前正在處理的DecorView對(duì)象所關(guān)聯(lián)的一個(gè)PhoneWindow對(duì)象的成員函數(shù)onKeyDown或者onKeyUp來(lái)處理,取決于變量isDown的值是true還是false,即當(dāng)前發(fā)生的鍵盤(pán)事件是與按鍵按下有關(guān),還是與按鍵松開(kāi)有關(guān)。
PhoneWindow類(lèi)的成員函數(shù)onKeyDown和onKeyUp主要是有來(lái)監(jiān)控一些特殊按鍵事件,例如電話鍵和音量鍵,以便可以執(zhí)行一些對(duì)應(yīng)的邏輯。例如,當(dāng)按下電話鍵時(shí),就打開(kāi)撥號(hào)程序;又如,當(dāng)按下音量鍵時(shí),就調(diào)節(jié)音量的大小。
接下來(lái),我們就繼續(xù)分析Activity類(lèi)所實(shí)現(xiàn)的Window.Callback接口的成員函數(shù)dispatchKeyEvent的實(shí)現(xiàn),以便可以了解鍵盤(pán)事件在Activity組件窗口的分發(fā)過(guò)程。
Step 7. Activity.dispatchKeyEvent
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks {
......
public boolean dispatchKeyEvent(KeyEvent event) {
......
Window win = getWindow();
if (win.superDispatchKeyEvent(event)) {
return true;
}
View decor = mDecor;
if (decor == null) decor = win.getDecorView();
return event.dispatch(this, decor != null
? decor.getKeyDispatcherState() : null, this);
}
......
}
這個(gè)函數(shù)定義在文件frameworks/base/core/java/android/app/Activity.java中。
Activity類(lèi)的成員函數(shù)getWindow返回的是與當(dāng)前正處理的Activity組件所關(guān)聯(lián)的一個(gè)PhoneWindow對(duì)象,Activity類(lèi)的成員函數(shù)dispatchKeyEvent獲得了這個(gè)PhoneWindow對(duì)象之后,就會(huì)調(diào)用它的成員函數(shù)superDispatchKeyEvent,以便可以將參數(shù)event所描述的鍵盤(pán)事件分發(fā)給它處理。
這個(gè)PhoneWindow對(duì)象在處理完成參數(shù)event所描述的鍵盤(pán)事件之后,如果希望該鍵盤(pán)事件能繼續(xù)往下分發(fā),那么Activity類(lèi)的成員函數(shù)dispatchKeyEvent就會(huì)將該鍵盤(pán)事件分發(fā)給當(dāng)前正在處理的Activity組件處理,這是通過(guò)調(diào)用參數(shù)event所描述的一個(gè)KeyEvent對(duì)象的成員函數(shù)dispatch來(lái)實(shí)現(xiàn)的。
注意,在調(diào)用event所描述的一個(gè)KeyEvent對(duì)象的成員函數(shù)dispatch的時(shí)候,第一個(gè)參數(shù)指定為當(dāng)前正在處理的Activity組件所實(shí)現(xiàn)的一個(gè)KeyEvent.Callback接口。參數(shù)event所指向的一個(gè)KeyEvent對(duì)象的成員函數(shù)dispatch的執(zhí)行的過(guò)程中,就會(huì)相應(yīng)地調(diào)用這個(gè)KeyEvent.Callback接口的成員函數(shù)onKeyDown、onKeyUp或者onKeyMultiple來(lái)處理它所描述的鍵盤(pán)事件,實(shí)際上就是調(diào)用Activity類(lèi)的成員函數(shù)onKeyDown、onKeyUp或者onKeyMultiple來(lái)處理參數(shù)event所描述的鍵盤(pán)事件。因此,我們?cè)谧远x一個(gè)Activity組件時(shí),如果需要處理分發(fā)給該Activity組件的鍵盤(pán)事件,那么就需要重寫(xiě)父類(lèi)Activity的成員函數(shù)onKeyDown、onKeyUp或者onKeyMultiple。
接下來(lái),我們就繼續(xù)分析PhoneWindow類(lèi)的成員函數(shù)superDispatchKeyEvent的實(shí)現(xiàn),以便可以了解鍵盤(pán)事件在Activity組件窗口的分發(fā)過(guò)程。
Step 8. PhoneWindow.superDispatchKeyEvent
public class PhoneWindow extends Window implements MenuBuilder.Callback {
......
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
......
@Override
public boolean superDispatchKeyEvent(KeyEvent event) {
return mDecor.superDispatchKeyEvent(event);
}
......
}
這個(gè)函數(shù)定義在文件frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java中。
PhoneWindow類(lèi)的成員變量mDecor描述的是當(dāng)前正在處理的Activity組件窗口的頂層視圖,PhoneWindow類(lèi)的成員函數(shù)superDispatchKeyEvent通過(guò)調(diào)用它所指向的一個(gè)DecorView對(duì)象的成員函數(shù)superDispatchKeyEvent來(lái)處理參數(shù)event所描述的鍵盤(pán)事件。
Step 9. DecorView.superDispatchKeyEvent
public class PhoneWindow extends Window implements MenuBuilder.Callback {
......
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
......
public boolean superDispatchKeyEvent(KeyEvent event) {
return super.dispatchKeyEvent(event);
}
......
}
......
}
這個(gè)函數(shù)定義在文件frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java中。
DecorView類(lèi)的成員函數(shù)superDispatchKeyEvent的實(shí)現(xiàn)很簡(jiǎn)單,它只是調(diào)用父類(lèi)ViewGroup的成員函數(shù)dispatchKeyEvent來(lái)處理參數(shù)event所描述的鍵盤(pán)事件。
Step 10. ViewGroup.dispatchKeyEvent
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
......
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
return super.dispatchKeyEvent(event);
} else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
return mFocused.dispatchKeyEvent(event);
}
return false;
}
......
}
這個(gè)函數(shù)定義在文件frameworks/base/core/java/android/view/ViewGroup.java中。
ViewGroup類(lèi)的成員函數(shù)dispatchKeyEvent的實(shí)現(xiàn)與在前面的Step 3中所介紹的ViewGroup類(lèi)的成員函數(shù)dispatchKeyEventPreIme的實(shí)現(xiàn)是類(lèi)似的,即如果當(dāng)前正在處理的視圖容器能夠獲得焦點(diǎn)并且該視圖容器的大小已經(jīng)計(jì)算好了,那么就會(huì)將參數(shù)event所描述的鍵盤(pán)事件分發(fā)給它的父類(lèi)View的成員函數(shù)dispatchKeyEvent來(lái)處理,否則的話,如果當(dāng)前正在處理的視圖容器有一個(gè)焦點(diǎn)子視圖,并且這個(gè)焦點(diǎn)子視圖的大小已經(jīng)計(jì)算好了,那么就將參數(shù)event所描述的鍵盤(pán)事件分發(fā)給該焦點(diǎn)子視圖的父類(lèi)View的成員函數(shù)dispatchKeyEvent來(lái)處理。
從前面的調(diào)用過(guò)程可以知道,當(dāng)前正在處理的視圖容器即為Activity組件窗口的頂層視圖。我們假設(shè)在該頂層視圖中,獲得焦點(diǎn)的是一個(gè)TextView控件,并且這個(gè)TextView控件的大小已經(jīng)計(jì)算好了,那么接下來(lái)就會(huì)調(diào)用這個(gè)TextView控件的父類(lèi)View的成員函數(shù)dispatchKeyEvent來(lái)處理參數(shù)event所描述的鍵盤(pán)事件。
Step 11. View.dispatchKeyEvent
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
......
private OnKeyListener mOnKeyListener;
......
public boolean dispatchKeyEvent(KeyEvent event) {
// If any attached key listener a first crack at the event.
//noinspection SimplifiableIfStatement
......
if (mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
return true;
}
return event.dispatch(this, mAttachInfo != null
? mAttachInfo.mKeyDispatchState : null, this);
}
......
}
這個(gè)函數(shù)定義在文件frameworks/base/core/java/android/view/View.java中。
當(dāng)View類(lèi)的成員變量mOnKeyListener的值不等于null時(shí),它所指向的一個(gè)OnKeyListener對(duì)象描述的是注冊(cè)到當(dāng)前正在處理的視圖的一個(gè)鍵盤(pán)事件監(jiān)聽(tīng)器。在這種情況下,如果當(dāng)前正在處理的視圖是處于啟用狀態(tài)的,即它的成員變量mViewFlags的ENABLED位等于1,那么參數(shù)event所描述的鍵盤(pán)事件就先分給該鍵盤(pán)事件監(jiān)聽(tīng)器處理,這是通過(guò)調(diào)用View類(lèi)的成員變量mOnKeyListener所指向的一個(gè)OnKeyListener對(duì)象的成員函數(shù)onKey來(lái)實(shí)現(xiàn)的。
注冊(cè)到當(dāng)前正在處理的視圖的鍵盤(pán)事件監(jiān)聽(tīng)器在處理完成參數(shù)event所描述的鍵盤(pán)事件之后,如果希望該鍵盤(pán)事件還能繼續(xù)往下處理,那么View類(lèi)的成員函數(shù)dispatchKeyEvent就會(huì)繼續(xù)調(diào)用參數(shù)event所指向的一個(gè)KeyEvent對(duì)象的成員函數(shù)dispatch來(lái)處理該鍵盤(pán)事件。
接下來(lái),我們就繼續(xù)分析KeyEvent類(lèi)的成員函數(shù)dispatch的實(shí)現(xiàn),以便可以了解鍵盤(pán)事件在Activity組件窗口的分發(fā)過(guò)程。
Step 12. KeyEvent.dispatch
public class KeyEvent extends InputEvent implements Parcelable {
......
public final boolean dispatch(Callback receiver, DispatcherState state,
Object target) {
switch (mAction) {
case ACTION_DOWN: {
......
boolean res = receiver.onKeyDown(mKeyCode, this);
......
return res;
}
case ACTION_UP:
......
return receiver.onKeyUp(mKeyCode, this);
case ACTION_MULTIPLE:
final int count = mRepeatCount;
final int code = mKeyCode;
if (receiver.onKeyMultiple(code, count, this)) {
return true;
}
......
return false;
}
return false;
}
......
}
這個(gè)函數(shù)定義在文件frameworks/base/core/java/android/view/KeyEvent.java中。
從前面的調(diào)用過(guò)程可以知道,參數(shù)receiver指向的是一個(gè)View對(duì)象所實(shí)現(xiàn)的一個(gè)KeyEvent.Callback接口,這個(gè)KeyEvent.Callback接口是用來(lái)接收當(dāng)前正在處理的鍵盤(pán)事件。
KeyEvent類(lèi)的成員變量mAction描述的的是當(dāng)前正在處理的鍵盤(pán)事件的類(lèi)型,當(dāng)它的值等于ACTION_DOWN、ACTION_UP和ACTION_MULTIPLE的時(shí)候,KeyEvent類(lèi)的成員函數(shù)dispatch就會(huì)分別調(diào)用參數(shù)receiver所指向的一個(gè)View對(duì)象的成員函數(shù)onKeyDown、onKeyUp和onKeyMultiple來(lái)接收當(dāng)前正在處理的鍵盤(pán)事件。
假設(shè)當(dāng)前正在處理的鍵盤(pán)事件是與按鍵按下相關(guān)的,即KeyEvent類(lèi)的成員變量mAction的值等于ACTION_DOWN,那么接下來(lái)就會(huì)調(diào)用參數(shù)receiver所指向的一個(gè)View對(duì)象的成員函數(shù)onKeyDown來(lái)接收當(dāng)前正在處理的鍵盤(pán)事件。
由于前面我們已經(jīng)假設(shè)了當(dāng)前獲得焦點(diǎn)的是一個(gè)TextView控件,因此,參數(shù)receiver指向的實(shí)際上是一個(gè)TextView對(duì)象。TextView類(lèi)重寫(xiě)了父類(lèi)View的成員函數(shù)onKeyDown,因此,接下來(lái)KeyEvent類(lèi)的成員函數(shù)dispatch就會(huì)調(diào)用TextView類(lèi)的成員函數(shù)onKeyDown來(lái)接收當(dāng)前正在處理的鍵盤(pán)事件。
Step 13. TextView.onKeyDown
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
......
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
int which = doKeyDown(keyCode, event, null);
if (which == 0) {
// Go through default dispatching.
return super.onKeyDown(keyCode, event);
}
return true;
}
......
}
這個(gè)函數(shù)定義在文件frameworks/base/core/java/android/widget/TextView.java中。
TextView類(lèi)的成員函數(shù)onKeyDown調(diào)用另外一個(gè)成員函數(shù)doKeyDown來(lái)處理參數(shù)event所描述的鍵盤(pán)事件,以便可以相應(yīng)地改變當(dāng)前獲得焦點(diǎn)的TextView控件的UI。當(dāng)TextView類(lèi)的成員函數(shù)doKeyDown的返回值which等于0的時(shí)候,就表示當(dāng)前獲得焦點(diǎn)的TextView控件希望參數(shù)event所描述的鍵盤(pán)事件可以繼續(xù)分發(fā)給它的父類(lèi)View處理,這是通過(guò)調(diào)用父類(lèi)View的成員函數(shù)onKeyDown來(lái)實(shí)現(xiàn)的。
至此,我們就分析完成TextView控件獲得鍵盤(pán)事件的過(guò)程了,整個(gè)TextView控件的實(shí)現(xiàn)框架也分析完成了。
在Android系統(tǒng)中,其它的Android控件與TextView控件的實(shí)現(xiàn)框架都是類(lèi)似的,區(qū)別就在于實(shí)現(xiàn)細(xì)節(jié)和所表現(xiàn)的UI不一樣,而且它們一般都有一個(gè)共同的特點(diǎn),那就是都在宿主窗口的繪圖表面上進(jìn)行UI繪制。在接下來(lái)的一篇文章中,我們就分析另外一個(gè)種以SurfaceView為代表的特殊控件,它們的UI是繪制在一個(gè)專(zhuān)用的繪圖表面上面的,即它們不與宿主窗口共享同一個(gè)繪圖表面。敬請(qǐng)關(guān)注!