【Android应用开发技术:用户界面】视图组件View

原创 2015年07月24日 16:58:01

作者:郭孝星
微博:郭孝星的新浪微博
邮箱:allenwells@163.com
博客:http://blog.csdn.net/allenwells
Github:https://github.com/AllenWells

【Android应用开发技术:用户界面】章节列表

View作为所有UI组件的子类,它代表了一个空白的矩形区域。ViewGroup继承了View,因此ViewGroup也可以被当做View使用,它同时也作为一个容器组件用来盛转其他组件,ViewGroup同样还可以再次包含ViewGroup。

Android图形用户界面的组件层次如下图所示:

这里写图片描述

一 View的绘制原理

1.1 View树的绘制流程

当Activity接收到焦点时,它会被请求绘制布局,该请求由framework处理。绘制从根节点开始,对布局树进行measure和draw。整个View树的绘制流程在ViewRoot类的performTraversals()函数中展开。该函数做的工作如下所示:

  1. 是否需要重新计算视图大小(measure)
  2. 是否需要重新安置视图的位置(layout)
  3. 是否需要重绘(draw)

View树的绘制流程如下图所示:

这里写图片描述

1.2 View绘制流程函数调用链

View绘制流程函数调用链如下图所示:

这里写图片描述

当用户主动调用request,只会触发measure和layout过程,而不会触发draw过程。

1.3 measure和layout过程

从整体上看measure和layout两个过程的执行:
树的遍历是有序的,由父视图到子视图,每一个ViewGroup负责绘制它所有的子视图,最底层的View负责绘制自身。

如下图所示:

这里写图片描述

1.3.1 measure过程

measure过程由measure(int, int)方法发起,从上到下有序测量View,在measure过程的最后,每个视图都存储了自己的尺寸大小和测量规格。measure过程会为一个View及所有子节点的mMeasuredWidth和mMeasuredHeight变量赋值,该值可以通过getMeasuredWidth()和getMeasuredHeight()方法获得,而这两个值必须在子视图的约束范围之内,才可以保证所有的父视图都接收所有子视图的测量。
如果子视图对于measure得到的大小不满意的时候,父视图会介入并设置测量进行第二次measure。

measure过程中传递尺寸的两个类

  1. ViewGroup.LayoutParams类(View自身的布局参数与)
    MATCH_PARENT:子视图和父视图一样大小(不包括padding值)
    WRAP_CONTENT:视图正好能包裹其内容大小(包括padding值)
  2. MeasureSpecs类(父视图对子视图的测量要求)
    UNSPECIFIED:父视图不对子视图进行任何约束,它可以达到它锁期望的任何尺寸。比如ScrollView和ListView。
    EXACTLY:父视图为子视图指定一个确切尺寸,而且无s论子视图期望多大,它都必须在该指定大小的边界内,对应的属性为 match_parent 或具体指,比如100dp,父控件可以通过MeasureSpec.getSize(measureSpec)直接得到子控件的尺寸。
    AT_MOST:父视图为子视图指定一个最大尺寸,子视图必须确保它自己的所有子视图可以适应在该尺寸范围内,对应的属性为 wrap_content,这种模式下,父控件无法确定子 View 的尺寸,只能由子控件自己根据需求去计算自己的尺寸,这种模式就是我们自定义视图需要实现测量逻辑的情况。
    getLayoutParams()方法获得是父视图类型的LayoutParams

measure核心方法

measure(int widthMeasureSpec, int heightMeasureSpec);

该方法定义在View.java类中,为final类型,不可被复写,但 measure 调用链最终会回调 View/ViewGroup 对象的 onMeasure()方法,因此自定义视图时,只需要复写 onMeasure() 方法即可。

onMeasure(int widthMeasureSpec, int heightMeasureSpec)

该方法就是我们自定义视图中实现测量逻辑的方法,该方法的参数是父视图对子视图的 width 和 height 的测量要求。在我们自身的自定义视图中,要做的就是根据该 widthMeasureSpec 和 heightMeasureSpec 计算视图的 width 和 height,不同的模式处理方式不同。

setMeasuredDimension()

测量阶段终极方法,在 onMeasure(int widthMeasureSpec, int heightMeasureSpec) 方法中调用,将计算得到的尺寸,传递给该方法,测量阶段即结束。该方法也是必须要调用的方法,否则会报异常。在我们在自定义视图的时候,不需要关心系统复杂的 Measure过程的,只需调用setMeasuredDimension()设置根据 MeasureSpec 计算得到的尺寸即可。

1.3.2 layout过程

layout过程有layout(int, int)方法发起,也是自上而下进行遍历,在该过程中,每个父视图会根据measure过程得到的尺寸摆放自己的子视图。
子视图的具体位置都是相对于父视图而言的View的onLayout()方法为空实现,ViewGroup的onLayout()方法为抽象方法。因此如果自定义View要继承ViewGroup时,需要实现onLayout()函数。
在layout过程中,子视图会调用getMeasuredWidth()和getMeasuredHeight()方法获取到 measure 过程得到的 mMeasuredWidth 和 mMeasuredHeight,作为自己的 width 和 height。然后调用每一个子视图的layout(l, t, r, b)函数,来确定每个子视图在父视图中的位置。

1.3.3 draw过程

1.3.3.1 draw绘制流程

draw绘制流程如下图所示:

这里写图片描述

1.3.3.2 draw过程核心方法

View.draw(Canvas canvas)

由于 ViewGroup 并没有复写此方法,因此,所有的视图最终都是调用 View 的 draw 方法进行绘制的。在自定义的视图中,也不应该复写该方法,而是复写 onDraw(Canvas) 方法进行绘制,如果自定义的视图确实要复写该方法,那么请先调用 super.draw(canvas)完成系统的绘制,然后再进行自定义的绘制。

View.onDraw()

View的onDraw(Canvas)默认是空实现,自定义绘制过程需要复写的方法,绘制自身的内容。

dispatchDraw()

发起对子视图的绘制。View中默认是空实现,ViewGroup 复写了dispatchDraw()来对其子视图进行绘制。该方法我们不用去管,自定义的 ViewGroup 不应该对dispatchDraw()进行复写。

drawChild(canvas, this, drawingTime)

直接调用了 View 的child.draw(canvas, this,drawingTime)方法,文档中也说明了,除了被ViewGroup.drawChild()方法外,你不应该在其它任何地方去复写或调用该方法,它属于 ViewGroup。而View.draw(Canvas)方法是我们自定义控件中可以复写的方法,具体可以参考上述对view.draw(Canvas)的说明。从参数中可以看到,child.draw(canvas, this, drawingTime) 肯定是处理了和父视图相关的逻辑,但 View 的最终绘制,还是 View.draw(Canvas)方法。

invalidate()

请求重绘 View 树,即 draw 过程,假如视图发生大小没有变化就不会调用layout()过程,并且只绘制那些调用了invalidate()方法的 View。

requestLayout()

当布局变化的时候,比如方向变化,尺寸的变化,会调用该方法,在自定义的视图中,如果某些情况下希望重新测量尺寸大小,应该手动去调用该方法,它会触发measure()和layout()过程,但不会进行 draw。

二 View的属性和方法

XML属性 相关方法 说明
android:alpha setAlpha(float) 设置该组件的透明度
android:background setBackgroundResource(int) 设置该组件的背景颜色
android:clickable setClickable(boolean) 设置该组件是否可以激发单击事件
android:contentDescription setContentDescription(CharSequence) 设置该组件的主要描述信息
android:drawingCacheQuality setDrawingCacheQuality(iint) 设置该组件所使用的绘制缓存的质量
android:fadeScrollbars setScrollbarFadingEnabled(boolean) 当不使用该组件的滚动条时,是否淡出显示滚动条
android:fadingEdge setVerticalFadingEdgeEnabled(boolean) 设置滚动该组件时组件边界是否使用淡出效果
android:fadingEdgeLength getVerticalFadingEdgeLength() 设置淡出边界的长度
android:focusable setFocusable(boolean) 设置组件是否可以得到焦点
android:focusableInTouchMode setFocusableInTouchMode(boolean) 设置该组件在触摸模式下是否可以得到焦点
android:id setId(int) 设置该组件的唯一标识。Java代码中可以通过findViewById来获取它
android:isScrollContainer setScrollContainer(boolean) 设置该组件是否作为可滚动容器使用
android:keepScreenOn setKeepScreenOn(boolean) 设置该组件是否会强制手机屏幕一直打开
android:longClickable setLongClickable(boolean) 设置该组件是否可以响应长单击事件
android:minHeight setMinimumHeight(int) 设置该组件的最小高度
android:minWidth setMinimumWidtht(int) 设置该组件的最小宽度
android:nextFocusDown SetNextFocusDownId(int) 设置焦点在该组件上,且按向下键时获得焦点的组件ID
android:nextFocusLeft setNextFocusLeftId(int) 设置焦点在该组件上,且按向左键时获得焦点的组件ID
android:nextFocusRight setNextFocusRightId(int) 设置焦点在该组件上,且按向右键时获得焦点的组件ID
android:nextFocusUp setNextFocusUpId(int) 设置焦点在该组件上,且按向上键时获得焦点的组件ID
android:onClick 为该组件的单击事件绑定监听器
android:padding setPadding(int,int,int,int) 在组件的四边设置填充区域
android:paddingLeft setPadding(int,int,int,int) 在组件的左边设置填充区域
android:paddingTop setPadding(int,int,int,int) 在组件的上边设置填充区域
android:paddingRight setPadding(int,int,int,int) 在组件的右边设置填充区域
android:paddingBottom setPadding(int,int,int,int) 在组件的下边设置填充区域
android:rotation setRotation(float) 设置该组件旋转的角度
android:rotationX setRotationX(float) 设置该组件绕X轴旋转的角度
android:rotationY setRotationY(float) 设置该组件绕Y轴旋转的角度
android:saveEnabled setSaveEnabled(boolean) 如果设置为false,那当该组件被冻结时不会保存它的状态
android:scaleX setScaleX(float) 设置该组件在水平方向的缩放比
android:scaleY setScaleY(float) 设置该组件在垂直方向的缩放比
android:scrollX 该组件初始化后的水平滚动偏移
android:scrollY 该组件初始化后的垂直滚动偏移
android:scrollbarAlwaysDrawHorizontalTrack 设置该组件是否总是显示水平滚动条的轨迹
android:scrollbarAlwaysDrawVerticalTrack 设置该组件是否总是显示垂直滚动条的轨迹
android:scrollbarDefaultDelayBeforeFade setScrollbarDefaultDelayBeforeFade(int) 设置滚动条在淡出隐藏之前延迟多少毫秒
android:scrollbarFadeDuration setScrollbarFadeDuration(int) 设置滚动条淡出隐藏过程需要多少秒
android:scrollbarSize setScrollbarSize(int) 设置垂直滚动条的宽度和水平滚动条的高度
android:scrollbarStyle setScrollbarStyle(int) 设置滚动条的风格和位置。该属性支持如下属性:insideOverlay、insideInset、OutsideOverlay、OutsideInset
android:scrollbarThumbHorizontal 设置该组件的水平滚动条的滑块对应的Drawable对象
android:scrollbarThumbVertical 设置该组件的垂直滚动条的滑块对应的Drawable对象
android:scrollbarTrackHorizontal 设置该组件的水平滚动条的轨道对应的Drawable对象
android:scrollbarTrackVertical 设置该组件的垂直滚动条的轨道对应的Drawable对象
android:scrollbars 定义该组件滚动时显示几个滚动条,该属性支持如下属性值。 none:不显示滚动条 horizont:显示水平滚动条 vertical:显示垂直滚动条
android:soundEffectsEnabled setSoundEffectsEnabled(boolean) 设置该组件被单击时是否使用音效
android:tag 为该组件设置一个字符串类型的tag值。接下来可通过View的getTag()获取该字符串,或通过findViewWithTag()查找该组件
android:transformPivotX setPivotX(float) 设置该组件旋转时旋转中心的X坐标
android:transformPivotY setPivotY(float) 设置该组件旋转时旋转中心的Y坐标
android:translationX setTranslationX(float) 设置该组件在X方向上位移
android:translationY setTranslationY(float) 设置该组件在Y方向上位移
android:visibility setVisibility(int) 设置该组件是否可见

View的属性和方法里包含了很多类似margin和padding等这样易混淆的概念,具体的分析请参见我的另一篇文章Android UI的设计技巧

【Android应用开发技术:用户界面】用户界面基本原理

作者:郭孝星 微博:郭孝星的新浪微博 邮箱:allenwells@163.com 博客:http://blog.csdn.net/allenwells Github:https://githu...
  • AllenWells
  • AllenWells
  • 2015年07月31日 17:07
  • 1161

【Android应用开发技术:用户界面】ViewAnimator

作者:郭嘉 邮箱:allenwells@163.com 博客:http://blog.csdn.net/allenwells github:https://github.com/AllenWel...
  • AllenWells
  • AllenWells
  • 2015年05月07日 21:05
  • 486

【Android应用开发技术:用户界面】ImageView

作者:郭嘉 邮箱:allenwells@163.com 博客:http://blog.csdn.net/allenwells Github:https://github.com/AllenWel...
  • AllenWells
  • AllenWells
  • 2015年05月07日 21:00
  • 415

【Android应用开发技术:用户界面】TextView

作者:郭孝星 微博:郭孝星的新浪微博 邮箱:allenwells@163.com 博客:http://blog.csdn.net/allenwells Github:https://githu...
  • AllenWells
  • AllenWells
  • 2015年05月07日 21:02
  • 670

【Android应用开发技术:用户界面】布局管理器

作者:郭孝星 微博:郭孝星的新浪微博 邮箱:allenwells@163.com 博客:http://blog.csdn.net/allenwells Github:https://githu...
  • AllenWells
  • AllenWells
  • 2015年08月17日 17:19
  • 1190

【Android应用开发技术:用户界面】SurfaceHolder

作者:郭孝星 微博:郭孝星的新浪微博 邮箱:allenwells@163.com 博客:http://blog.csdn.net/allenwells Github:https://githu...
  • AllenWells
  • AllenWells
  • 2015年07月31日 16:52
  • 723

【Android应用开发技术:用户界面】ProgressBar

作者:郭嘉 邮箱:allenwells@163.com 博客:http://blog.csdn.net/allenwells github:https://github.com/AllenWel...
  • AllenWells
  • AllenWells
  • 2015年05月07日 21:02
  • 415

【Android应用开发技术:用户界面】SurfaceView

作者:郭孝星 微博:郭孝星的新浪微博 邮箱:allenwells@163.com 博客:http://blog.csdn.net/allenwells Github:https://githu...
  • AllenWells
  • AllenWells
  • 2015年07月31日 16:51
  • 690

【Android应用开发技术:用户界面】AdapterView

作者:郭嘉 邮箱:allenwells@163.com 博客:http://blog.csdn.net/allenwells github:https://github.com/AllenWel...
  • AllenWells
  • AllenWells
  • 2015年05月07日 21:04
  • 479

【Android应用开发技术:用户界面】Menu

作者:郭嘉 邮箱:allenwells@163.com 博客:http://blog.csdn.net/allenwells github:https://github.com/AllenWel...
  • AllenWells
  • AllenWells
  • 2015年05月07日 21:03
  • 465
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:【Android应用开发技术:用户界面】视图组件View
举报原因:
原因补充:

(最多只允许输入30个字)