继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Android View总结

眼眸繁星
关注TA
已关注
手记 109
粉丝 7
获赞 59

关于Android View控件

Android中控件大致被分为两类ViewGroup,View。ViewGroup作为容器管理View。Android视图,是类似于Dom树的架构。父视图负责测量定位绘制等操作。我们经常在用的findViewById 方法代价昂贵的原因,就是因为他负责至上而下遍历整棵控件树,来寻找View实例,在重复操作中尽量少用。现在在用的很多控件都是直接或者间接继承自View的,

                                              

Android UI界面架构

每个Activity包含一个PhoneWindow对象,PhoneWindow设置DecorView为应用窗口的根视图。在里面就是熟悉的TitleView和ContentView,没错,平时使用的setContentView()就是设置的ContentView。


Android是如何绘制View的?

当一个Activity启动时,会被要求绘制出它的布局。Android框架会处理这个请求,当然前提是Activity提供了合理的布局。绘制从根视图开始,从上至下遍历整棵视图树,每一个ViewGroup负责让自己的子View被绘制,每一个View负责绘制自己,通过draw()方法.绘制过程分三步走。

o    Measure

o    Layout

o    Draw

整个绘制流程是在ViewRoot中的performTraversals()方法展开的。部分源代码如下。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

private void performTraversals() {

    ......

    //最外层的根视图的widthMeasureSpec和heightMeasureSpec由来

    //lp.width和lp.height在创建ViewGroup实例时等于MATCH_PARENT

    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);

    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

    ......

    mView.measure(childWidthMeasureSpec,   childHeightMeasureSpec);

    ......

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

    ......

    mView.draw(canvas);

    ......

}

在绘制之前当然要知道view的尺寸和绘制。所以先进行measu和layout(测量和定位),

Measure过程

1

2

3

4

5

6

7

8

public final void measure(int widthMeasureSpec, int heightMeasureSpec) { 

    //.... 

 

    //回调onMeasure()方法     

    onMeasure(widthMeasureSpec,   heightMeasureSpec); 

    

    //more 

}

计算view的实际大小,获得高宽存入mMeasuredHeight和mMeasureWidth,measure(int, int)传入的两个参数。MeasureSpec是一个32位int值,高2位为测量的模式,低30位为测量的大小。测量的模式可以分为以下三种。

o    EXACTLY
精确值模式,当layout_width或layout_height指定为具体数值,或者为match_parent时,系统使用EXACTLY。

o    AT_MOST
最大值模式,指定为wrap_content时,控件的尺寸不能超过父控件允许的最大尺寸。

o    UNSPECIFIED
不指定测量模式,View想多大就多大,一般不太使用。

根据上面的源码可知,measure方法不可被重写,自定义时需要重写的是onMeasure方法

1

2

3

4

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

          setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),   widthMeasureSpec),

                  getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));

}

查看源码可知最终的高宽是调用setMeasuredDimension()设定的,如果不重写,默认是直接调用getDefaultSize获取尺寸的。

使用View的getMeasuredWidth()和getMeasuredHeight()方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程之后被调用才能返回有效值。

Layout过程

Layout方法就是用来确定view布局的位置,就好像你知道了一件东西的大小以后,总要知道位置才能画上去。

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

layout获取四个参数,左,上,右,下坐标,相对于父视图而言。这里可以看到,使用了刚刚测量的宽和高。

1

2

3

4

5

6

7

8

9

10

11

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

    int oldL = mLeft;

    int oldT = mTop;

    int oldB = mBottom;

    int oldR = mRight;

    boolean changed = setFrame(l, t, r, b);

    if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {

        .....

        onLayout(changed, l, t, r, b);

        .....

}

通过setFrame设置坐标。如果坐标改变过了,则重新进行定位。如果是View对象,那么onLayout是个空方法。因为定位是由ViewGroup确定的。

当layout结束以后getWidth()与getHeight()才会返回正确的值。

这里出现一个问题,getWidth/Height() and getMeasuredWidth/Height()有什么区别?

o    getWidth():View在設定好佈局後整個View的寬度。

o    getMeasuredWidth():對View上的內容進行測量後得到的View內容佔據的寬度


Draw过程

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

public void draw(Canvas canvas) {

        ......

        /*

         * Draw traversal performs several   drawing steps which must be executed

         * in the appropriate order:

         *

         *      1. Draw the background

         *      2. If necessary, save the canvas'   layers to prepare for fading

         *      3. Draw view's content

         *      4. Draw children

         *      5. If necessary, draw the fading edges   and restore layers

         *      6. Draw decorations (scrollbars for   instance)

         */

        // Step 1, draw the background, if needed

        ......

        if (!dirtyOpaque) {

            drawBackground(canvas);

        }

        // skip step 2 & 5 if possible   (common case)

        ......

        // Step 2, save the canvas' layers

        ......

            if (drawTop) {

                canvas.saveLayer(left, top,   right, top + length, null, flags);

            }

        ......

        // Step 3, draw the content

        if (!dirtyOpaque) onDraw(canvas);

        // Step 4, draw the children

        dispatchDraw(canvas);

        // Step 5, draw the fade effect and   restore layers

        ......

        if (drawTop) {

            matrix.setScale(1, fadeHeight * topFadeStrength);

            matrix.postTranslate(left, top);

            fade.setLocalMatrix(matrix);

            p.setShader(fade);

            canvas.drawRect(left, top, right,   top + length, p);

        }

        ......

        // Step 6, draw decorations (scrollbars)

        onDrawScrollBars(canvas);

        ......

    }

重点是第三步调用onDraw方法。其它几步都是绘制一些边边角角的东西比如背景、scrollBar之类的。其中dispatchDraw,是用来递归调用子View,如果没有则不需要。

onDraw方法是需要自己实现的,因为每个控件绘制的内容不同。主要用canvas对象进行绘制,这里就不说了。

原文链接:http://www.apkbus.com/blog-705730-62708.html

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP