码迷,mamicode.com
首页 > 其他好文 > 详细

自定义视图

时间:2015-02-25 18:44:29      阅读:341      评论:0      收藏:0      [点我收藏+]

标签:

画布是无穷大的,即可以绘制任意多的内容或任意大的图形(只要内存足够大,然而实际应用中并不会绘制一个无穷大的界面,那么到底应该绘制一个多大的界面呢?对于不同类型的View,其绘制的大小有所不同,一般分为两种情况,一种是内容型视图,另一种是图形型视图 。所谓内容型视图一般是指,该视图将显示一段文本内容,比如TextView,其绘制指的就是显示一段文本内容,内容有多少,就应该绘制多少,所以视图的大小由内容的多少决定。所谓图形型视图一般是指,该视图显示的是一个图形,比如三角形、正方形、背景图等,此时该视图的大小往往会根据父视图为该View 开了一个多大的“ 窗口 ” 而动态调整。 “ 窗口 ” 是指,在父视图看 来 该 View 应该占多大的区域,即 布 局 (layout)大小。换句话说,该视图的大小是“ 被父窗体主宰” 的layoutxml文件中layout_height和 layout_width属性设置的宽和高不是指视图的大小,而是指父视图给该View设置的“窗

口” 大小,这就是为什么这个属性的名称是以“ layout—” 为前缀,而不是直接使用width和 height的原因。该属性值可以是一个“ 相对值”,比如 WRAPCONTENT、 MATCHPARENT等 ,也可以是一个具体值,比 如 lOOdipView类内部用两个变量measuredWidth和 measuredHeight保存 视 图 的 布 局 (layout)大小值。这两个更应

该被命名为layoutWidth和 layoutHeighView内部不是有mLeftmRigthmTopmBottom四个变量吗,它 与 measuredWidth又有什么关系呢?

实际上,这四个变量指的是该View在父视图中所占的区域, mRight-mLeft的大小一般就是measuredWidth的大小, mBottom-mTop就是measuredHeigth的大小。measure过程的本质是把视图布局时使用的“ 相对值” 转换为具体值的过程,即 把 WRAP_COTENT及 MATCH_PARENT转换为具体的值。如果 Framework中不使用相对值,那么也就完全不需要measure过程了。

View系统启动measure过程是从ViewRoot中调用 host.measure()开始

ViewGroup类 是 一 个abstract类 ,应用程序必须实现一个具体的ViewGroup实例。在该实例中,程序员应该调用 measureChildWithMarginO对 下 一 层 的 子 视 图 继 续 进 行 measure()操 作 ,但 这 不 是 必 需 的 , 因 为 measure()仅仅是把layout width和 layoutheight置的相对值转换为具体值将layout程中助父视图对视图进行布局操作。如 果 某 个ViewGroup实例对子视图的布局不依赖子视图的大小,那么就不需要对所包含的子视图进行measure操作。

View中 measure()数原型为void  measure(int widthMeasureSpec, int heightMeasureSpec)

参数 widthMeasureSpec 和 heightMeaureSpec 分别对应宽和高的 measureSpec., 该参数是父视图传递给子视图的,measureSpec是 一 个int型值,当父视图对子视图进行measure操作时,会调用子视图的measure()数,该参数的意思是父视图所提供的measure的 “ 规格”,因为父视图最终为子视图提供的“ 窗口”大小是由父视图和子视图共同决定的 该值由高32位 和 低16位组成,其中高 32位保存的是specMode,低 16位 为 specSizespecMode有三种,分别如下。MeasureSpec.EXACTLY AT_MOST UNSPECIFIED

MeasureSpec.EXACTLY: “ 确定的”,父元素确定子元素的确切大小,子元素将被限定在给定的边界而忽略自身的大小。

MeasureSpec .AT_MOST: “ 最多”,意思是说,子视图的大小最多是specSize中指定的值。在一般情况下, View 的设计者应该尽可能小地设置视图的大小,并且不能超过specSize,当然,也可以超过specSize

MeasureSpec .UNSPECIFIED未指定,父元素不对元素施加任何束缚,子元素可以得到任意的大小。

视图大小是由父视图和子视图共同决定的. LinearLayout中 的 layout—height扮演了两个角色,一个是 和 LinearLayout的父视图一起对LinearLayout本 身 进 行measure操作;另一个角色是作为 TextView的 measureSpec,并 和 TextView中 的 layout_heigtht —起 对 TextView进 行 measure操作.也就是说onMeasure中的两个参数就是父视图的layout_witdh等大小。

ViewGroup提供了三个类似的函数用于对子视图进行measure()操作。

? measureChildren():如其名称所示, children是 child 复数,该 函 数 内 部 使 用 for()环调用measureChild()对每一个子视图进行measure操作。
measureChild()为指定的子视图进行measure操作。
measureChildWithMarginsO该函数与 measureChild 的唯一区别在于, measure 时考虑把 margin及 padding也作为子视图大小的一部分。

static int MeasureSpec.getMode(int measureSpec) : 根据提供的测量值(格式),提取模式(上述三个模式之一

static int MeasureSpec.getSize(int measureSpec) : 根据提供的测量值(格式),提取大小值(这个大小也就是我们通常所说的大小

static int makeMeasureSpec(int size,int mode) : 根据提供的大小值和模式,创建一个测量值(格式)

LinearLayout 中的 onMeasure过程举例

View 中 的 measure()函数是不能被重载的,以保证View系 统 中 measure()的基本流程。ViewGroup的实例一般需要重载0nMeasure()数,并在该函数中调用 ViewGroup的 measureChild()相关函数对每一个子视图进行measure操作 。 LinearLayout中 的 onMeasure()数内部,首先判断该LinearLayout是水平的还是垂直的,并分别调用 measureHorizontal()和 measureVertical().

先计算所有子视图的高度由于ListView本身的高度仅share剩余部分,所 以 TextView将有足够的空间显示,其 height为 60,所以将首先获得60 高度,此时父窗口 LinearLayout将 剩 余40,再把这40 均匀分配给TextView和 ListView,因为它们的weight值 都 为 2。最终的结果是TextView将获得80高度,而 ListView获 得 20 高度

接下来需要判断父视图中是否还有剩余空间,并将剩余空间均匀分配给weight>0的子视图们.

layout过程的设计思路

在 layout()数中,首先调用 setFrame()数给当前视图设置参数中指定的位置,然后回调onLayoutO函数。 ViewGroup类中重载了 onLayout()数,并且将其函数类型设置成了一个abstract类型,因此,所 有 的 ViewGroup实例必须实现onLayout()View统希望程序员能够在onLayout()数中对该视图所包含的子视图进行layout操作,当该视图是ViewGroup时,程序员一般会在onLayout()函数中调用子视图 child的 layoutO方法,从而完成对子视图的位置分配。当然,如果子视图也是一个ViewGroup实例,就又会调用相应的onLayout()数。

View类 中 layout()数的:

1. 调用setFrame(1,t,r,b)将位置参数保存起来,这些参数将保存到View内 部 变 量(mLeft、 mTopmRight、 mBottom)o保存完变量前,会先对比这些参数是否和原来的相同,如果相同,则什么都不做,如果不同才进行重新赋值,并且在赋值前,会 给 mPrivateFlags中添加DRAWN标识,同时调用invalidate^告 诉 View系统原来占用的位置需要重绘。注意是先调用 invalidateO,而后赋值的,因为需要重绘的是老的区域。

2. 回 调 onLayout(), View中定义的onLayout()数默认什么都不做,View统提供onLayout()函数的目的是为了使系统包含有子视图的父视图能够在onLayoutO函数对子视图进行位置分配,正因为如此 ,如果是父视图,就必须重载onLayout(),也正因为如此, ViewGroup类 才 把 onLayout()载修改为了一个 abstract 类型。

LinearLayout 中 onLayout内部过程

onLayout()函 数 中 首 先 根 据 mOrientation变 量 判 断 是 水 平 还 是 垂 直 ,如 果 是 垂 直 则调用layoutVertical()开始进行 layout 操作。

1.获得子视图可用的宽度。读者可能觉得奇怪,这里是进行垂直方向上的布局,为什么却要可用的宽度,而不是可用的髙度呢?因为,就算是垂直方向,子视图本身也货以水平居中,而要居中就得知道的宽度是多少,从而计算出子视图左边沿的位置

2.根据父视图中的gravity属性,决定子视图的起始位置。在默认情况下, gravity是 TOP,应用程序可以设置为BOTTOM或 者 CENTER_VERTICAL

3. 此时已经确定了子视图的垂直方向上的位置,于是就开始for()环为每一个子视图分配位置

TextView  gravity  layout 的关系

子视图中 gravity的值并不是android:gravity值,而 是 android:layout__gravity?

在 LayoutInflater类中有一个inflater(resId,root)函数,在大多数情况下,大家调用该函数时,第二个参数root都为空。那么这个参数的意义又是什么?为什么大多数情况下都要为空呢?

该段代码包含了一个LinearLayout,其中又包含两个子视图,分别是一个TextView及 一 个ListView。从 X M L 文件中构造对应的View树 是 在 Layoutlnflater中 的 inflate()数中完成的,如果该段代码是作为 setContentView()参数,则会被当作Activity的界面,那么 LinearLayout中定义的layout_width及 height都是有意义的。但如果这段代码仅仅是应用程序调用inflate()数创建一个View对象,那 么 layoutwidth及 height就没有任何意义。

inflateO函数会首先判断参数root是否为空,如果为空,就 以 X M L 文件中的根视图构造一个临时的 View对象,此 处 是 LinearLayout对象,此 时 该 LinearLayout中声明的layout_width及 height都没有任何用处。接下来继续读取X M L 文 件 中 TextView读 取 TextView后,就 需 要 把 该 TextView添加到LinearLayout中。因为要添加,所以必须有一个LayoutParams对象,于是, inflate()数中就以TextView中定义的layout_width及 height为参数,回 调 TextView父视 图 的 generateLayoutParams()

<TextView>标签中包含的android:layoutwidth、 height并没有定义 LayoutParams对象,它只是提供了构造LayoutParams参数所需要的值,而真正构造LayoutParams对象的 是它的父视图,这 些 参 数 最 终 会 产 生 什 么 样 的LayoutParams取 决 于 该 TextView的父视图中generateLayoutParams(attrs)的具体实现。

这也就是上一节中所说的为什么要使用layout_gravity而 不 是 gravity的原因,因为在LinearLayout的 generateLayoutParams()数中,是把 android:layout_gravity 属性值赋值给了 LayoutParams.gravity,而不 是 android:gravity。事实上, android:gravity属 性 将 作 为 TextView内部的参数, TextView内部的onDraw()数中将根据android:gravity值决定把文字显示在什么地方

root参数的意义在于它将提供 generateLayoutParams()数,该函数产生的LayoutParams对象将作为 X M L 文件中包含的视图被添加 到 root时的布局属性。

如果你在一个XM L 文件中仅仅定义了一个TextView标签,然后调用 inflate(xml, null)从这个XML文 件 中 infalte出一个TextView对象,那 么 该 TextView标签中所包含的android:layout__width、 height将没有任何意义。当 你 想 把 infalte 出 来 的 TextView对 象 添 加 到 某 个ViewGroup时,还必须构造一个LayoutParams 对象

绘制draw )过程

如果该View是 一 个ViewGroup,则需要递归绘制 该 ViewGroup中所包含的所有子视图。

 技术分享

mView对象就是窗口的根视图,对 于 Activity而言,就是 PhoneWindow.DecorView 对象。

在一般情况下,View对象不应该重载dmw()数,因此,mView.draw()调用到了 View类 的 draw()函数。该函数的内部过程也就是View系统绘制过程的核心过程,该函数中会依次绘制上一节所讲的四种绘制兀素,其中绘制视图本身的具体实现就是回调onDraw()数,应用程序一^般 会 重 载 onDraw()函数以绘制所设计的View的真正界面内容。绘制完界面内容后,如果该视图内部还包含子视图,贝U调 用 dispatchDraw()数, ViewGroup载了该函数。因此,实际调用的是ViewGroup中 的 dispatchDraw()数,应用程序不应该再重载ViewGroup类 中 的 dispatchDmwO函数,因为该函数内部已经有了默认的实现,并且该实现代表了 View 系统的内.

View类中draw()函数内部流程

1. 绘制背景.首先根据滚动值对canvas的坐标进行调整,然后再恢复坐标.为什么需要先调用tmnslateO平 移 Canvas的坐标呢?因为对每一个视图而言, Canvas的 坐 标 原 点 (0 , 0 ) 对应的都是该视图的内部区域,如 图 13-35所示。

2. 显示视图的渐变框,daoying, setVerticalFadingEdgeO, setHorizontalFadingEdgeO

3. 绘制视图本身实 际 上 回 调 onDmw()数即可, View 设计者可以在onDraw()数中调用canvas的各种绘制函数进行绘制。

4. 调 用 dispatchDmwO绘制子视图。如果该视图内部不包含子视图,则不需要重载该函数,而对所有 的 ViewGroup实例而言,都必须重载该函数,否则它也就不是vfewGroup 了。

5. 回 调 onDrawScrollbars()绘制滚动条

动画的绘制

根据动画的参数对当前View做一定的图形变换,比如缩放、平移等,然后将变换后的图像绘制到屏幕上。绘制完后,再发起一个重绘的消息,就这样连续绘制,直到动画参数指示动画结束。

从效果的角度看, View系统中包含的动画可以分为三类,分别是窗口动画、视图动画、布局动画.

对 于 View系统而言,所能提供的动画更是有限, Android中 View系统仅支持基本的五种动画,分别为平移、缩放、旋转、扭曲及颜色阿尔法通道变化,这 称 之 为 “ 动画参数” 或 “ 动画类型”动画使用一个Animation类来表示,当 View要开始动画时,从 Animation类中获取动画的参数,并根据这些参数对View进行图形变换, 然后将变换后的图形绘制到屏幕上。

视图动画是在ViewGroup类 中 drawChild()数中完成的。 drawChild()数首先会判断该视图是否包 含 Animation对象,如果包含,则 利用 Animation中的动画参数对该视图进行变换,然后把变换后的图像绘制到canvas中。应用程序可以使用 res/anim/xxx.xml文件描述一个动画,对于视图动画而言,一般使用如下的xml标签描述一个动画。

<Set><scale/><alpha/>

<l a y o u t A n i m a t i o n>

自定义视图

标签:

原文地址:http://blog.csdn.net/andywuchuanlong/article/details/43938555

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!