View 是 Android 中最基本的 UI 组件,在屏幕上绘制一块矩形区域。
ViewGroup 是一种特殊的 View,它能够包含多个子 View 和子 ViewGroup,用于放置、组织、管理视图结构。java
经常使用控件和布局的继承结构:性能优化
Android Project 默认生成的 avtivity_main.xml 布局文件中,根结点使用 RelativeLayout,然而做为顶级 View 的 DecorView 则是垂直方向的 LinearLayout,从上至下分为标题栏、内容栏。经常使用的 setContentView()
方法就是为内容栏设置布局。工具
onMeasure()
过程,而 LinearLayout 只需一次,可是当 LinearLayout 设置 weight 属性后,一样须要两次 onMeasure()
过程。DecorView 层级深度已知且固定,标题栏与内容栏,采用 RelativeLayout 并不会下降层级深度,所以使用 LinearLayout 效率更高。布局
Project 默认使用 RelativeLayout 做为根结点,是但愿开发者可以尽可能减小 View 层级结构,避免使用 LinearLayout 多层嵌套完成布局。性能
LayoutInflater inflate()
方法用于动态加载布局,将 XML 布局文件实例化为其对应的 View 对象。优化
public View inflate(int resource, ViewGroup root) // 方法一 public View inflate(int resource, ViewGroup root, boolean attachToRoot) // 方法二
状况一: root 为 null;spa
若是 root 为 null,attachToRoot 参数将失去意义。code
无需将 resource 指定的布局添加到 root 中,同时没有任何 ViewGroup 容器来协助 resource 指定的布局的根元素生成布局参数 LayoutParams。orm
状况二: root 不为 null,attachToRoot 为 true; xml
将 resource 指定的布局添加到 root 中,inflate()
方法返回结合后的 View,其根元素是 root。View 将会根据它的父 ViewGroup 容器的 LayoutParams 进行测量和放置。
使用方法一即未设置 attachToRoot 参数时,若是 root 不为 null,attachToRoot 参数默认为true。
状况三: root 不为 null,attachToRoot 为 false;
无需将 resource 指定的布局添加到 root 中,inflate()
方法返回 resource 指定的布局 View,根元素是自身的最外层,View 不存在父 ViewGroup,可是能够根据 root 的 LayoutParams 进行测量和放置。
状况三不解之处在于,既然 attachToRoot 为 false,无需将 resource 指定的布局添加到 root 中,那么为何 root 仍然不为 null?建立的 View 必然包含 layout 属性,可是这些属性须要在 ViewGroup 容器中才能生效,根据 ViewGroup 容器的 LayoutParams 进行测量和放置 View。
状况三的意思是,无需将 View 添加到某个 ViewGroup 容器中,却又能根据这个 ViewGroup 容器的 LayoutParams 进行测量和放置 View。
状况一和状况三依赖手动添加 View。
多个参数版本的 inflate()
方法最终汇合调用:
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)
Pull 解析方式基于事件驱动方式,Android 中使用 XmlPullParser 对象,调用 setInput()
方法传递数据给解析器。开始循环解析,经过 getEventType()
方法获取当前解析事件,若是当前解析事件并不是 END_DOCUMENT
,调用 next()
方法获取下一个解析事件。针对不一样事件处理,getName()
方法能够得到当前节点名字(tag name),nextText()
方法能够得到当前节点内的具体内容(TEXT
)。
Events(事件):
START_DOCUMENT
开始文档START_TAG
开始标签TEXT
文本内容END_TAG
结束标签END_DOCUMENT
结束文档XML(可扩展标记语言)组成部分:
<?xml version="1.0" encoding="UTF-8"?>
。<
开头,以 >
结尾。包括:start-tag(<section>
)、end-tag(</section>
)、empty-element tag(<line-break />
)。onMeasure()
决定 View 的大小onLayout()
决定 View 在 ViewGroup 中的位置onDraw()
绘制 ViewonMeasure()
视图大小的测量过程,是由父视图、布局文件、以及视图自己共同完成的。
父视图提供参考大小(MeasureSpec: specSize, specMode)给子视图
UNSPECIFIED
子视图按照自身条件设置成任意的大小EXACTLY
父视图但愿子视图的大小应该由 specSize 来决定AT MOST
子视图最大只能是 specSize 中指定的大小布局文件中指定视图的大小
MATCH_PARENT
WRAP_CONTENT
onLayout()
根据测量出来的(onMeasure()
)宽度和高度肯定视图的位置。关键方法:public void layout (int l, int t, int r, int b)
方法接收左、上、右、下的坐标。
onDraw()
完成测量(onMeasure()
)和布局操做(onLayout()
)以后,建立 Canvas 对象绘制视图。
重要方法:
dispatchTouchEvent()
onInterceptTouchEvent()
onTouchEvent()
事件分发顺序:由 Activity 开始先传递给 ViewGroup 再传递给 View。
事件分发始于 Activity.dispatchTouchEvent()
方法,传递事件至 Window 的根视图。
若最终没有视图消费事件则调用 Activity.onTouchEvent(event)
方法。
ViewGroup 中能够经过 ViewGroup.onInterceptTouchEvent()
方法拦截事件传递,返回 true 表明同一事件列再也不向下传递给子 View,返回 false 表明事件继续传递,默认返回 false。
事件递归传递至子 View 的 View.dispatchTouchEvent()
方法,若是事件被子 View 消费,则返回 true,ViewGroup 将没法再处理事件。
若是没有子 View 消费事件则判断 ViewGroup 中是否存在已注册的事件监听器(mOnTouchListener),存在则调用它的 ViewGroup.OnTouchListener.onTouch()
方法,若是 onTouch()
方法返回 false 即未消费事件,则进一步去执行 ViewGroup.onTouchEvent(event)
方法。
View.dispatchTouchEvent()
方法:首先判断 View 中是否存在已注册的事件监听器(mOnTouchListener),存在则调用它的 View.OnTouchListener.onTouch()
方法,如若 onTouch()
方法返回 false 即未消费事件,则进一步去执行 View.onTouchEvent(event)
方法。
View 能够注册事件监听器(Listener)实现 onClick(View v)、onTouch(View v, MotionEvent event)
方法。相比 onClick()
方法,onTouch()
方法可以作的事情更多,判断手指按下、抬起、移动等事件。同时注册二者事件传递顺序,onTouch()
方法将会先于 onClick()
方法执行,而且 onTouch()
方法可能执行屡次(MotionEvent 事件:ACTION_DOWN
、ACTION_UP
、ACTION_MOVE
)。如若设置 onTouch()
方法返回值为 true,事件视为被 onTouch()
方法消费,再也不继续向下传递给 onClick()
方法。
优化布局层级
每一个控件和布局都须要通过初始化、布局、绘制过程才能呈现出来。当使用多层嵌套的 LinearLayout 以至产生较深的视图层级结构,更甚者在 LinearLayout 中使用 layout_weight
参数,致使子 View 须要两次 onMeasure()
过程。如此反复执行初始化、布局、绘制过程容易形成性能问题。
须要开发者检查布局、修正布局,能够借助 Lint 工具发现布局文件中的视图层级结构里值得优化的地方,同时扁平化处理本来多层嵌套的布局,例如使用 RelativeLayout 做为根节点。
使用 <include/> 复用布局
经过使用 <include/>
和 <merge/>
标签,在当前布局中嵌入另外一个较大的布局做为组件,从而复用完整的布局的视图层级结构。
<include/>
和 <merge/>
标签的区别:
<include/>
标签旨在重用布局文件
layout1.xml:
<FrameLayout> <include layout="@layout/layout2"/> </FrameLayout>
layout2.xml:
<FrameLayout> <TextView /> </FrameLayout>
最终布局视图层级结构:
<FrameLayout> <FrameLayout> <TextView /> </FrameLayout> </FrameLayout>
<merge/>
标签旨在减小视图层级
layout2.xml:
<merge> <TextView /> </merge>
最终布局视图层级结构:
<FrameLayout> <TextView /> </FrameLayout>
懒加载 View
有时布局中包含不多使用的复杂视图,能够在须要时加载视图,减小内存使用,加快渲染速度。ViewStub 是一个轻量级视图,在构建视图层级结构中消耗资源较小。可是实际项目使用中,开发者习惯切换视图的 Visibility 而不是使用 ViewStub。
使用 wrap_content
和 match_parent
,布局尽量自适应屏幕大小。
wrap_content
让当前控件的大小可以恰好包含住里面的内容。match_parent
让当前控件的大小和父布局的大小同样。使用配置限定符,程序在运行时根据当前设备的配置自动加载合适的资源。
Android 常见的限定符:
屏幕特征 | 限定符 |
---|---|
大小 | small, normal, large, xlarge |
分辨率 | ldpi, mdpi, hdpi, xhdpi, xxhdpi |
方向 | land, port |
使用 Nine-Patch 图片 ,从图片资源角度,支持不一样屏幕大小,Nine-Patch 图片容许指定哪些区域能够拉伸而哪些区域不能够。