Android必知必会之绘图机制

  1. Android中每一个组件的绘制过程,都要经过三个阶段:测量、布局、绘制,分别对应着方法onMeasureonLayoutonDraw(这三个方法定义于View类中)。

    当然,这三个方法都是允许组件自己重定义的方法,来实现组件对自己的尺寸进行测量对自己进行布局以及绘制自己的内容

    onMeasure

    有child views时,要分别对子组件调用相关测量方法,比如measureChildmeasureChildWithMargins等,并根据子组件的dimension来确定自己的尺寸;

    最后要调用setMeasuredDimension(measuredWidth, measuredHeight)方法来保存自己的尺寸信息。

    onLayout

    对于container,需要对子组件进行布局,调用子组件的layout(int l, int t, int r, int b)方法,传入相对于container的座标。最终同样会执行到子组件的onLayout方法来实现对子组件的布局。

    布局说白了就是确定自己的 绘制位置大小,即左上右下四个座标 - 这样也就提供了大小

    对于容器来说,将其中的具体组件布局好了,也就将其自身布局好了。

    onDraw

    使用该方法的参数Canvas进行内容绘制,其中的座标系是组件内座标系。

    其实自定义动画往往就是借助属性动画来对绘制参数进行变化同时于onDraw时体现出变化效果来实现的。

  2. 从源码中看,Android的绘图是从ViewRootImpl类的performTraversals方法开始的,可以把这个方法视为一个顶层的控制方法,在其中控制整个绘图的流程。

    具体情况如下所述:

    首先,在其中会调用performMeasure方法,在performMeasure方法中调用View的measure方法,进而调用到具体组件所实现的onMeasure方法。

    View的measure是final方法,方法原型为:

    public final void measure(int widthMeasureSpec, int heightMeasureSpec)

    也就是说不允许子类修改测量的框架,只能够修改真正进行测量工作的onMeasure方法。

    然后,测量结束会调用performLayout方法,在performLayout方法中调用View的layout方法,该方法原型为:

    public void layout(int l, int t, int r, int b)

    在该方法中会调用View的onLayout方法,对组件进行布局。

    在拓展ViewGroup类的时候,对于所重写的onLayout方法,一般最后一步就是分别调用组件各自的layout方法来“Place the child.”。

    之后,会调用performDraw方法,通过performDraw -> draw -> drawSoftware最终会调用View的draw(Canvas)方法。

    draw方法中会有六步操作,在第三步“draw the content”时会调用onDraw(Canvas)方法,进行内容的绘制。

  3. 如果我们要拓展ViewGroup类实现一个布局,就要在其中重写onMeasure方法来对布局中的组件进行测量,并在获得其中所有组件的尺寸后计算得到布局的尺寸,然后调用setMeasuredDimension方法进行设置;之后还需要重写onLayout方法,在其中调用各个组件的layout方法,传入计算出的组件座标位置,实现对组件的布局。

    至于绘制,则由具体的组件自己重写onDraw方法进行实现,在ViewRootImpl类的performTraversals逻辑中进行控制。

  4. getMeasuredXXX & getXXX

    在测量结束后(调用方法setMeasuredDimension后),就可以调用getMeasuredWidthgetMeasuredHeight来获取视图测量出的宽和高了,在这之前这两个方法返回值均为0。

    在布局结束后,就可以调用方法getWidthgetHeight来获取视图的宽高了。

    由于一般情况下,会根据测量的情况去布局组件,所以这两个方法的返回值是一样的。

  5. 关于MeasureSpec

    MeasureSpec是一个32位的int数,其中前2位用来表示模式,余下30位用来表示size。

    包括的模式有:

    EXACTLY

    表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。

    AT_MOST

    表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。

    系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。

    UNSPECIFIED

    表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见,不太会用到。

  6. performTraversals代码时会发现,比如进行布局的时候,代码中所调用的是performLayout方法,在该方法中所执行的关键操作是

    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

    当时我想,这一个调用怎么实现对所有组件的布局的呢?还因此在代码中找了好一会儿循环语句,现在我明白了:
    这个host是个布局对象,调用其layout会进一步调用其onLayout方法,在onLayout方法中实现对所有子组件的遍历布局。同理,测量也是这样的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章