Android学习笔记View的工作原理

  自定义View,也可以称为自定义控件,通过自定义View可以使得控件实现各种定制的效果。

  实现自定义View,需要掌握View的底层工作原理,比如View的测量过程、布局流程以及绘制流程,除此之外,还需要掌握View常见的回调方法。而对于那些具有滑动效果的自定义View,我们还需要处理View的滑动,如果遇到滑动冲突则需要处理相应的滑动冲突。

  下面是View的常见回调方法:

  • 构造方法
  • onAttach
  • onVisibilityChanged
  • onDetach
  • onFinishInflate
  • onSizeChanged
  • onMeasure
  • onLayout
  • onTouchEvent

  自定义控件的实现手段可简要分为四种类:

  • 继承View重写onDraw方法,这种方法主要是用于实现一些不规则的效果,采用这种方式需要自己支持wrap_content,并且处理padding。
  • 继承ViewGroup派生特殊的Layout,这种方法主要是用于实现自定义的布局,当某种效果看起来像是几个View组合在一起时,可以采用这种方法来实现。采用这种方法是需要合理处理ViewGroup的测量和布局这两个过程,并同时处理子元素的测量和布局过程。
  • 继承特定的View,用于拓展已有的View的功能。
  • 继承特定的ViewGroup(如LinearLayout、RelativeLayout),其适用情形和方法2 类似。

  在自定义View中需要的注意点:

  应当遵守Android标准控件的规范(如命名、可配置、事件处理、状态保存及恢复等)

  • 命名表意明确
  • 控件属性可以在XML中配置
  • 让View支持wrap_content和padding(下文会具体讲到)
  • 在View中尽量不使用Handler,因为View中自带post系列的方法。
  • 自定义View的内存泄漏问题(如果有线程或者动画,需要及时停止)
  • View的滑动冲突(在View带有滑动嵌套的情形,需要处理好滑动冲突)
  • 具有一定的交互性,如按下、点击等
  • 自定义View内部实现状态保存和恢复的机制
  • 兼容性

  下面主要从View的基本知识、View的绘制过程讲一下View的工作原理。

1.从Activity中的View结构讲起

  每个Activity都含有一个Window对象,而这个Window对象一般都是PhoneWindow。PhoneWindow将以DecorView设置为整个应用窗口的根View。DecorView作为窗口界面的顶层视图,封装了一些窗口操作的通用方法。可以这么说,DecorView将要显示的具体内容呈现在了PhoneWindow中,这里面的所有的View的监听事件都是通过WindowManagerService来接收的,并通过Activity对象来回调相应的onClickListenr。

  在显示上,将屏幕分成两部分,一个是TitleView,另一个是ContentView,这个ContentView想必大家都很熟悉,它是一个ID为content的FrameLayout,activity_main就是设置在这样一个FrameLayout中。

如下图1 和图2 所示:

图1

图2

View的绘制流程:

图3

  如上图所示,performTraversals会依次调用performMeasure、performLayout、performDraw三个方法,这三个方法分别完成顶级View的measure、layout、draw这三大流程。

  其中在performMeasure中又会调用measure,接着在measure中调用onMeasure方法,在onMeasure中会对所有的子元素进行measure过程,这个时候measure流程就从父容器传递到子元素中了,即完成依次measure操作,接着子元素进行同样的measure过程,如此方法直至完成整个View树的遍历。同理,performLayout和performDraw的传递流程和performmeasure是类似的(performDraw的传递过程是在draw方法中的dispatchDraw完成的,并无实质区别)。

measure过程决定了View的宽高,measure完成后,可以通过getMeasuredWidth和getMeasuredHeight方法来获取到View测量后的宽高,在几乎所有的情况下它都等同于View的最终高度,但特殊情况除外。Layout过程确定了View的四个顶点的坐标和实际的View的宽高,完成以后,可以通过getTop、getBottom、getLeft、getRight来得到四个顶点的位置,并可以通过getWidth和getHeight来得到View的最终宽高。Draw过程决定了View的显示,只有draw方法完成后,View的内容才会显示在屏幕上。

