在前面一個(gè)系列的文章中,我們以窗口為單位瘩缆,分析了WindowManagerService服務(wù)的實(shí)現(xiàn)。同時(shí)佃蚜,在再前面一個(gè)系列的文章中庸娱,我們又分析了窗口的組成。簡(jiǎn)單來說谐算,窗口就是由一系列的視圖按照一定的布局組織起來的熟尉。實(shí)際上,每一個(gè)視圖都是一個(gè)控件洲脂,這些控制可以將自己的UI繪制在窗口的繪圖表面上斤儿,同時(shí)還可以與用戶進(jìn)行交互,即獲得用戶的鍵盤或者觸摸屏輸入恐锦。在本文中往果,我們就詳細(xì)分析窗口控件的上述實(shí)現(xiàn)原理。
由于android系統(tǒng)提供的控件比較多一铅,因此我們只能挑一個(gè)比較有代表的控件進(jìn)行分析陕贮。這個(gè)比較有代表性的控件便是TextView,其它的一些基礎(chǔ)控件馅闽,例如Button飘蚯、EditText和CheckBox等馍迄,都是直接或者間接地以它為父類的福也。每一個(gè)控件的實(shí)現(xiàn)都是相當(dāng)復(fù)雜的,不過基本上都是一些細(xì)節(jié)問題攀圈,而且不同的控件有不同的實(shí)現(xiàn)細(xì)節(jié)暴凑,因此,本文并不打算詳細(xì)地分析TextView的具體實(shí)現(xiàn)赘来,而是從所有控件為了實(shí)現(xiàn)自己的功能而需要的東西出發(fā)现喳,去分析TextView的實(shí)現(xiàn)框架凯傲。
那么,控件為了實(shí)現(xiàn)自己的功能而需要的東西是什么呢嗦篱?有兩個(gè)材料是必不可少的冰单。第一個(gè)材料是畫布,第二個(gè)材料是用戶輸入灸促。有畫布才能繪制UI诫欠,而有用戶輸入才能與用戶進(jìn)行交互。因此浴栽,接下來我們主要分析TextView的繪制流程荒叼,以及它獲得用戶輸入的過程。用戶輸入主要包括鍵盤輸入以及觸摸屏輸入典鸡,本文主要關(guān)注的是鍵盤輸入被廓。觸摸屏輸入與鍵盤輸入的獲取過程是類似的,讀者如果有興趣的話萝玷,可以參照本文的內(nè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ì)象來描述的法精。其中,PhoneWindow對(duì)象用來描述窗口對(duì)象痴突,DecorView對(duì)象用來描述窗口的頂層視圖搂蜓,ViewRoot對(duì)象除了用來與WindowManagerService服務(wù)通信之外,還用來接收用戶輸入辽装。窗口控件本身也是一個(gè)視圖帮碰,即一個(gè)View對(duì)象,它們是以樹形結(jié)構(gòu)組織在一起形成整個(gè)窗口的UI的拾积。為了簡(jiǎn)單起見殉挽,本文假設(shè)要分析的TextView控件是直接以窗口的頂層視圖為父視圖的,即以DecorView為父視圖拓巧,如圖1所示:
圖1 窗口結(jié)構(gòu)示意圖以及DecorView斯碌、TextView的類關(guān)系圖
圖1顯示的是一個(gè)包含了TextView控件的Activity窗口的結(jié)構(gòu)示意圖以及DecorView、TextView的簡(jiǎn)單類關(guān)系圖肛度,從中可以看出:
1. 用戶輸入首先是由ViewRoot接收傻唾,然后再分發(fā)給TextView處理;
2. DecorView是一個(gè)視圖容器承耿,因此冠骄,它是從ViewGroup繼承下來伪煤,而ViewGroup本身又是從View繼承下來的;
3. TextView是一個(gè)簡(jiǎn)單視圖凛辣,因此抱既,它是直接繼承了View。
接下來扁誓,我們就以圖1所示的Activity窗口為例蝙砌,來分析TextView控件的UI繪制框架及其獲得鍵盤輸入的過程。
一. TextView控件的UI繪制框架
從前面[Android應(yīng)用程序窗口(Activity)的測(cè)量(Measure)跋理、布局(Layout)和繪制(Draw)過程分析]一文可以知道择克,Activity窗口的UI繪制操作分為三步來走,分別是測(cè)量前普、布局和繪制肚邢。
1. 測(cè)量
為了能告訴父視圖自己的所占據(jù)的空間的大小,所有控件都必須要重寫父類View的成員函數(shù)onMeasure拭卿。
TextView類的成員函數(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分別用來描述寬度測(cè)量規(guī)范和高度測(cè)量規(guī)范。測(cè)量規(guī)范使用一個(gè)int值來表法峻厚,這個(gè)int值包含了兩個(gè)分量响蕴。
第一個(gè)是mode分量,使用最高2位來表示惠桃。測(cè)量模式有三種浦夷,分別是MeasureSpec.UNSPECIFIED(0)、MeasureSpec.EXACTLY(1)辜王、和MeasureSpec.AT_MOST(2)劈狐。
第二個(gè)是size分量,使用低30位來表示呐馆。當(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í)際需求來設(shè)置自己的寬度和高度坟岔。
TextView類的成員函數(shù)onMeasure根據(jù)上述規(guī)則計(jì)算好自己的寬度wdith和高度height之后,必須要調(diào)用從父類View繼承下來的成員函數(shù)setMeasuredDimension來通知父視圖它所要設(shè)置的寬度和高度闺阱,否則的話炮车,該函數(shù)調(diào)用結(jié)束之后舵变,就會(huì)拋出一個(gè)類型為IllegalStateException的異常酣溃。
2. 布局
前面的測(cè)量工作實(shí)際上是確定了控件的大小瘦穆,但是控件的位置還未確定∩尥悖控件的位置是通過布局這個(gè)操作來完成的扛或。
我們知道,控件是按照樹形結(jié)構(gòu)組織在一起的碘饼,其中熙兔,子控件的位置由父控件來設(shè)置,也就是說艾恼,只有容器類控件才需要執(zhí)行布局操作住涉,這是通過重寫父類View的成員函數(shù)onLayout來實(shí)現(xiàn)的。從Activity窗口的結(jié)構(gòu)可以知道钠绍,它的頂層視圖是一個(gè)DecorView舆声,這是一個(gè)容器類控件。Activity窗口的布局操作就是從其頂層視圖開始執(zhí)行的柳爽,每碰到一個(gè)容器類的子控件媳握,就調(diào)用它的成員函數(shù)onLayout來讓它有機(jī)會(huì)對(duì)自己的子控件的位置進(jìn)行設(shè)置,依次類推磷脯。
我們常見的FrameLayout蛾找、LinearLayout、RelativeLayout赵誓、TableLayout和AbsoluteLayout打毛,都是屬于容器類控件,因此俩功,它們都需要重寫父類View的成員函數(shù)onLayout隘冲。由于TextView控件不是容器類控件,因此绑雄,它可以不重寫父類View的成員函數(shù)onLayout展辞。
3. 繪制
有了前面兩個(gè)操作之后,控件的位置的大小就確定下來了万牺,接下來就可以對(duì)它們的UI進(jìn)行繪制了罗珍。控件為了能夠繪制自己的UI脚粟,必須要重寫父類View的成員函數(shù)onDraw覆旱。
TextView類的成員函數(shù)onDraw的實(shí)現(xiàn)如下所示:
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
......
@Override
protected void onDraw(Canvas canvas) {
//在畫布canvas上繪制UI
......
}
......
}
這個(gè)函數(shù)定義在文件frameworks/base/core/java/android/widget/TextView.java中。
參數(shù)canvas描述的是一塊畫布核无,控件的UI就是繪制在這塊畫布上面的扣唱。畫布提供了豐富的接口來繪制UI,例如畫線(drawLine)、畫圓(drawCircle)和貼圖(drawBitmap)等等噪沙。有了這些UI畫圖接口之后炼彪,就可以隨心所欲地繪制控件的UI了。
從前面[Android應(yīng)用程序窗口(Activity)的測(cè)量(Measure)正歼、布局(Layout)和繪制(Draw)過程分析](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)程空間來,因此仲墨,窗口的控件就可以在本進(jìn)程內(nèi)訪問這塊匿名共享內(nèi)存了勾缭,實(shí)際上就是往這塊匿名共享內(nèi)存填入U(xiǎn)I數(shù)據(jù)。注意目养,這個(gè)過程執(zhí)行完成之后俩由,控件的UI還沒有反映到屏幕上來,因?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ù)來控制的。因此努释,當(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的過程可以參考前面[Android系統(tǒng)Surface機(jī)制的SurfaceFlinger服務(wù)渲染應(yīng)用程序UI的過程分析](http://blog.csdn.net/luoshengyang/article/details/8079456)一文。
從上面的描述就可以看出逸邦,控件的UI雖然是在一塊簡(jiǎn)單的畫布進(jìn)行繪制恩沛,但是其中蘊(yùn)含了豐富的知識(shí)點(diǎn),并且需要應(yīng)用程序進(jìn)程缕减、WindowManagerService服務(wù)和SurfaceFlinger服務(wù)三方緊密而有序的配合雷客。
如果我們仔細(xì)閱讀[Android應(yīng)用程序窗口(Activity)的測(cè)量(Measure)、布局(Layout)和繪制(Draw)過程分析](http://blog.csdn.net/luoshengyang/article/details/8372924)一文桥狡,還可以得出以下兩個(gè)結(jié)論:
(1). 一個(gè)窗口的所有控件的UI都是繪制在窗口的繪圖表面上的搅裙,也就是說皱卓,一個(gè)窗口的所有控件的UI數(shù)據(jù)都是填寫在同一塊圖形緩沖區(qū)中;
(2). 一個(gè)窗口的所有控件的UI的繪制操作是在主線程中執(zhí)行的部逮,事實(shí)上娜汁,所有與UI相關(guān)的操作都是必須是要在主線程中執(zhí)行,否則的話甥啄,就會(huì)拋出一個(gè)類型為CalledFromWrongThreadException的異常來。
為什么要規(guī)定所有與UI相關(guān)的操作都必須在主線程中執(zhí)行呢炬搭?我們知道蜈漓,這些與UI相關(guān)的操作都涉及到大量的控件內(nèi)部狀態(tài)以及需要訪問窗口的繪圖表面,也就是說宫盔,要大量地訪問控件類的成員變量以及窗口繪圖表面里面的圖形緩沖區(qū)融虽,因此,如果不將這些與UI相關(guān)的操作限定在同一個(gè)線程中執(zhí)行的話灼芭,那么就會(huì)涉及到線程同步問題有额。線程同步的開銷是很大的,因此彼绷,就要保證那些與UI相關(guān)的操作都在同一個(gè)線程中執(zhí)行巍佑。這個(gè)負(fù)責(zé)執(zhí)行UI相關(guān)操作的線程便是應(yīng)用程序進(jìn)程的主線程,因此我們也將應(yīng)用程序進(jìn)程的主線程稱為UI線程寄悯。
我們知道萤衰,應(yīng)用程序進(jìn)程的主線程除了負(fù)責(zé)執(zhí)行與UI相關(guān)的操作之外,還負(fù)責(zé)響應(yīng)用戶的輸入猜旬,因此脆栋,我們就要盡量地避免執(zhí)行很耗時(shí)的UI操作,否則的話洒擦,系統(tǒng)就會(huì)由于應(yīng)用程序進(jìn)程的主線程無法及時(shí)響應(yīng)用戶輸入而彈出ANR對(duì)話框椿争。
那么,有沒有辦法讓某一個(gè)控件的UI享有獨(dú)立的圖形緩沖區(qū)呢熟嫩?也就是這個(gè)控件不將自己的UI數(shù)據(jù)填入到它的宿主窗口的繪圖表面的圖形緩沖區(qū)里面去秦踪。如果可以的話,那么我們就可以在另外一個(gè)獨(dú)立的線程中繪制該控件的UI掸茅。這樣做的好處是顯而易見——可以在這個(gè)獨(dú)立的線程執(zhí)行相對(duì)比較耗時(shí)的UI繪制操作而不會(huì)導(dǎo)致主線程無法及時(shí)響應(yīng)用戶輸入洋侨。答案是肯定的,在接下來的一篇文章中倦蚪,我們就分析一個(gè)可以具有獨(dú)立圖形緩沖區(qū)的控件——SurfaceView希坚。
二. TextView控件獲取鍵盤輸入的過程分析
從前面[Android應(yīng)用程序鍵盤(Keyboard)消息處理機(jī)制分析](http://blog.csdn.net/luoshengyang/article/details/6882903)一文可以知道,每一個(gè)窗口的創(chuàng)建的時(shí)候陵且,都會(huì)與系統(tǒng)的輸入管理器建立一個(gè)用戶輸入接收通道裁僧。輸入管理器在啟動(dòng)兩個(gè)線程个束,其中一個(gè)用來監(jiān)控用戶輸入,即監(jiān)控用戶是否按下或者放開了鍵盤按鍵聊疲,或者是否觸摸了屏幕茬底,另外一個(gè)用來將監(jiān)控到的用戶輸入事件分發(fā)給當(dāng)前激活的窗口來處理,而這個(gè)分發(fā)過程就是通過前面建立的通道來進(jìn)行的获洲。
當(dāng)前激活的窗口接收到輸入管理器分發(fā)過來的用戶輸入事件之后阱表,就會(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來將前面接收到的用戶輸入分發(fā)給合適的控件最爬。其中,ViewRoot類的成員函數(shù)deliverKeyEvent負(fù)責(zé)分發(fā)鍵盤輸入事件门岔,而ViewRoot類的成員函數(shù)deliverPointerEvent負(fù)責(zé)分發(fā)觸摸屏輸入事件爱致。
接下來,我們就從ViewRoot類的成員函數(shù)deliverKeyEvent開始寒随,分析一個(gè)TextView控件獲得鍵盤輸入的過程(獲得觸摸屏輸入的過程是類似的)糠悯,如圖2所示:
圖2 TextView控件獲得鍵盤輸入的過程
這個(gè)過程可以分為14個(gè)步驟,接下來我們就詳細(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描述的是窗口接收到的鍵盤事件,另外一個(gè)參數(shù)sendDone表示該鍵盤事件處理完成后讯泣,是否需要向系統(tǒng)的輸入管理器發(fā)送一個(gè)通知忘朝。
ViewRoot類的成員變量mView描述的是窗口的頂層視圖,即它指向的是一個(gè)DecorView對(duì)象判帮,ViewRoot類的成員函數(shù)deliverKeyEvent首先是調(diào)用它的成員函數(shù)dispatchKeyEventPreIme來讓它優(yōu)先于輸入法處理參數(shù)event所描述的鍵盤事件局嘁。如果這個(gè)DecorView對(duì)象的成員函數(shù)dispatchKeyEventPreIme的返回值handled等于true,那么就說明參數(shù)event所描述的鍵盤事件已經(jīng)處理完畢晦墙,即ViewRoot類的成員函數(shù)deliverKeyEvent不用往下執(zhí)行了悦昵。在這種情況下,如果參數(shù)sendDone的值等于true晌畅,那么ViewRoot類的成員函數(shù)deliverKeyEvent在返回之前但指,還會(huì)調(diào)用成員函數(shù)finishInputEvent來通知系統(tǒng)的輸入管理器,當(dāng)前激活的窗口已經(jīng)處理完成剛剛發(fā)生的鍵盤事件了抗楔。在接下來的Step 2到Step 4中棋凳,我們?cè)僭敿?xì)分析鍵盤事件優(yōu)先于輸入法分發(fā)給窗口處理的過程。
假設(shè)窗口不在輸入法前面攔截參數(shù)event所描述的鍵盤事件连躏,接下來ViewRoot類的成員函數(shù)deliverKeyEvent就會(huì)將該鍵盤事件分發(fā)給輸入法處理剩岳,這個(gè)分發(fā)過程如下所示:
1. 調(diào)用InputMethodManager類的靜態(tài)成員函數(shù)peekInstance獲得一個(gè)類型為InputMethodManager輸入法管理器imm秃流;
2. 調(diào)用ViewRoot類的成員函數(shù)enqueuePendingEvent將參數(shù)event所描述的鍵盤事件緩存起來瓢喉,等到輸入法處理完成該鍵盤事件之后,再繼續(xù)對(duì)它進(jìn)行處理;
3. 調(diào)用第1步獲得的輸入法管理器imm的成員函數(shù)dispatchKeyEvent來將參數(shù)event所描述的鍵盤事件分發(fā)給輸入法處理放典。
這里有兩個(gè)地方是需要注意的铅檩。第一個(gè)地方是只有當(dāng)前窗口正在顯示輸入法的情況下仗颈,ViewRoot類的成員函數(shù)deliverKeyEvent才會(huì)將參數(shù)event所描述的鍵盤事件分發(fā)給輸入法處理刻盐,這是通過檢查ViewRoot類的成員變量mLastWasImTarget的值是否等于true來確定的。第二個(gè)地方是在將參數(shù)event所描述的鍵盤事件分發(fā)給輸入法處理時(shí)蠢箩,ViewRoot類的成員函數(shù)deliverKeyEvent會(huì)同時(shí)傳遞一個(gè)類型為InputMethodCallback的回調(diào)接口給輸入法链蕊,以便輸入法處理完成參數(shù)event所描述的鍵盤事件之后,可以調(diào)用這個(gè)回調(diào)接口的成員函數(shù)finishedEvent來向窗口發(fā)送一個(gè)鍵盤事件處理完成通知谬泌。這個(gè)類型為InputMethodCallback的回調(diào)接口就保存在ViewRoot類的成員變量mInputMethodCallback中滔韵,當(dāng)它的成員函數(shù)finishedEvent被調(diào)用的時(shí)候,它就會(huì)調(diào)用ViewRoot類的成員函數(shù)deliverKeyEventToViewHierarchy來繼續(xù)將參數(shù)event所描述的鍵盤事件分發(fā)給窗口處理呵萨。
如果窗口當(dāng)前不需要與輸入法交互奏属,即ViewRoot類的成員變量mLastWasImTarget的值等于false跨跨,那么ViewRoot類的成員函數(shù)deliverKeyEvent就會(huì)直接調(diào)用成員函數(shù)deliverKeyEventToViewHierarchy來將參數(shù)event所描述的鍵盤事件分發(fā)給窗口處理潮峦。
接下來,我們就先析窗口在輸入法之前處理鍵盤輸入的過程勇婴,接著再分析窗口在輸入法之后處理鍵盤輸入的過程忱嘹。
從前面的分析可以知道,ViewRoot類的成員函數(shù)deliverKeyEvent是通過調(diào)用DecorView類的成員函數(shù)dispatchKeyEventPreIme來將獲得的鍵盤輸入優(yōu)先于輸入法分發(fā)給窗口處理的耕渴。DecorView類的成員函數(shù)dispatchKeyEventPreIme是從父類ViewGroup繼承下來的拘悦,因此,接下來我們就繼續(xù)分析ViewGroup類的成員函數(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類的成員函數(shù)dispatchKeyEventPreIme首先是檢查當(dāng)前正在處理的視圖容器是否能夠獲得焦點(diǎn)。如果能夠獲得焦點(diǎn)的話添诉,那么ViewGroup類的成員變量mPrivateFlags的FOCUSED位就會(huì)等于1屁桑。在當(dāng)前正在處理的視圖容器能夠獲得焦點(diǎn)的情況下,還要檢查正在處理的視圖容器是否已經(jīng)計(jì)算過大小了栏赴,即檢查ViewGroup類的成員變量mPrivateFlags的HAS_BOUNDS位是否等于1蘑斧。只有在已經(jīng)計(jì)算過大小并且能夠獲得焦點(diǎn)的情況下,那么正在處理的視圖容器才有資格處理參數(shù)event所描述的鍵盤事件须眷。注意竖瘾,正在處理的視圖容器是通過調(diào)用其父類View的成員函數(shù)dispatchKeyEventPreIme來處理參數(shù)event所描述的鍵盤事件的。
如果當(dāng)前正在處理的視圖容器沒有資格處理參數(shù)event所描述的鍵盤事件花颗,但是它有一個(gè)能夠獲得焦點(diǎn)的子視圖捕传,并且這個(gè)子視圖的大小也是已經(jīng)計(jì)算好了的,那么ViewGroup類的成員函數(shù)dispatchKeyEventPreIme就會(huì)將參數(shù)event所描述的鍵盤事件分發(fā)給該子視圖處理扩劝。當(dāng)前正在處理的視圖容器能夠獲得焦點(diǎn)的子視圖是通過ViewGroup類的成員變量mFocused來描述的乐横,通過調(diào)用這個(gè)成員變量所描述的一個(gè)View對(duì)象的成員函數(shù)dispatchKeyEventPreIme即可將參數(shù)event所描述的鍵盤事件分發(fā)給夠獲得焦點(diǎn)的子視圖處理求橄。
一個(gè)視圖容器是如何知道它的焦點(diǎn)子視圖的呢?我們知道葡公,當(dāng)我們?cè)谄聊簧嫌|摸一個(gè)窗口時(shí)罐农,就會(huì)發(fā)生一個(gè)Pointer事件。這個(gè)Pointer事件關(guān)聯(lián)有一個(gè)觸摸點(diǎn)催什,通過檢查這個(gè)觸摸點(diǎn)當(dāng)前是包含在窗口頂層視圖的哪一個(gè)子視圖里面涵亏,就可以知道哪一個(gè)子視圖是焦點(diǎn)子視圖了。
從上面的分析可以知道蒲凶,無論是當(dāng)前正在處理的視圖容器獲得焦點(diǎn)气筋,還是它的子視圖獲得焦點(diǎn),最終都是通過調(diào)用View類的成員函數(shù)dispatchKeyEventPreIme來在輸入法之前處理參數(shù)event所描述的鍵盤事件旋圆,因此宠默,接下來我們就繼續(xù)分析View類的成員函數(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類的成員函數(shù)dispatchKeyEventPreIme的實(shí)現(xiàn)很簡(jiǎn)單搀矫,它只是通過調(diào)用另外一個(gè)成員函數(shù)onKeyPreIme來在輸入法之前處理參數(shù)event所描述的鍵盤事件。
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類的成員函數(shù)onKeyPreIme默認(rèn)是不會(huì)在輸入法之前處理參數(shù)event所描述的鍵盤事件的瓤球,因此,我們?cè)趯?shí)現(xiàn)自己的控件的時(shí)候敏弃,如果需要在輸入法之前處理鍵盤輸入卦羡,那么就必須重寫父類View的成員函數(shù)onKeyPreIme。在重寫父類View的成員函數(shù)onKeyPreIme來處理一個(gè)鍵盤事件的時(shí)候麦到,如果不希望這個(gè)鍵盤事件分發(fā)給輸入法處理绿饵,那么就返回一個(gè)true值,否則的話瓶颠,就返回一個(gè)false值拟赊。
我們假設(shè)當(dāng)前獲得焦點(diǎn)的是圖1所示的TextView控件,但是由于TextView類沒有重寫其父類View的成員函數(shù)onKeyPreIme步清,因此要门,參數(shù)event所描述的鍵盤事件接下來就會(huì)繼續(xù)分發(fā)給輸入法或者當(dāng)前激活的窗口處理。
這一步執(zhí)行完成之后廓啊,回到前面的Step 1中欢搜,即ViewRoot類的成員函數(shù)deliverKeyEvent中,無論接下來是否需要先將一個(gè)鍵盤事件分發(fā)給輸入法處理谴轮,最終都會(huì)調(diào)用到ViewRoot類的成員函數(shù)deliverKeyEventToViewHierarchy來繼續(xù)將該鍵盤事件分發(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類的成員函數(shù)deliverKeyEventToViewHierarchy首先將參數(shù)event所描述的鍵盤事件交給當(dāng)前激活的窗口的頂層視圖來處理第步,這是通過調(diào)用ViewRoot類的成員變量mView所描述的一個(gè)DecorView對(duì)象的成員函數(shù)dispatchKeyEvent來實(shí)現(xiàn)的疮装。
如果當(dāng)前激活的窗口的頂層視圖在處理完成參數(shù)event所描述的鍵盤事件之后缘琅,希望該鍵盤事件還能繼續(xù)被ViewRoot類的成員函數(shù)deliverKeyEventToViewHierarchy處理,那么前面調(diào)用DecorView類的成員函數(shù)dispatchKeyEvent得到的返回值keyHandled的值就會(huì)等于false廓推。在這種情況下刷袍,如果參數(shù)event描述的是一個(gè)按下的鍵盤事件,即變量isDown的值等于true樊展,那么ViewRoot類的成員函數(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類的成員變量mView所描述的一個(gè)DecorView對(duì)象的成員函數(shù)findFocus的返回值focused不等于null,那么接下來就要根據(jù)變量direction的值來決定下一個(gè)焦點(diǎn)子視圖是誰墩弯。例如吩跋,假設(shè)變量direction的值等于View.FOCUS_LEFT,那么就表示在當(dāng)前的焦點(diǎn)子視圖focused的左邊查找一個(gè)最靠近的子視圖作為下一個(gè)焦點(diǎn)子視圖最住,這是通過調(diào)用當(dāng)前焦點(diǎn)子視圖focused的成員函數(shù)focusSearch來實(shí)現(xiàn)的钞澳。
一旦找到了下一個(gè)焦點(diǎn)子視圖v怠惶,并且該子視圖不是當(dāng)前的焦點(diǎn)子視圖focused涨缚,那么ViewRoot類的成員函數(shù)deliverKeyEventToViewHierarchy就需要將子視圖v設(shè)置為焦點(diǎn)子視圖,這是通過調(diào)用變量v所描述的一個(gè)View對(duì)象的成員函數(shù)requestFocus來實(shí)現(xiàn)的策治。
通過前面的操作脓魏,參數(shù)event所描述的鍵盤事件就處理完成了。如果這時(shí)候參數(shù)sendDone的值等于true通惫,那么就表示需要通知系統(tǒng)的輸入管理器茂翔,參數(shù)event所描述的鍵盤事件已經(jīng)處理完成了,這是通過調(diào)用ViewRoot類的成員函數(shù)finishInputEvent來實(shí)現(xiàn)的履腋。
接下來珊燎,我們就繼續(xù)分析DecorView類的成員函數(shù)dispatchKeyEvent的實(shí)現(xiàn),以便可以了解窗口的頂層視圖分發(fā)鍵盤事件的過程遵湖。
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類的成員函數(shù)getCallback是從父類Window繼承下來的,它返回的是一個(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ì)象接收到鍵盤事件的時(shí)候迁沫,就可以該鍵盤事件分發(fā)給與它所關(guān)聯(lián)的Activity組件處理芦瘾。
DecorView類的成員變量mFeatureId用來描述當(dāng)前正在處理的DecorView對(duì)象的特征捌蚊,當(dāng)它的值小于0的時(shí)候,就表示當(dāng)前正在處理的一個(gè)DecorView對(duì)象是用來描述一個(gè)Activity組件窗口的頂層視圖的近弟。
因此缅糟,當(dāng)當(dāng)前正在處理的DecorView對(duì)象描述的是一個(gè)Activity組件窗口的頂層視圖,并且這個(gè)Activity組件實(shí)現(xiàn)有一個(gè)Window.Callback接口時(shí)祷愉,DecorView類的成員函數(shù)dispatchKeyEvent就會(huì)調(diào)用該Window.Callback接口的成員函數(shù)dispatchKeyEvent來通知對(duì)應(yīng)的Activity組件溺拱,它接收到一個(gè)鍵盤事件了。否則的話谣辞,參數(shù)event所描述的鍵盤事件就會(huì)被分發(fā)給當(dāng)前正在處理的DecorView對(duì)象的父對(duì)象來處理迫摔,這是通過調(diào)用DecorView類的父類View的成員函數(shù)dispatchKeyEvent來實(shí)現(xiàn)的。
我們假設(shè)當(dāng)前正在處理的DecorView對(duì)象描述的是一個(gè)Activity組件窗口的頂層視圖泥从,并且這個(gè)Activity組件實(shí)現(xiàn)有一個(gè)Window.Callback接口句占,那么參數(shù)event所描述的鍵盤事件接下來就會(huì)分給該Activity組件處理。如果該Activity組件在處理完成這個(gè)鍵盤事件之后躯嫉,希望該鍵盤事件還能繼續(xù)分發(fā)下去給其它對(duì)象處理纱烘,那么它所實(shí)現(xiàn)的Window.Callback接口的成員函數(shù)dispatchKeyEvent的返回值handled就會(huì)等于false,這時(shí)候DecorView類的成員函數(shù)dispatchKeyEvent就會(huì)將該鍵盤事件分發(fā)給與當(dāng)前正在處理的DecorView對(duì)象所關(guān)聯(lián)的一個(gè)PhoneWindow對(duì)象的成員函數(shù)onKeyDown或者onKeyUp來處理祈餐,取決于變量isDown的值是true還是false擂啥,即當(dāng)前發(fā)生的鍵盤事件是與按鍵按下有關(guān),還是與按鍵松開有關(guān)帆阳。
PhoneWindow類的成員函數(shù)onKeyDown和onKeyUp主要是有來監(jiān)控一些特殊按鍵事件哺壶,例如電話鍵和音量鍵,以便可以執(zhí)行一些對(duì)應(yīng)的邏輯蜒谤。例如山宾,當(dāng)按下電話鍵時(shí),就打開撥號(hào)程序鳍徽;又如资锰,當(dāng)按下音量鍵時(shí),就調(diào)節(jié)音量的大小阶祭。
接下來绷杜,我們就繼續(xù)分析Activity類所實(shí)現(xiàn)的Window.Callback接口的成員函數(shù)dispatchKeyEvent的實(shí)現(xiàn),以便可以了解鍵盤事件在Activity組件窗口的分發(fā)過程濒募。
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類的成員函數(shù)getWindow返回的是與當(dāng)前正處理的Activity組件所關(guān)聯(lián)的一個(gè)PhoneWindow對(duì)象,Activity類的成員函數(shù)dispatchKeyEvent獲得了這個(gè)PhoneWindow對(duì)象之后萨咳,就會(huì)調(diào)用它的成員函數(shù)superDispatchKeyEvent懊缺,以便可以將參數(shù)event所描述的鍵盤事件分發(fā)給它處理。
這個(gè)PhoneWindow對(duì)象在處理完成參數(shù)event所描述的鍵盤事件之后,如果希望該鍵盤事件能繼續(xù)往下分發(fā)鹃两,那么Activity類的成員函數(shù)dispatchKeyEvent就會(huì)將該鍵盤事件分發(fā)給當(dāng)前正在處理的Activity組件處理遗座,這是通過調(diào)用參數(shù)event所描述的一個(gè)KeyEvent對(duì)象的成員函數(shù)dispatch來實(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í)行的過程中馋记,就會(huì)相應(yīng)地調(diào)用這個(gè)KeyEvent.Callback接口的成員函數(shù)onKeyDown号坡、onKeyUp或者onKeyMultiple來處理它所描述的鍵盤事件,實(shí)際上就是調(diào)用Activity類的成員函數(shù)onKeyDown梯醒、onKeyUp或者onKeyMultiple來處理參數(shù)event所描述的鍵盤事件宽堆。因此,我們?cè)谧远x一個(gè)Activity組件時(shí)茸习,如果需要處理分發(fā)給該Activity組件的鍵盤事件畜隶,那么就需要重寫父類Activity的成員函數(shù)onKeyDown、onKeyUp或者onKeyMultiple号胚。
接下來籽慢,我們就繼續(xù)分析PhoneWindow類的成員函數(shù)superDispatchKeyEvent的實(shí)現(xiàn),以便可以了解鍵盤事件在Activity組件窗口的分發(fā)過程猫胁。
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類的成員變量mDecor描述的是當(dāng)前正在處理的Activity組件窗口的頂層視圖,PhoneWindow類的成員函數(shù)superDispatchKeyEvent通過調(diào)用它所指向的一個(gè)DecorView對(duì)象的成員函數(shù)superDispatchKeyEvent來處理參數(shù)event所描述的鍵盤事件弃秆。
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類的成員函數(shù)superDispatchKeyEvent的實(shí)現(xiàn)很簡(jiǎn)單,它只是調(diào)用父類ViewGroup的成員函數(shù)dispatchKeyEvent來處理參數(shù)event所描述的鍵盤事件驾茴。
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類的成員函數(shù)dispatchKeyEvent的實(shí)現(xiàn)與在前面的Step 3中所介紹的ViewGroup類的成員函數(shù)dispatchKeyEventPreIme的實(shí)現(xiàn)是類似的氢卡,即如果當(dāng)前正在處理的視圖容器能夠獲得焦點(diǎn)并且該視圖容器的大小已經(jīng)計(jì)算好了锈至,那么就會(huì)將參數(shù)event所描述的鍵盤事件分發(fā)給它的父類View的成員函數(shù)dispatchKeyEvent來處理,否則的話译秦,如果當(dāng)前正在處理的視圖容器有一個(gè)焦點(diǎn)子視圖峡捡,并且這個(gè)焦點(diǎn)子視圖的大小已經(jīng)計(jì)算好了,那么就將參數(shù)event所描述的鍵盤事件分發(fā)給該焦點(diǎn)子視圖的父類View的成員函數(shù)dispatchKeyEvent來處理筑悴。
從前面的調(diào)用過程可以知道们拙,當(dāng)前正在處理的視圖容器即為Activity組件窗口的頂層視圖。我們假設(shè)在該頂層視圖中阁吝,獲得焦點(diǎn)的是一個(gè)TextView控件砚婆,并且這個(gè)TextView控件的大小已經(jīng)計(jì)算好了,那么接下來就會(huì)調(diào)用這個(gè)TextView控件的父類View的成員函數(shù)dispatchKeyEvent來處理參數(shù)event所描述的鍵盤事件。
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類的成員變量mOnKeyListener的值不等于null時(shí)坷虑,它所指向的一個(gè)OnKeyListener對(duì)象描述的是注冊(cè)到當(dāng)前正在處理的視圖的一個(gè)鍵盤事件監(jiān)聽器。在這種情況下埂奈,如果當(dāng)前正在處理的視圖是處于啟用狀態(tài)的迄损,即它的成員變量mViewFlags的ENABLED位等于1,那么參數(shù)event所描述的鍵盤事件就先分給該鍵盤事件監(jiān)聽器處理账磺,這是通過調(diào)用View類的成員變量mOnKeyListener所指向的一個(gè)OnKeyListener對(duì)象的成員函數(shù)onKey來實(shí)現(xiàn)的芹敌。
注冊(cè)到當(dāng)前正在處理的視圖的鍵盤事件監(jiān)聽器在處理完成參數(shù)event所描述的鍵盤事件之后,如果希望該鍵盤事件還能繼續(xù)往下處理垮抗,那么View類的成員函數(shù)dispatchKeyEvent就會(huì)繼續(xù)調(diào)用參數(shù)event所指向的一個(gè)KeyEvent對(duì)象的成員函數(shù)dispatch來處理該鍵盤事件氏捞。
接下來,我們就繼續(xù)分析KeyEvent類的成員函數(shù)dispatch的實(shí)現(xiàn)冒版,以便可以了解鍵盤事件在Activity組件窗口的分發(fā)過程幌衣。
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)用過程可以知道壤玫,參數(shù)receiver指向的是一個(gè)View對(duì)象所實(shí)現(xiàn)的一個(gè)KeyEvent.Callback接口豁护,這個(gè)KeyEvent.Callback接口是用來接收當(dāng)前正在處理的鍵盤事件。
KeyEvent類的成員變量mAction描述的的是當(dāng)前正在處理的鍵盤事件的類型欲间,當(dāng)它的值等于ACTION_DOWN楚里、ACTION_UP和ACTION_MULTIPLE的時(shí)候,KeyEvent類的成員函數(shù)dispatch就會(huì)分別調(diào)用參數(shù)receiver所指向的一個(gè)View對(duì)象的成員函數(shù)onKeyDown猎贴、onKeyUp和onKeyMultiple來接收當(dāng)前正在處理的鍵盤事件班缎。
假設(shè)當(dāng)前正在處理的鍵盤事件是與按鍵按下相關(guān)的,即KeyEvent類的成員變量mAction的值等于ACTION_DOWN她渴,那么接下來就會(huì)調(diào)用參數(shù)receiver所指向的一個(gè)View對(duì)象的成員函數(shù)onKeyDown來接收當(dāng)前正在處理的鍵盤事件达址。
由于前面我們已經(jīng)假設(shè)了當(dāng)前獲得焦點(diǎn)的是一個(gè)TextView控件,因此趁耗,參數(shù)receiver指向的實(shí)際上是一個(gè)TextView對(duì)象沉唠。TextView類重寫了父類View的成員函數(shù)onKeyDown,因此苛败,接下來KeyEvent類的成員函數(shù)dispatch就會(huì)調(diào)用TextView類的成員函數(shù)onKeyDown來接收當(dāng)前正在處理的鍵盤事件满葛。
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類的成員函數(shù)onKeyDown調(diào)用另外一個(gè)成員函數(shù)doKeyDown來處理參數(shù)event所描述的鍵盤事件罢屈,以便可以相應(yīng)地改變當(dāng)前獲得焦點(diǎn)的TextView控件的UI嘀韧。當(dāng)TextView類的成員函數(shù)doKeyDown的返回值which等于0的時(shí)候,就表示當(dāng)前獲得焦點(diǎn)的TextView控件希望參數(shù)event所描述的鍵盤事件可以繼續(xù)分發(fā)給它的父類View處理缠捌,這是通過調(diào)用父類View的成員函數(shù)onKeyDown來實(shí)現(xiàn)的锄贷。
至此,我們就分析完成TextView控件獲得鍵盤事件的過程了,整個(gè)TextView控件的實(shí)現(xiàn)框架也分析完成了谊却。
在Android系統(tǒng)中蹂随,其它的Android控件與TextView控件的實(shí)現(xiàn)框架都是類似的,區(qū)別就在于實(shí)現(xiàn)細(xì)節(jié)和所表現(xiàn)的UI不一樣因惭,而且它們一般都有一個(gè)共同的特點(diǎn)岳锁,那就是都在宿主窗口的繪圖表面上進(jìn)行UI繪制。在接下來的一篇文章中蹦魔,我們就分析另外一個(gè)種以SurfaceView為代表的特殊控件激率,它們的UI是繪制在一個(gè)專用的繪圖表面上面的,即它們不與宿主窗口共享同一個(gè)繪圖表面勿决。敬請(qǐng)關(guān)注乒躺!