View体系详解系列

                            View体系详解(1)

    前言:看了大概一个月SystemUI的相关源码,里面关于自定义View的知识比较多,迫使自己要去了解以前不太懂的显示子系统的知识,以前只知道一些粗略的view知识,如它是用来显示具体画面的,它的载体是window,它可以复写事件处理函数去处理某些点击事件,自定义view要实现onMeasure, onLayout, onDraw等,但是一直比较模糊,只是知道个大概,经过一阵子的源码和博客的阅读,对view体系有了许多新认识和领悟,因此记录下来。计划分以下几部分:

            View体系详解(1):View体系总览

           View体系详解(2):自定义View流程以及系统相关行为

            View体系详解(3):View事件处理机制

*****写的不好请理解,由于自己知识水平和技术经验所限,不可避免有错漏的地方,恳请指正。*******

  View体系总览

    1.首先我们要明确View的概念是什么?源码在View的class文件中有一个介绍:

* This class represents the basic building block for user interface components. A View

* occupies a rectangular area on the screen and is responsible for drawing and

* event handling.

      所以view相当于一个可以在屏幕上面绘制内容的有限区域,并且负责处理具体的交互事件。这也回答了我们为什么自定义view的时候要经过三个步骤:测量(Measure),布局(Layout),还有绘画(Draw),并且当用户希望界面可以响应我们的操作时,我们为什么会调用setOnClickListener()等函数去设置处理事件的逻辑函数,这个后面细说。


    2.那么View又怎么被管理呢?我们在xml文件中定义了view,它怎么就能显示在屏幕上了?我们可以看看在AS中新建一个app工程,最初的xml文件中的写法(部分内容省略),然后调用代码:setContentView(R.layout.activity_main) , 画面就有了,一个空白界面,中间是熟悉的Hello World!字样。

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    tools:context=".MainActivity">

         

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:text="Hello World!"/>

        我们可以看到,外面是一层FrameLayout,查看源码便可以发现,它的父类是ViewGroup,再上去的父类便是View,由此我们可以知道View的管理是通过View树的形式来实现的,比如一开始的画面,它的根节点便是FrameLayout,也就是一个布局,子节点便是被包含在其中的TextView,而setContentView()是封装了xml文件解析,DecorView(activity的根view节点)实例化,view树生成等逻辑,这也是为什么如果你要找到xml文件中定义的TextView实例,要在该函数之后去调用findViewById(),因为只有调用setContentView()后,我们的xml文件的内容才被解析,TextView实例才会创建并被赋值一个唯一的id,这样我们才能找到它的实例。

        既然如此,那么我们可以在布局里面,再嵌套别的布局么?答案是可以的,因为布局也是一个view,不过它可以容纳别的view在里面,因为布局是继承自ViewGroup的,这就说明它本身是一个根节点view。从这我们也可以看出这里用的是组合模式,无论单个view还是一个布局,都可以容纳在他们的根节点布局中,层层嵌套。


    3.如此又引入一个新问题,既然布局中可以嵌套布局,那么怎么管理这些根节点?他们的优先级如何管理呢?如果都让View来处理这些,那么View的职责就十分庞大,不利于维护,所以要把这部分的内容分离出去管理,让view去专注于内容的显示,如此一来,Window就出现了,那么它们的层级就可以同下图来表示。之前网上总是说window是一个抽象的概念,它负责管理view,什么的,我初看十分不解,其实window就是对应一块surface,任何画面的显示都要在surface上绘画出来,所以window的概念可以理解为是上层显示系统的基础,我们需要它来显示画面,那么就需要去WMS中申请一个window,有了Window之后,系统才会执行绘制流程让我们写的view去显示出来,该流程后面会细说。

view的各个层级的关系

    4.层级关系确定后,再看它们的关系,如下面的UML图:

view的UML图

        这个类图可以这么理解:view通过ViewParent接口跟View树根节点沟通, 而View树根节点通过ViewManager这个接口与WindowManager沟通,而WindowManager又通过ViewRootImpl当中的WindowSession类去跟WMS沟通。

      我们可以看到,定义了一个自定义view,或者原生的view,均需要继承view这个父类,其中view有个重要的成员变量:ViewParent mParent 。在图中ViewParent是一个接口,实现这个接口的有ViewGroup和ViewRootImpl,那么mParent是指向谁呢?答案在代码中可以找到。ViewParent接口定义了一系列与父节点沟通的接口函数,子节点可以通过这个接口跟父节点沟通,既然如此,根据层次关系和类图,每个子view的mParent变量指向的便是自己的父节点,也就是ViewGroup,而ViewGroup这个父节点的mParent变量指向的则是ViewRootImpl,这也进一步证明了它们的层级关系。

      管理view的责任,交给ViewManager这个接口,里面就三个函数,分别对应着add,update,remove,由WindowManagerImpl去实现,于是我们获取到了WindowManager的服务引用后,通过下面一段代码就可以把我们一个view显示出来:

        WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);

        WindowManager.LayoutParams lp = new WindowManager.LayoutParams();  //布局参数是用来定义这个window的属性的

        TextView view = new TextView(this);

        view.setText("this is text view");

        wm.addView(view, lp);

      我们可以在类图中看到,ViewGroup也实现了ViewManager的接口,但是它跟WindowManager的表现是不一样的,它的实现是管理view树的接口,如在addView实现函数中,它是把子类加入到了自己的view树结构里,这其中并没有跟WMS的交互。

