Android
的用户界面框架(Android UI Framework
)采用MVC
(Model-View-Controller
)模型,为用户界面提供了处理用户输入的控制器(Controller
)和显示界面内容的视图(View
)。其中模型层(Model
)是应用程序的核心,数据和代码都被保存在模型中。
MVC
模型中的视图将应用程序的信息反馈给用户,可能的反馈方法包括视觉、听觉或者触觉等,最常用的就是通过屏幕显示反馈信息。
在Android
中,界面是由一颗View
树来定义的。View
树的叶子结点是视图组件,而其他非叶子结点则是容器组件。View
树的如下:
View树的这种树型结构也为组件的事件管理,如触摸屏、键盘点击事件,提供了很多的便利。可以说组件的事件传递机制就是沿着树,从高层向底层传递的。
从上图中可以看出,一个ViewGroup
可以拥有多个View
,也可以包含多个ViewGroup
。Android
这种非常灵活的View
层次结构可以形成非常复杂的布局,开发者可以利用这个特性开发出特别精致的界面。
当调用Activity
的setContentView()
方法的时候,可以将一棵树的根节点或者其子树的根节点,设置是叶子节点作为参数传入此方法,这样系统就获得了该结点的参数,并将该节点作为根节点来测距和绘制,最终将以该结点为根节点的整棵树都绘制在界面上。绘制过程中,父节点负责请求其子节点绘制它们自己,子节点可能会请求他们在父节点中的大小和位置,父节点对每个子节点的大小和位置由最终的决定权。
Android
的用户界面都是由视图组件(View
)和容器组件(ViewGroup
)组成的。为了便于理解,可以将容器看成是屏幕上的一个矩形的区域,是视图则是位于该矩形区域内的一个组件。
View
类是Android API中定义的类,位于android.view
包中。一个View
类或者其子类的对象中存储与该对象相关的布局和内容属性等,并且可以实现处理包括测距、布局、绘图、焦点变换、滚动条以及屏幕区域内的该对象的按键和手势在内的多种功能。View还为Android中的Widget提供服务。Widget
是Android
的系统包,包含了一组View
类的可实例化子类。这些子类可以用于绘制交互式屏幕元素。使用Widget
,我们可以快速地创建用户界面。
ViewGroup
类也是Android API中的类,位于android.view
包中。值得一提的是,ViewGroup
虽然是View
的子类,但是与View类有着完全不同的任何和功能。ViewGroup
,其实是一组View
类对象的集合。实际上,ViewGroup
的功能也确实是这样的,它负责装载和管理一组View
,包括View
类的对象和ViewGroup
本身。
ViewGroup的一个重要的子类是布局(Layout)。Layout可以为一组View创建一个结构。
Android界面的组成元素是View
和ViewGroup
的子类和简介子类。视图组件的作用是显示,而容器组件的作用是管理。
方法 | 说明 |
---|---|
getTop() | 获取View自身顶边到其父控件布局顶边的距离。 |
getLeft() | 获取View自身左边到其父控件布局左边的距离。 |
getTop() | 获取View自身右边到其父控件布局左边的距离。 |
getTop() | 获取View自身底边到其父控件布局顶边的距离。 |
上图中的圆点,就是我们的触摸点(假定)。触摸事件最终都会由onTouchEvent(MotionEvent event)
方法来处理。
方法 | 说明 |
---|---|
getX() | 获取点击事件距离控件左边的距离,即视图坐标。 |
getY() | 获取点击事件距离控件顶边的距离,即视图坐标。 |
getRawX() | 获取点击事件距离整个屏幕左边的距离,即绝对坐标。 |
getRawY() | 获取点击事件距离整个屏幕顶边的距离,即绝对坐标。 |
视图展示的系统架构,由视图根结点(RootView)开始,视图根结点负责连接窗口管理器(WindowManage)与装饰视图(DecorView),窗口管理器用于响应用户事件,而装饰视图用于展示特定图像。
performTravesals()
中包含了以下三个方法:
performMeasure()
测量performLayout
布局performDraw
绘制Android
的显示过程可以简单概括为:Android
应用程序把经过测量、布局绘制后的surface
缓存数据,通过SurfaceFlinger
把数据渲染到显示屏幕上,通过Android
的刷新机制来刷新数据。也就是说应用层负责绘制,系统层负责渲染,通过进程间通信把应用层需要绘制的数据传递到系统层服务,系统层服务通过刷新机制把数据更新到屏幕。
绘制任务是由应用发起的,最终通过系统层绘制到硬件屏幕上。也就是说,应用进程绘制好后,通过跨进程通信机制把需要显示的内容传递到系统层,由系统层的SurfaceFlinger
服务绘制到屏幕上。
在Android
中每个View
绘制中有三个核心步骤,通过Measure
和Layout
来确定当前需要绘制的View
所在的大小和位置,通过绘制(Draw)到Surface
,在Android系统中整体的绘图源码是在ViewRootImp
类performTraversals()
方法,通过这个方法可以看出Measure
和Layout
都是通过递归来获取View
的大小和位置的,并且以深度作为优先级。可以看出,层级越深,元素越多,耗时也越长(优化点)。
方法 | 说明 |
---|---|
Measure | 用深度优先原则递归得到所有视图(View)的宽、高;获取当前View的正确宽度childWidthMeasureSpec 和高度childHeightMeasureSpec 之后,可以调用它的成员函数Measure来设置它的大小。如果当前正在测量的子视图child是一个视图容器,那么它又会重复执行操作,直到它的所有子孙视图的大小都测量完成为止。 |
Layout | 用深度优先原则递归得到所有视图(View)的位置;当一个子View在应用程序窗口左上角的位置确定之后,再结合它在前面测量过程中确定的宽度和高度,就可以完全确定它在应用程序窗口中的布局。 |
Draw | 目前Android支持了两种绘制方式:软件绘制(CPU)和硬件加速(GPU),其中硬件加速在Android3.0开始已经全面支持,很明显,硬件加速在UI的显示和绘制的效率远远高于CPU绘制。 GPU的缺点: 耗电:GPU的功耗与CPU高。 兼容问题:某些接口和函数不支持硬件加速。 内存大:使用OpenGL的接口至少需要8MB的内存。 |
Android的界面绘制是通过递归来绘制界面,因此通过减少Layout层级,减少测量、绘制时间,提高复用性三个方面来优化布局的时间是非常重要的。
RelativeLayout
和LinearLayout
。LinearLayout
。LinearLayout
有时会使嵌套层级变多,应该使用RelativeLayout
,使界面尽量扁平化。Space
控件onDraw
方法进行了一个空的实现。Merge
只能用在布局XML文件的根元素。Merge
来加载一个布局,必须指定一个ViewGroup
作为其父元素,并且要设置加载的attachToRoot
参数为true
(参照inflat(int, ViewGroup, boolean)
)ViewStub
中使用Merge
标签。原因就是ViewStub
的inflate
方法中根本没有attachToRoot
的设置。使用ViewStub
标签提高显示速度。
在开发中,我们经常会遇到一个问题,在某一个布局中的子布局非常多,但是在App中,又不是所有的布局都需要显示(部分显示),打开这个界面的时候,会根据不同的场景和属性显示不同的Layout。例如:一个页面对不同的用户,比如未登录用户、普通用户、VIP用户会显示不同的页面。又或者说,有些用户喜欢对APP界面中的某些元素进行隐藏,比如微信的朋友圈入口,这个时候可能就用到了INVISIBLE
或者GONE
属性。但是INVISIBLE
或者GONE
属性的效率非常的低,原因是即使将元素隐藏了,它们仍然在布局中,仍会测试和解析这些布局。
这个时候,就可以使用ViewStub来解决。
ViewStub是一个轻量级的View,它是一个看不见的,并且不占布局位置,占用资源非常小的视图对象。可以为ViewStub指定一个布局,加载布局的使用,只有ViewStub会被初始化,然后当ViewStub被设置为可见的时候,或者调用了ViewStub.inflate()
的时候,ViewStub所指向的布局才会被加载和实例化,然后ViewStub的布局属性都会传给它指向的布局。这样,就可以使用ViewStub来设置是否显示某个布局。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
android:id="@+id/topBar"
layout="@layout/common_top_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/top_bar_height" />
<ViewStub
android:id="@+id/viewstub_first"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout="@layout/viewstub_first" />
<ViewStub
android:id="@+id/viewstub_second"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout="@layout/viewstub_second" />
<ViewStub
android:id="@+id/viewstub_default"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout="@layout/layout_default" />
LinearLayout>
在调用的时候,根据不同的需求切换不同的Layout,这样可以提高页面初始化的速度,使用代码如下:
View view = view.inflate(R.layout.activity_viewstub, container, false);
switch (choice) {
case First:
ViewStub stub1 = view.findViewById(R.id.viewstub_first);
stub1.inflate();
break;
case Second:
ViewStub stub2 = view.findViewById(R.id.viewstub_second);
stub2.inflate();
break;
default:
ViewStub stub = view.findViewById(R.id.viewstub_default);
stub.inflate();
break;
}
ViewStub显示有两种方式,上面的代码使用的是inflate方法,也可以直接使用ViewStub.setVisibility(View.Visible)
方法。
ViewStub
只能加载一次,之后ViewStub对象会被置为空。换句话说,某个被ViewStub指定的布局被加载后,就不能再通过ViewStub来控制它了。所以它不适用于需要按需显示隐藏的情况。ViewStub
只能用来加载一个布局文件,而不是某个具体的View,当然也可以把View写在某个布局文件中。如果想操作一个具体的View,还是使用visibility属性。延迟加载的功能非常重要,特别是界面中显示的内容比较多并且所占空间比较大的时候。在Android应用程序中,可以使用ViewStub
实现延迟加载的功能。
当调用ViewStub
的setVisbility()
函数设置为可见或者调用inflate
初始化该View的时候,ViewStub
引用的资源开始初始化,然后引用的资源填充在ViewStub
所在的位置上。在没有调用setVisibility(int)
函数或者inflate()
函数之前,ViewStub
一直存在组件树层级结构中。但是由于ViewStub
非常轻量级,所以对性能的影响非常小。
提高布局效率的方法总体来说就是减少层级,提高绘制速度和布局复用。
影响布局效率的主要原因如下:
标签使用。
标签加载一些不常用的布局。
标签减少布局的嵌套层次。wrap_content
,wrap_content
会增加布局Measure时的计算成本,已知宽高为固定值的时候,不用wrap_content
。