2.如何完成测量过程呢?

  Android系统提供了一个MeasureSpec类,通过它可以帮助我们测量View。MeasureSpec是一个32位的int值,其中高2位为测量的模式,低30位为测量的大小。

  EXACTLY:精确值模式, 当我们将空间的layout_width或者layout_height属性指定为具体值时,或者指定为match_parent属性时,系统使用的是EXACTLY。

  AT_MOST:最大值模式,当空间的layout_width属性或者layout_height属性为wrap_content时,控件大小一般随着空间的子控件或者内容的变化而变化,此时,控件的尺寸只要不超过父控件允许的最大尺寸即可。

  UNSPECIFIED:不指定其测量大小,通常情况下在绘制自定义View时才会使用它。

在view的测量过程中,系统会将LayoutParams在父容器的约束下转换为对应的MeasureSpec,然后根据这个MeasureSpec来确定View测量后的宽高。MeasureSpec由父容器和LayoutParams共同决定。

  对于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams决定

  对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams决定

当View的LayoutParams采用精确值时,不管父容器的MeasureSpec是什么,View的MeasureSpec模式都是EXACTLY,并且大小遵循LayoutParms的大小。

当View的宽高是match_parent模式,view的MeasureSpec模式遵循父容器的MeasureSpec模式。

当View的宽高是wrap_content,不管父容器的模式是EXACTLY还是AT_MOST,View的模式都是AT_MOST并且大小不超过父容器的剩余空间。

  下面分别简要讲一下View的measure过程和ViewGroup的measure过程。

1)View的measure过程:

  参考源码:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {         

     setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),    

     getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}

图4

  由上述源码可知,在调用onMeasure方法时会调用setMeasuredDimension方法,在这个方法中会传入其宽高。由此可知,在自定义View中,需要重新定义view的宽和高。

View类默认的onMeasure方法只支持EXACTLY模式,如果在自定义控件的时候不重写onMeasure方法,就只能使用EXACTLY模式。控件可以相应你指定的具体宽高值或者match_parent属性,如果要让自定义View支持wrap_content属性,则必须要重写onMeasure方法,否则在布局中使用wrap_content就相当于使用match_parent)

2)ViewGroup的measure过程:

对于ViewGroup而言,除了完成自己的measure过程,还要遍历去调用所有子元素的measure方法,各个子元素再递归执行这个过程。

ViewGroup是一个抽象类,它没有重写View的onMeasure方法,但它提供了一个measureChildren的方法,在measureChildren方法中它会遍历ViewGroup中的子元素,并调用measureChild方法,对子元素进行measure。measureChild的思想就是取出子元素的LayoutParams,然后通过getChildMeasureSpec来创建子元素的measureSpec,最后将子元素的measureSpec传递给measure方法就能完成测量,如下图所示:

图5

  正如前面提到的ViewGroup是一个抽象类,它没有重写onMeasure方法,其测量过程中的onMeasure需要其子类去具体实现。如LinearLayout、RelativeLayout。不同的ViewGroup子类的布局特性不同,这也导致其测量细节不同。

  下面简要了解一下LinearLyaout和RelativeLayout的onMeasure实现

1)LinearLayout的Measure实现:

LinearLayout的布局方向有两种,所以LinearLayout会根据mOrientation来分别调用measureVertical或者是measureHorizontal。以水平布局为例,

遍历所有的view,跳过为null或者属性为View.GONE的,加上分割线宽度mDividerWidth和左右margin,计算所有View的childWidth之和mTotalLength,统计所有View的weight和totalWeight,并且对子view进行测量。

