由于Android系统提供的控件比较多,因此我们只能挑一个比较有代表的控件进行分析。这个比较有代表性的控件便是TextView,其它的一些基础控件,例如Button、EditText和CheckBox等,都是直接或者间接地以它为父类的。每一个控件的实现都是相当复杂的,不过基本上都是一些细节问题,而且不同的控件有不同的实现细节,因此,本文并不打算详细地分析TextView的具体实现,而是从所有控件为了实现自己的功能而需要的东西出发,去分析TextView的实现框架。
那么,控件为了实现自己的功能而需要的东西是什么呢?有两个材料是必不可少的。第一个材料是画布,第二个材料是用户输入。有画布才能绘制UI,而有用户输入才能与用户进行交互。因此,接下来我们主要分析TextView的绘制流程,以及它获得用户输入的过程。用户输入主要包括键盘输入以及触摸屏输入,本文主要关注的是键盘输入。触摸屏输入与键盘输入的获取过程是类似的,读者如果有兴趣的话,可以参照本文的内容来自己研究一下。
这个函数定义在文件frameworks/base/core/java/android/view/ViewRoot.java中。
参数event描述的是窗口接收到的键盘事件,另外一个参数sendDone表示该键盘事件处理完成后,是否需要向系统的输入管理器发送一个通知。
ViewRoot类的成员变量mView描述的是窗口的顶层视图,即它指向的是一个DecorView对象,ViewRoot类的成员函数deliverKeyEvent首先是调用它的成员函数dispatchKeyEventPreIme来让它优先于输入法处理参数event所描述的键盘事件。如果这个DecorView对象的成员函数dispatchKeyEventPreIme的返回值handled等于true,那么就说明参数event所描述的键盘事件已经处理完毕,即ViewRoot类的成员函数deliverKeyEvent不用往下执行了。在这种情况下,如果参数sendDone的值等于true,那么ViewRoot类的成员函数deliverKeyEvent在返回之前,还会调用成员函数finishInputEvent来通知系统的输入管理器,当前激活的窗口已经处理完成刚刚发生的键盘事件了。在接下来的Step 2到Step 4中,我们再详细分析键盘事件优先于输入法分发给窗口处理的过程。
假设窗口不在输入法前面拦截参数event所描述的键盘事件,接下来ViewRoot类的成员函数deliverKeyEvent就会将该键盘事件分发给输入法处理,这个分发过程如下所示:
1. 调用InputMethodManager类的静态成员函数peekInstance获得一个类型为InputMethodManager输入法管理器imm;
2. 调用ViewRoot类的成员函数enqueuePendingEvent将参数event所描述的键盘事件缓存起来,等到输入法处理完成该键盘事件之后,再继续对它进行处理;
3. 调用第1步获得的输入法管理器imm的成员函数dispatchKeyEvent来将参数event所描述的键盘事件分发给输入法处理。
这里有两个地方是需要注意的。第一个地方是只有当前窗口正在显示输入法的情况下,ViewRoot类的成员函数deliverKeyEvent才会将参数event所描述的键盘事件分发给输入法处理,这是通过检查ViewRoot类的成员变量mLastWasImTarget的值是否等于true来确定的。第二个地方是在将参数event所描述的键盘事件分发给输入法处理时,ViewRoot类的成员函数deliverKeyEvent会同时传递一个类型为InputMethodCallback的回调接口给输入法,以便输入法处理完成参数event所描述的键盘事件之后,可以调用这个回调接口的成员函数finishedEvent来向窗口发送一个键盘事件处理完成通知。这个类型为InputMethodCallback的回调接口就保存在ViewRoot类的成员变量mInputMethodCallback中,当它的成员函数finishedEvent被调用的时候,它就会调用ViewRoot类的成员函数deliverKeyEventToViewHierarchy来继续将参数event所描述的键盘事件分发给窗口处理。
如果窗口当前不需要与输入法交互,即ViewRoot类的成员变量mLastWasImTarget的值等于false,那么ViewRoot类的成员函数deliverKeyEvent就会直接调用成员函数deliverKeyEventToViewHierarchy来将参数event所描述的键盘事件分发给窗口处理。
接下来,我们就先析窗口在输入法之前处理键盘输入的过程,接着再分析窗口在输入法之后处理键盘输入的过程。
从前面的分析可以知道,ViewRoot类的成员函数deliverKeyEvent是通过调用DecorView类的成员函数dispatchKeyEventPreIme来将获得的键盘输入优先于输入法分发给窗口处理的。DecorView类的成员函数dispatchKeyEventPreIme是从父类ViewGroup继承下来的,因此,接下来我们就继续分析ViewGroup类的成员函数dispatchKeyEventPreIme的实现。
Step 2. ViewGroup.dispatchKeyEventPreIme
[java]
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;
}
......
}
这个函数定义在文件frameworks/base/core/java/android/view/ViewGroup.java中。
ViewGroup类的成员函数dispatchKeyEventPreIme首先是检查当前正在处理的视图容器是否能够获得焦点。如果能够获得焦点的话,那么ViewGroup类的成员变量mPrivateFlags的FOCUSED位就会等于1。在当前正在处理的视图容器能够获得焦点的情况下,还要检查正在处理的视图容器是否已经计算过大小了,即检查ViewGroup类的成员变量mPrivateFlags的HAS_BOUNDS位是否等于1。只有在已经计算过大小并且能够获得焦点的情况下,那么正在处理的视图容器才有资格处理参数event所描述的键盘事件。注意,正在处理的视图容器是通过调用其父类View的成员函数dispatchKeyEventPreIme来处理参数event所描述的键盘事件的。
如果当前正在处理的视图容器没有资格处理参数event所描述的键盘事件,但是它有一个能够获得焦点的子视图,并且这个子视图的大小也是已经计算好了的,那么ViewGroup类的成员函数dispatchKeyEventPreIme就会将参数event所描述的键盘事件分发给该子视图处理。当前正在处理的视图容器能够获得焦点的子视图是通过ViewGroup类的成员变量mFocused来描述的,通过调用这个成员变量所描述的一个View对象的成员函数dispatchKeyEventPreIme即可将参数event所描述的键盘事件分发给够获得焦点的子视图处理。
一个视图容器是如何知道它的焦点子视图的呢?我们知道,当我们在屏幕上触摸一个窗口时,就会发生一个Pointer事件。这个Pointer事件关联有一个触摸点,通过检查这个触摸点当前是包含在窗口顶层视图的哪一个子视图里面,就可以知道哪一个子视图是焦点子视图了。
从上面的分析可以知道,无论是当前正在处理的视图容器获得焦点,还是它的子视图获得焦点,最终都是通过调用View类的成员函数dispatchKeyEventPreIme来在输入法之前处理参数event所描述的键盘事件,因此,接下来我们就继续分析View类的成员函数dispatchKeyEventPreIme的实现。
Step 3. View.dispatchKeyEventPreIme
[java]
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
......
public boolean dispatchKeyEve
补充:移动开发 , Android ,