简单了解View是What?

Android UI界面架构

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

Android是如何绘制View的?

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

    • Measure
    • Layout
    • Draw

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

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的尺寸和绘制。所以先进行measulayout(测量和定位)。如下图:

Measure过程

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

    //回调onMeasure()方法
    onMeasure(widthMeasureSpec, heightMeasureSpec);  

    //more
}

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

    • 精确值模式,当layout_widthlayout_height指定为具体数值,或者为match_parent时,系统使用EXACTLY。
    • AT_MOST  最大值模式,指定为wrap_content时,控件的尺寸不能超过父控件允许的最大尺寸。
    • UNSPECIFIED  不指定测量模式,View想多大就多大,一般不太使用。

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

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

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);
        .....
}

  当layout结束以后getWidth()getHeight()才会返回正确的值。通过setFrame设置坐标。如果坐标改变过了,则重新进行定位。如果是View对象,那么onLayout是个空方法。因为定位是由ViewGroup确定的。

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

  • getWidth():View在設定好佈局後整個View的寬度。
  • getMeasuredWidth():對View上的內容進行測量後得到的View內容佔據的寬度

Draw过程

 1 public void draw(Canvas canvas) {
 2         ......
 3         /*
 4          * Draw traversal performs several drawing steps which must be executed
 5          * in the appropriate order:
 6          *
 7          *      1. Draw the background
 8          *      2. If necessary, save the canvas‘ layers to prepare for fading
 9          *      3. Draw view‘s content
10          *      4. Draw children
11          *      5. If necessary, draw the fading edges and restore layers
12          *      6. Draw decorations (scrollbars for instance)
13          */
14
15         // Step 1, draw the background, if needed
16         ......
17         if (!dirtyOpaque) {
18             drawBackground(canvas);
19         }
20
21         // skip step 2 & 5 if possible (common case)
22         ......
23
24         // Step 2, save the canvas‘ layers
25         ......
26             if (drawTop) {
27                 canvas.saveLayer(left, top, right, top + length, null, flags);
28             }
29         ......
30
31         // Step 3, draw the content
32         if (!dirtyOpaque) onDraw(canvas);
33
34         // Step 4, draw the children
35         dispatchDraw(canvas);
36
37         // Step 5, draw the fade effect and restore layers
38         ......
39         if (drawTop) {
40             matrix.setScale(1, fadeHeight * topFadeStrength);
41             matrix.postTranslate(left, top);
42             fade.setLocalMatrix(matrix);
43             p.setShader(fade);
44             canvas.drawRect(left, top, right, top + length, p);
45         }
46         ......
47
48         // Step 6, draw decorations (scrollbars)
49         onDrawScrollBars(canvas);
50         ......
51     }

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

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

时间: 2024-07-30 20:39:47

简单了解View是What?的相关文章

简单自定义View

简单的自定义View下,参考李刚疯狂Andriod package com.utils.MyDemo; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.view.View; public class

使用属性动画简单实现view飞入效果

比较简单的效果,可以用来菜单飞入之类,作为记录吧, package com.test.animation; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.LinearLayout; import com.nineoldandroids.animation.AnimatorSet;

IOS简单获取View截图图像(Quartz2D)

1. 先指定图像的大小 UIGraphicsBeginImageContext(view.frame.size); 2. 在指定的区域绘制图像 [view drawViewHierarchyInRect:view.bounds afterScreenUpdates:NO]; 3. 获取图像上下文 UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 4. 关闭图像上下文 UIGraphicsEndImageContext();

Android Scroller简单用法 --View滚动

转:http://ipjmc.iteye.com/blog/1615828 Android里Scroller类是为了实现View平滑滚动的一个Helper类.通常在自定义的View时使用,在View中定义一个私有成员mScroller = new Scroller(context).设置mScroller滚动的位置时,并不会导致View的滚动,通常是用mScroller记录/计算View滚动的位置,再重写View的computeScroll(),完成实际的滚动.   相关API介绍如下 mScr

简单模板view调用

$this->display();//模板名称与当前控制器.当前操作方法的名称不一致 $this->display();//调用当前控制器对应目录指定名称的模板 $this->display();调用其他控制器下的具体模板文件

iOS 简单提示view

+(void)showMessage:(NSString *)message{    UIWindow * window = [UIApplication sharedApplication].keyWindow;    UIView *showview =  [[UIView alloc]init];    showview.backgroundColor = [UIColor blackColor];    showview.frame = CGRectMake(1, 1, 1, 1);  

手把手带你画一个动态错误提示 Android自定义view

嗯..再差1篇就可以获得持之以恒徽章了,今天带大家画一个比较简单的view. 转载请注明出处:http://blog.csdn.net/wingichoy/article/details/50477108 废话不多说,看效果图: 首先 构造函数 测量... 这里就一笔带过了. public ErrorView(Context context) { this(context, null); } public ErrorView(Context context, AttributeSet attrs

【二】注入框架RoboGuice使用:(Your First View Injection)

上一篇我们简单的介绍了一下RoboGuice的使用([一]注入框架RoboGuice使用:(A brief example of what RoboGuice does)),今天我们我看下View的注解的使用方法. 为了在Activity中使用View的注解,必须实现以下三个步骤: ①:自己定义的Activity继承RoboActivity ②:设置内容视图 content View ③:使用 @InjectView来进行注解View控件 下面我们来详细看一下使用方法: (一)首先创建一个继承与

View的onSaveInstanceState和onRestoreInstanceState过程分析

为什么要介绍这2个方法呢?这是因为在我们的开发中最近遇到了一个很诡异的bug.大体是这样的:在我们的ViewPager中 有2页的root view都是ScrollView,我们在xml里面都用了android:id="@+id/scroll_view"这样的代码,即2个布局里面的 ScrollView用了同一个id.我们重载了ScrollView的onSaveInstanceState()用来save当前的scrollX和scrollY,在使用过程中 发现restore回来的时候其中