2)RelativeLayout的Measure实现:

  当第一次执行onMeasure或者requestLayout后,需要调用sortChildren方法,根据添加顺序对所有的子view进行排序,横着一次,竖着一次,然后对两个序列进行检查,通过依赖图静态类中的getSortedViews方法根据依赖关系进行排序。

  之后在onMeasure中,对子view进行遍历,即对两个序列进行分别遍历。

  首先是横向遍历,调用mSortedHorizontalChildren,获取RelativeLayout.layoutParams,并依次调用方法,计算控件的横向位置及mLeft和mRight,然后横向测量子View,接下去根据前面的结果很想摆放子View,如果此时父RelativeLayout的宽度是WRAP_CONTENT,会在此时对宽高进行修正。

  横向完毕后进行垂直排列的View序列进行上述操在,步骤大致相同,在此处会对子view进行measure时就会正确的测量,之后的操作就是对父RelativeLayout的宽高等属性进行再次修正。

  从上面的分析中,一个最明显的不同就是RelativeLayout在进行measure过程中需要进行两次遍历,而LinearLayout则只需要一次遍历过程。

此外,需要注意的是,在某些极端情况下,系统可能需要调用多次measure才能确定最终的测量宽高,在这种情况下,在onMeasure方法中拿到的测量高很可能是不准确的。所以最好在onLayout方法中获取View的高宽。

3.如何获取View的宽和高

(1)调用onWindowFocusChanged方法(焦点变化),这个时候View已经初始化完毕,这个时候去获取View的宽高是没有问题的。然而当频繁进行onResume和onPause,onWindowFocusChanged方法也会被频繁调用。

(2)调用view.post(runnable)

通过post将一个Runnable投递到消息队列的尾部,然后等待Looper调用此Runnable,view也已经初始化好了。

(3)ViewTreeObserver

使用ViewTreeObserver的众多回调可以使用这个功能,如OnGlobalLayoutListener,当View树的状态发生改变或者View树的View的可见性发生改变时,OnGlobalLayoutListener会被回调,需要注意的是,伴随着View树状态的改变,onGlobalLayoutListener会被回调多次。

(4)View.measure(int widthMeasureSpec,int heightMeasureSpec)

  • match_parent 不能
  • 具体值和wrap_content可以。

4.Layout过程

Layout过程用于ViewGroup确定子元素的位置,当ViewGroup的位置被确定后,它在onLayout中会遍历所有的子元素,并调用其layout方法,在layout方法中onLayout方法又会被调用。

layout方法首先通过setFrame方法俩设置view的四个顶点的位置,接着调用onLayout方法,确定子元素的位置。

由于onLayout的实现同样与布局有关,因此View和ViewGroup均没有实现onLayout方法。

5.draw过程

  • 将View绘制到屏幕上,大概的几个步骤

    1.绘制背景background.draw(canvas)

    2.绘制自己(onDraw)

    3.绘制children(dispatchDraw)

    4.绘制装饰(onDrawScrollBars)

  • View的绘制过程是通过dispatchDraw来实现的,它会遍历所有子元素的draw方法。
  • 如果一个View不需要绘制任何内容,那么设置setWillNotDraw为true后,系统会进行相应的优化;ViewGroup默认为true,如果我们的自定义ViewGroup需要通过onDraw来绘制内容的时候,需要显示的关闭它。
时间: 2024-10-02 05:30:19

Android学习笔记View的工作原理的相关文章

【知了堂学习笔记】ajax工作原理

ajax工作原理 什么是ajax? ajax 的全称是Asynchronous JavaScript and XML,其中,Asynchronous 是异步的意思.从全称中就可以看出AJAX = 异步 JavaScript 和 XML.  AJAX 是一种用于创建快速动态网页的技术.通过在后台与服务器进行少量数据交换,AJAX 可以使网页实现异步更新.这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新.传统的网页(不使用 AJAX)如果需要更新内容,必需重载整个网页面. 使用 AJ

[Android学习笔记]理解焦点处理原理的相关记录

