通过翻译开发参考学习View类

1.前言


移动端是展示后台数据、处理用户交互的平台,界面对它而言自然重要。界面按功能大体分为两部分:视图和布局。布局用来排列视图,而视图则用来展示数据,响应交互(包括动画等)。由此可见,视图在整个开发中占有相当重要的地位。学习视图,对开发界面有很大的帮助,查参考文档是学习视图的有效手段。
  下面我就根据自己的理解翻译 View 类的参考文档的主要部分。

2.使用视图


窗口中所有的视图被组织成树状的结构,可以通过代码添加视图,也可以通过一个或多个XML布局文件指定一个视图树。不同的视图有不同的功能,比如:充当控件,显示文本、图像或者其它内容。
  一旦创建了视图树,通常有以下几种常见操作:

  • 设置属性:不同的视图有不同的属性和设置的方法,在创建时就知道的属性可以在XML布局文件中设置。
  • 设置焦点:系统会处理焦点的移动来响应用户的交互,调用 requestFocus() 方法强制给视图焦点。
  • 设置监听器:当视图发生有趣的事情时,将会通知设置的监听器。比如,所有的视图都允许调用
    setOnFocusChangeListener(android.view.View.OnFocusChangeListener) 方法设置监听器来获取自己焦点的得失情况。其它视图提供了更特殊的监听器,比如,对按钮点击事件的监听。
  • 设置可见性:调用 setVisibility(int) 方法隐藏或者显示视图。

注意:通常测量、布局、绘制视图是由安卓框架负责,仅当自定义ViewGroup时,才需调用这些方法。

3.自定义视图


从重写那些所有视图都会被系统调用的基本方法开始(如下表)。也不必重写所有方法,实际上,仅仅 onDraw(android.graphics.Canvas) 方法就够了。

类别 方法 描述
创建 Constructors 有两种创建视图的方式,一种在代码中调用构造函数,另一种通过解析和应用布局文件中定义的属性实现。
- onFinishInflate() 当视图及其所有子视图已经通过XML创建完成时调用。
Layout onMeasure(int, int) 当视图及其所有子视图决定所需尺寸时调用。
- onLayout(boolean, int, int, int, int) 当需要给所有子视图分配尺寸和位置时调用。
- onSizeChanged(int, int, int, int) 当视图的尺寸已经改变时调用。
Drawing onDraw(android.graphics.Canvas) 当视图需要渲染内部时调用。
Event processing onKeyDown(int, KeyEvent) 当一个新的按键事件发生时调用。
- onKeyUp(int, KeyEvent) 当一个按键松开事件发生时调用。
- onTrackballEvent(MotionEvent) 当一个轨迹球移动事件发生时调用。
- onTouchEvent(MotionEvent) 当一个触屏相关事件发生时调用。
Focus onFocusChanged(boolean, int, android.graphics.Rect) 当一个视图获得或丢失焦点时调用。
- onWindowFocusChanged(boolean) 当窗口内的视图获得或丢失焦点时调用。
Attaching onAttachedToWindow() 当视图关联一个窗口时调用。
- onDetachedFromWindow() 当视图脱离一个窗口时调用。
- onWindowVisibilityChanged(int) 当窗口内的视图可见性已经变化时调用。

4.IDs


视图可能会有整数id与之相关联。这些id在XML布局文件中被指定,用于在视图树中找到特定的视图。常见的模式是:

  • 先在布局文件中定义一个按钮并指定一个唯一的id。
<Button
     android:id="@+id/my_button"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:text="@string/my_button_text"/>
  • 再在一个Activity的 onCreate()方法中查找。
Button myButton = (Button) findViewById(R.id.my_button);

视图id不需要在整个树中保持唯一,但至少在你搜索的那部分中保证唯一是个好的习惯。

5.位置


视图的几何形状是矩形,在窗口上的位置由左顶点的坐标决定,尺寸由宽高决定,单位都是像素。
  通过调用 getLeft() 和 getTop() 方法可以获取视图的位置。前者返回视图所在矩形的X坐标(左边),后者返回Y坐标(顶部),都是相对父视图而言。例如:当 getLeft() 方法返回20,表示视图在直接父视图左边缘的右侧20像素处。
  此外,为了免去计算,还提供了 getRight() 和 getBottom() 方法获取代表视图的矩形的右底点的坐标。例如,调用 getRight() 方法相当于 getLeft() + getWidth()(详见 Size 中对宽度的概念)。

6.尺寸和内外边距


视图的大小用宽度和高度表示,实际上可以用两对宽高表示。
  第一对被称为测量的宽和高。这些尺寸定义了一个视图希望在父视图内的大小,通过调用 getMeasuredWidth() 和 getMeasuredHeight() 方法获取。
  第二对简单地称为宽和高,或者绘制的宽和高。这些尺寸定义了布局后绘制时,视图在屏幕上的实际大小,可以不同于测量的宽高,通过调用 getWidth() 和 getHeight() 方法获取。
  在测量尺寸的过程中,视图应该考虑到自己的内边距。它以像素为单位填充在视图四周,使内容发生一定量的偏移。例如,在左边填充2,意味着将内容从左边缘向右移动2像素。可以通过 setPadding(int, int, int, int) 或者 setPaddingRelative(int, int, int, int) 设置,通过 getPaddingLeft(),getPaddingTop(),getPaddingRight(),getPaddingBottom(),getPaddingStart(),getPaddingEnd() 获取。
  视图默认支持内边距,但若没有ViewGroup的支持,是无法使用外边距的。详见 ViewGroupViewGroup.MarginLayoutParams

7.布局


此过程分为测量、布置两步。先从上到下遍历整个视图树,调用 measure(int, int) 方法将每个视图的尺寸规范向下传递,并存储自己的尺寸大小。然后再从上到下依次调用 layout(int, int, int, int) 方法,每个父视图根据测量的结果负责摆放所有的子视图。
  当一个视图 measure() 方法返回了,它连同所有子视图可以通过 getMeasuredWidth() 和 getMeasuredHeight() 方法获取结果。值的大小必须遵守父视图的约束,保证所有父视图接受它们子视图的测量结果。父视图可能不止一次调用子视图的 measure() 方法,比如,第一次因为不知道每个子视图想要的尺寸;第二次传入有效数字,因为子视图不符合约束的部分太大或太小。
  有两个类影响尺寸。LayoutParams类仅仅描述视图想要的宽高大小,每个尺寸可以设定以下三种之一:

  • 一个确切的数字。
  • MATCH_PARENT,希望和父视图一样大,不包括父内边距。
  • WRAP_CONTENT,仅仅足够去包含内容,包括自己内边距。

ViewGroup的不同子类有不同的LayoutParams,比如,AbsoluteLayout 自己的LayoutParams添加了X和Y值。
  View.MeasureSpec 类用来告诉父视图希望怎样去测量和摆放,同时通过 View.MeasureSpec.makeMeasureSpec(int size, int mode) 生成需求传到子视图,可以设定为以下三种之一:

  • EXACTLY,父视图规定子视图一个确切的尺寸,子视图及内部视图必需满足。
  • AT_MOST,父视图让子视图尽可能的大直到某个值,子视图及内部视图必需满足。
  • UNSPECIFIED,父视图满足子视图想要的尺寸。比如,调用子视图的 measure() 方法并设置宽为 240 EXACTLY、高为UNSPECIFIED,意味着子视图宽度240像素,高度不管。

当视图觉得当前的尺寸不再适合时,自己调用 requestLayout() 初始化布局。

8.绘制


通过遍历视图树,记录需要更新的视图的操作命令,然后将这些命令反映到屏幕上,替换这些区域。
  视图树的记录和绘制顺序大体是,父视图先于子视图,兄弟视图依次进行。如果给视图设置背景图片,视图会在回掉自己的 onDraw() 方法之前绘制它。子视图的绘制顺序可以通过对ViewGroup调用 setChildrenDrawingOrderEnabled(boolean) 、重写 getChildDrawingOrder(int, int) 和设置 setZ(float) 来改变。
  调用 invalidate() 强制视图绘制。

9.事件处理与线程


最基本的流程如下:

  1. 事件传入并分发给合适的视图。视图处理事件并通知相应的监听器。
  2. 在处理事件的过程中,视图的尺寸可能需要更改,将调用 requestLayout()。
  3. 在处理事件的过程中,视图的显示可能需要更改,将调用 invalidate()。
  4. 不管上面的两个方法哪个被调用了,系统会适当地对视图树进行测量、布局和绘制。

注意:整个视图树是单线程的。在任何视图上调用任何方法时,必须始终处于UI线程上。如果正在对其它线程操作,并希望从该线程更新视图状态,应该使用Handler。

10.焦点处理


系统通过处理日常焦点移动来响应用户交互,包括删除或隐藏或新增有效视图等改变焦点行为。视图通过 isFocusable() 方法表明是否能够接受焦点,setFocusable(boolean) 方法改变能否接受焦点的状态,isFocusableInTouchMode() 方法表明是否能够接受焦点(触摸模式下)和
setFocusableInTouchMode(boolean) 方法改变能否接受焦点的状态(触摸模式下)。
  焦点移动是基于找到给定方向上最相邻的视图的算法。某些情况下,默认算法不能满足开发者的预期行为,需要在布局文件中用XML属性明确指出,比如:nextFocusDown、nextFocusLeft、nextFocusRight、nextFocusUp。
  调用 requestFocus() 方法可以让任意指定的视图获取焦点。

