Android控件TextView的实现原理分析

本文深入分析了Android控件TextView的实现原理,包括UI绘制的测量、布局和绘制三个步骤,以及键盘输入的处理过程。TextView作为基础控件,其绘制在窗口的绘图表面上,通过onMeasure、onLayout和onDraw方法完成。键盘输入则通过ViewRoot的deliverKeyEvent系列方法分发,经过多个步骤最终到达TextView的onKeyDown进行处理。文章还探讨了控件与输入法交互及焦点管理等内容。
摘要由CSDN通过智能技术生成

分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow

也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!

               

        在前面一个系列的文章中,我们以窗口为单位,分析了WindowManagerService服务的实现。同时,在再前面一个系列的文章中,我们又分析了窗口的组成。简单来说,窗口就是由一系列的视图按照一定的布局组织起来的。实际上,每一个视图都是一个控件,这些控制可以将自己的UI绘制在窗口的绘图表面上,同时还可以与用户进行交互,即获得用户的键盘或者触摸屏输入。在本文中,我们就详细分析窗口控件的上述实现原理。

《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

        由于Android系统提供的控件比较多,因此我们只能挑一个比较有代表的控件进行分析。这个比较有代表性的控件便是TextView,其它的一些基础控件,例如Button、EditText和CheckBox等,都是直接或者间接地以它为父类的。每一个控件的实现都是相当复杂的,不过基本上都是一些细节问题,而且不同的控件有不同的实现细节,因此,本文并不打算详细地分析TextView的具体实现,而是从所有控件为了实现自己的功能而需要的东西出发,去分析TextView的实现框架。

        那么,控件为了实现自己的功能而需要的东西是什么呢?有两个材料是必不可少的。第一个材料是画布,第二个材料是用户输入。有画布才能绘制UI,而有用户输入才能与用户进行交互。因此,接下来我们主要分析TextView的绘制流程,以及它获得用户输入的过程。用户输入主要包括键盘输入以及触摸屏输入,本文主要关注的是键盘输入。触摸屏输入与键盘输入的获取过程是类似的,读者如果有兴趣的话,可以参照本文的内容来自己研究一下。

        从前面Android应用程序窗口(Activity)实现框架简要介绍和学习计划这个系列的文章可以知道,应用程序窗口,即Activity窗口,是由一个PhoneWindow对象,一个DecorView对象,以及一个ViewRoot对象来描述的。其中,PhoneWindow对象用来描述窗口对象,DecorView对象用来描述窗口的顶层视图,ViewRoot对象除了用来与WindowManagerService服务通信之外,还用来接收用户输入。窗口控件本身也是一个视图,即一个View对象,它们是以树形结构组织在一起形成整个窗口的UI的。为了简单起见,本文假设要分析的TextView控件是直接以窗口的顶层视图为父视图的,即以DecorView为父视图,如图1所示:


图1 窗口结构示意图以及DecorView、TextView的类关系图

        

        图1显示的是一个包含了TextView控件的Activity窗口的结构示意图以及DecorView、TextView的简单类关系图,从中可以看出:

        1. 用户输入首先是由ViewRoot接收,然后再分发给TextView处理;

        2. DecorView是一个视图容器,因此,它是从ViewGroup继承下来,而ViewGroup本身又是从View继承下来的;

        3. TextView是一个简单视图,因此,它是直接继承了View。

        接下来,我们就以图1所示的Activity窗口为例,来分析TextView控件的UI绘制框架及其获得键盘输入的过程。

        一. TextView控件的UI绘制框架

        从前面Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制(Draw)过程分析一文可以知道,Activity窗口的UI绘制操作分为三步来走,分别是测量、布局和绘制。

       1. 测量

        为了能告诉父视图自己的所占据的空间的大小,所有控件都必须要重写父类View的成员函数onMeasure。

        TextView类的成员函数onMeasure的实现如下所示:

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;        //计算TextView控件的宽度和高度        ......        setMeasuredDimension(width, height);    }    ......}

        这个函数定义在文件frameworks/base/core/java/android/widget/TextView.java中。

        参数widthMeasureSpec和heightMeasureSpec分别用来描述宽度测量规范和高度测量规范。测量规范使用一个int值来表法,这个int值包含了两个分量。

        第一个是mode分量,使用最高2位来表示。测量模式有三种,分别是MeasureSpec.UNSPECIFIED(0)、MeasureSpec.EXACTLY(1)、和MeasureSpec.AT_MOST(2)。

        第二个是size分量,使用低30位来表示。当mode分量等于MeasureSpec.EXACTLY时,size分量的值就是父视图要求当前控件要设置的宽度或者高度;当mode分量等于MeasureSpec.AT_MOST时,size分量的值就是父视图限定当前控件可以设置的最大宽度或者高度;当mode分量等于MeasureSpec.UNSPECIFIED时,父视图不限定当前控件所设置的宽度或者高度,这时候当前控件一般就按照实际需求来设置自己的宽度和高度。

        TextView类的成员函数onMeasure根据上述规则计算好自己的宽度wdith和高度height之后,必须要调用从父类View继承下来的成员函数setMeasuredDimension来通知父视图它所要设置的宽度和高度,否则的话,该函数调用结束之后,就会抛出一个类型为IllegalStateException的异常。

        2. 布局

        前面的测量工作实际上是确定了控件的大小,但是控件的位置还未确定。控件的位置是通过布局这个操作来完成的。 

        我们知道,控件是按照树形结构组织在一起的,其中,子控件的位置由父控件来设置,也就是说,只有容器类控件才需要执行布局操作,这是通过重写父类View的成员函数onLayout来实现的。从Activity窗口的结构可以知道,它的顶层视图是一个DecorView,这是一个容器类控件。Activity窗口的布局操作就是从其顶层视图开始执行的,每碰到一个容器类的子控件,就调用它的成员函数onLayout来让它有机会对自己的子控件的位置进行设置,依次类推。

        我们常见的FrameLayout、LinearLayout、RelativeLayout、TableLayout和AbsoluteLayout,都是属于容器类控件,因此,它们都需要重写父类View的成员函数onLayout。由于TextView控件不是容器类控件,因此,它可以不重写父类View的成员函数onLayout。

        3. 绘制

        有了前面两个操作之后,控件的位置的大小就确定下来了,接下来就可以对它们的UI进行绘制了。控件为了能够绘制自己的UI,必须要重写父类View的成员函数onDraw。

        TextView类的成员函数onDraw的实现如下所示:

public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {    ......    @Override    protected void onDraw(Canvas canvas) {        //在画布canvas上绘制UI        ......    }    ......}
        这个函数定义在文件frameworks/base/core/java/android/widget/TextView.java中。

        参数canvas描述的是一块画布,控件的UI就是绘制在这块画布上面的。画布提供了丰富的接口来绘制UI,例如画线(drawLine)、画圆(drawCircle)和贴图(drawBitmap)等等。有了这些UI画图接口之后,就可以随心所欲地绘制控件的UI了。

        从前面Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制(Draw)过程分析一文可以知道,Java层的Canvas实际上是封装了C++层的SkCanvas。C++层的SkCanvas内部有一块图形缓冲区,这块图形缓冲区就是窗口的绘图表面(Surface)里面的那块图形缓冲区。

        从前面Android应用程序与SurfaceFlinger服务的关系概述和学习计划一文可以知道,窗口的绘图表面里面的那块图形缓冲区实际上是一块匿名共享内存,它是SurfaceFlinger服务负责创建的。SurfaceFlinger服务创建完成这块匿名共享内存之后,就会将其返回给窗口所运行在的进程。窗口所运行在的进程获得了这块匿名共享内存之后,就会映射到自己的进程空间来,因此,窗口的控件就可以在本进程内访问这块匿名共享内存了,实际上就是往这块匿名共享内存填入UI数据。注意,这个过程执行完成之后,控件的UI还没有反映到屏幕上来,因为这时候将控件的UI数据填入到图形缓冲区而已。

        从前面Android窗口管理服务WindowManagerService的简要介绍和学习计划一文可以知道,窗口的UI的显示是WindowManagerService服务来控制的。因此,当窗口的所有控件都绘制完成自己的UI之后,窗口就会向WindowManagerService服务发送一个Binder进程间程通信请求。WindowManagerService服务接收到这个Binder进程间程通信请求之后,就会请求SurfaceFlinger服务刷新相应的窗口的UI。SurfaceFlinger服务刷新窗口UI的过程可以参考前面Android系统Surface机制的SurfaceFlinger服务渲染应用程序UI的过程分析一文。

        从上面的描述就可以看出,控件的UI虽然是在一块简单的画布进行绘制,但是其中蕴含了丰富的知识点,并且需要应用程序进程、WindowManagerService服务和SurfaceFlinger服务三方紧密而有序的配合。

        如果我们仔细阅读Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制(Draw&#x

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值