下面通过一个例子来缕一缕他们的关系吧!


先看界面,这是我自己的手机,我写的一个apk推到手机中,非常简单,就是一个activity,其中有三个TextView对象,分别为1,2,3,还自定义了一个继承于FrameLayout的MyView,里面啥也没干。


实例

代码如下,其中MyView为FrameLayout子类,我在xml文件中用它作为根布局,里面嵌套了一个TextView,也就是view3.

public class MainActivity extends AppCompatActivity {

    MyView myView;

    TextView view1;

    TextView view2;

    TextView view3;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        myView = findViewById(R.id.MyView);

        view3 = findViewById(R.id.text);

        view1 = new TextView(this);

        view1.setText("this is view1");

        view1.setTextColor(Color.RED);

        myView.addView(view1);       

        view2 = new TextView(this);

        view2.setText("this is view2");

        view2.setTextColor(Color.GREEN);

        WindowManager wm = (WindowManager)getSystemService(Context.WINDOW_SERVICE);

        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(300, 200, 2, 0, 0);

        wm.addView(view2, lp);

    }

}

  xml文件如下:

    android:id="@+id/MyView"

    xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:app="http://schemas.android.com/apk/res-auto"

    xmlns:tools="http://schemas.android.com/tools"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    tools:context=".MainActivity">

   

        android:id="@+id/text"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="this is view3"

    问题如下:

  1. 这个activity里,有几个窗口?

    2.view1,2,3,分别属于哪个窗口?

    3.都有addView的调用,那么它们的区别是什么呢?

如果都回答正确,那么view体系的层级关系以及类之间的关系,也就清楚了。

答案如下:

    1. 2个窗口,即两个surface,白色背景的为一个窗口,这里叫它window1,黑色背景的也为一个窗口,叫它window2。

    2.view1是通过MyView的函数addView()添加的,其中MyView继承于FrameLayout,FrameLayout继承于ViewGroup,所以MyView对象是顶层View,view1自然是依附在window1里面,这里的addView操作是把view1这个view添加到MyView的这个view树中去,这里并没有新的窗口产生。而view2虽然也是一个TextView,它甚至都不是一个ViewGroup的子类,仅仅是单个view,但是它也可以作为一个根节点去通过WindowManager的addView()接口把自己添加到一个新窗口中,也就是我们看到的黑色背景window2,所以view2是依附在window2中。至于view3自然也是属于window1的,因为它定义在xml文件中,是属于MyView的子view,它是嵌套在MyView中的。

    3.ViewGroup和WindowManager中都实现了addView接口,但是它们的逻辑不一样,也是因为它们所处在的模块不同导致。ViewGroup是属于View的,它是View树的根节点,调用addView()是把子view添加到自己的View树中,然后重新绘制,这样view就显示出来了,但是没有新窗口产生,还是原来ViewGroup所在的窗口,而WindowManager的addView()则是通过把传入的view添加到一个新创建的window中去显示,也就是说它不属于任何的ViewGroup,这是它们的本质区别,由此我们也可以看到接口编程的作用,一个接口,不同的实现有不同的行为。


总结:

(1). view是通过view树的方式管理的,view树里面可以有多个节点,view树的根节点为顶层view,下面为子view,共同的载体为window,window为一个surface,即一张画布,有了画布我们才能把view的内容画出来,但是view的层级关系,如何绘制出来等,window并不关心,这使得它们各司其职。

  (2). ViewGroup实现了ViewParent和ViewManager,ViewRootImpl实现了ViewParent,WindowManager则实现了ViewManger,从接口的实现可以看出它们三者是view的管理者,但是管理的方式不同:ViewGroup管理的对象为自己的子view,ViewRootImpl管理的是ViewGroup对象,子view的parent是ViewGroup,而ViewGroup的parent则为ViewRootImpl。  而WindowManager管理的是ViewRootImpl,ViewRootImpl的实例会在WindowManager模块中被创建起来并管理,所以view的层级关系为:window > viewroot  > viewgroup > view。

      知道了view的体系架构以后,接下来把调用流程梳理清楚,比如什么时候触发绘制流程,把一个新的子view添加到view树中会发生什么,调用WindowManager的addView()后系统如何创建window,如何实现一个自定义view等等,这将会在:《View体系详解(2):自定义View流程以及系统相关行为 》  中去讲解。

你可能感兴趣的:(View体系详解系列)