11.触摸模式


当用户通过方向键浏览用户界面时,给按钮之类的可操作视图提供焦点是必要的,那样用户能够知道哪些是可以交互的。如果具有触摸功能且用户开始通过触摸与界面交互,就不再需要给指定视图焦点或者保持高亮。这种交互模式命名为“触摸模式”。
  对于具有触摸功能的设备,一旦用户触摸屏幕,设备将会进入触摸模式,只有那些调用 isFocusableInTouchMode() 返回true的视图可以获取焦点,比如:文本编辑控件。按钮等可触摸的视图,被触摸时不再获取焦点,只会触发点击监听器。
  用户只要点击任何一个方向键,设备将会退出触摸模式,找到一个视图给予焦点,那样用户无需触摸屏幕便与用户界面恢复交互。
  触摸模式的状态由Activity保持,调用 isInTouchMode() 方法可以查看设备当前是否处于触摸模式。

12.滚动


系统为视图滚动其内容提供了基本的支持,包括监听X和Y轴滚动偏移量和绘制滚动条。查看 scrollBy(int, int),scrollTo(int, int) 和 awakenScrollBars() 获取更多信息。

13.标签


与IDs不同,标签不是用来识别视图的,本质上是提供与视图相关的额外信息。通常放在视图内部,用来方便地存储与视图相关的信息,而不是作为一个独立的结构。
  在XML布局中,可以将字符串设置给标签。一个标签时,使用 android:tag 属性;多个标签时,使用<tag>标签作为子元素。

<View ...
      android:tag="@string/mytag_value" />
<View ...>
    <tag android:id="@+id/mytag"
         android:value="@string/mytag_value" />
</View>

在代码中,可以通过调用 setTag(Object) 或者 setTag(int, Object) 方法设置任意对象给标签。

14.主题


默认情况下,视图通过构造函数中传入的Context对象获取主题来创建自己。但是,可以在XML布局中设置 android:theme 属性或者通过代码向构造函数中传入 ContextThemeWrapper 对象来指定不同的主题。
  当在XML中使用 android:theme 属性,被指定的主题优先于Context主题(见 LayoutInflater),而且作用于视图自己包括子元素。
  下面的例子中,所有的视图使用 Material dark 颜色样式创建。但是,由于被使用的那个覆盖主题只定义了一个属性子集,Context主题(例如Activity的主题)属性 android:colorAccent 的值保留了下来。

<LinearLayout
        ...
        android:theme="@android:theme/ThemeOverlay.Material.Dark">
    <View ...>
</LinearLayout>

15.属性


视图有几个公开的变换相关的属性,比如:ALPHA 、TRANSLATION_X 和 TRANSLATION_Y,可以通过 Property<View, Float> 赋值或者同名的 setter/getter 方法设置(就像 setAlpha(float) 与 ALPHA)。这些属性能够设置视图渲染相关的持续性状态,通常与Animator结合起来展示动画。

16.动画


从安卓3.0开始,推荐使用 android.animation 包的API进行视图动画。这些类改变的是视图对象的实际属性,比如alpha 和 translationX。3.0之前,类似的效果只是改变显示层面上的绘制。ViewPropertyAnimator类使操作视图属性进行动画变得简单高效。
  另外,若使用3.0之前的动画进行视图的渲染,先调用 setAnimation(Animation) 或者 startAnimation(Animation) 方法将Animation对象与视图关联。动画会随着时间改变视图的缩放、旋转、位移和透明度。如果动画关联的视图有子视图,那么动画将影响节点往下的整个子树。当动画开始,系统会负责重绘视图适当的部分直到动画完成。

17.安全


应用必须能够验证正在执行的行为合理且有用户的允许,比如:授予请求购买或者点击广告的权限。不幸的是,恶意程序可能会偷偷隐藏带有意图的控件来欺骗用户执行这些行为。作为补救措施,系统提供了触摸过滤机制,提高访问敏感功能的保护。
  通过调用 setFilterTouchesWhenObscured(boolean) 方法或者设置XML布局中 android:filterTouchesWhenObscured 属性值为true来开启触摸过滤机制。开启后,当视图窗口被另外的可见窗口遮挡时,忽略接收到的触摸事件。因此,当吐司、对话框或者其它窗口出现在视图窗口之上时,那个视图不会接收到触摸事件。
  重写 onFilterTouchEventForSecurity(MotionEvent) 方法实现自己的安全规则可以更精细地控制安全,也可以参考 FLAG_WINDOW_IS_OBSCURED

18.总结


到这里,我们浏览了安卓界面机制的概况。ViewGroup对应的布局用法,请参考这篇文章,有时间我也会翻译的。若想了解动画,可以看看这个文集,我也会接着更新。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容