焦点处理相关记录 以下所涉及的焦点部分,只是按键移动部分,不明确包含Touch Focus部分 需解决问题 控件的下一个焦点是哪? 分析思路 当用户通过按键(遥控器等)触发焦点切换时,事件指令会通过底层进行一系列处理.在ViewRootImpl.java中有一个方法,deliverKeyEventPostIme(...),因为涉及到底层代码,所以没有详细的跟踪分析此方法的调用逻辑,根据网上的资料,按键相关的处理会经过此方法. private void deliverKeyEventPostIme

Android艺术开发探索第四章——View的工作原理(下)

Android艺术开发探索第四章--View的工作原理(下) 我们上篇BB了这么多,这篇就多多少少要来点实战了,上篇主席叫我多点自己的理解,那我就多点真诚,少点套路了,老司机,开车吧! 我们这一篇就扯一个内容,那就是自定义View 自定义View 自定义View的分类 自定义View的须知 自定义View的实例 自定义View的思想 一.自定义View的分类 自定义View百花齐放,没有什么具体的分类,不过可以从特性大致的分为4类,其实在我看来,就三类,继承原生View,继承View和继承Vie

Android艺术开发探索第四章——View的工作原理(上)

这章就比较好玩了,主要介绍一下View的工作原理,还有自定义View的实现方法,在Android中,View是一个很重要的角色,简单来说,View是Android中视觉的呈现,在界面上Android提供了一套完整的GUI库,里面有很多控件,但是有时候往往并不能满足于需求,所以只有自定义View了,我们会简单的说下流程,然后再去实践除了View的三大流程之外,View常见的回调方法也是必须掌握的,比如构造方法,onAttach,onVisibilityChanged,onDetach,另外对于一些

Android开发艺术探索——第四章View的工作原理

Android开发艺术探索--第四章View的工作原理 4.1 (一)初识ViewToot和DecorView 基本概念 ViewRoot对应于ViewRootImpl类,是连接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot来完成的.在ActivityThread中,当Activity对象被创建完成后,会将DecorView添加到View中.同时,会创建ViewRootImpl对象,并将ViewTootImpl对象和DecorView建立关联.

Android学习笔记二

17. 在ContentProvider中定义的getType()方法是定义URI的内容类型. 18. SQLiteDatabase类中的insert/delete/update/query方法其实也挺好用的,我在EquipmentProvider类中做了实现 19. Android专门有个单元测试项目(Android Test Project),在这个项目中,可以新建一个继承AndroidTestCase类的具体测试类来单元测试某个功能.我新建了一个AndroidTestProject项目,在

udacity android学习笔记: lesson 3

udacity android学习笔记: lesson 3 作者:干货店打杂的 /titer1 /Archimedes 出处:https://code.csdn.net/titer1 联系:1307316一九六八 声明:本文采用以下协议进行授权: 自由转载-非商用-非衍生-保持署名|Creative Commons BY-NC-ND 3.0 ,转载请注明作者及出处. tips:https://code.csdn.net/titer1/pat_aha/blob/master/Markdown/an

Android学习笔记-回顾计划

人最怕的是,没有方向! 1.楔子: 本人接触Andrjoid开发也有一年多了,期间在一家外包公司独立开发了五六个项目.虽谈不上大牛,但自认小有所成.平时没什么爱好,就喜欢看看技术博客,试验各种开源代码,写写学习笔记. 最近感觉有点陷入瓶颈了,进步甚慢,却又不知该如何进一步提升自己.对于开发中遇到的很多问题,虽有所领悟,然不够系统,一些小知识点,也常有遗漏.觉得是时候系统的反思一下自己的知识体系了,于是决定制定一个回顾计划,综合自己看的博客.书籍,以及自己的开发实践,对一些常用的知识点进行整理.

Pro Android学习笔记(十二):了解Intent(下)

解析Intent,寻找匹配Activity 如果给出component名字(包名.类名)是explicit intent,否则是implicit intent.对于explicit intent,关键就是component 名字,在<intent-fliter>中声明的其他属性被忽略.对于implicit intent,则根据action,category和data来进行匹配.然而一个intent fliter中可以声明多个actions,多个categories,多个data属性,因此可以满