View简介
1.View原理及其子类介绍
View是Android UI组件的基类,ViewGroup是容纳UI组件的容器,ViewGroup本身也是从View派生出来的。Android视图,是类似于Dom树的架构。父视图负责测量定位绘制等操作。我们经常在用的findViewById 方法代价昂贵的原因,就是因为他负责至上而下遍历整棵控件树,来寻找View实例,在重复操作中尽量少用。现在在用的很多控件都是直接或者间接继承自View的,如下图:
View与GroupView的子类详尽继承关系图分析见:Android View与GroupView原理以及其子类描述
2.Android UI界面架构
每个Activity包含一个PhoneWindow对象,PhoneWindow类继承了Window类,PhoneWindow类有两个重要的成员变量mDecor和mContentParent,它们的类型分别DecorView和ViewGroup。其中,成员变量mDecor是用描述自己的窗口视图,而成员变量mContentParent用来描述视图内容的父窗口。PhoneWindow设置DecorView为应用窗口的根视图,再里面就是熟悉的TitleView和ContentView。TitleView就是在很多界面顶部显示的那部分内容,可以在代码中控制让它是否显示没错,而ContentView就是一个FrameLayout,这个布局的id叫作content,平时使用的setContentView()就是设置的ContentView,调用setContentView()时所传入的布局其实就是放到这个FrameLayout中的。
DecorView类所描述的应用程序窗口视图是否需要重新绘制是由另外一个类ViewRoot来控制的,ViewRoot类继承于Handler类。系统在启动一个Activity组件的过程中,会为这个Activity组件创建一个ViewRoot对象,同时还会将前面为这个Activity组件所创建的一个PhoneWindow对象的成员变量mDecor所描述的一个视图(DecorView)保存在这个ViewRoot对象的成员变量mView中。这样,这个ViewRoot对象就可以通过调用它的成员变量mView的所描述的一个DecorView的成员函数draw来绘制一个Acitivity组件的UI了。ViewRoot类的作用是非常大的,它除了用来控制一个Acitivity组件的UI绘制之外,还负责接收Acitivity组件的IO输入事件,例如,键盘事件。
View的绘制过程
由以上介绍可以知道,Android中的任何一个布局、任何一个控件其实都是直接或间接继承自View的,如TextView、Button、ImageView、ListView等。这些控件虽然是Android系统本身就提供好的,我们只需要拿过来使用就可以了,那它们又是怎样被绘制到屏幕上的呢?任何一个视图都不可能凭空突然出现在屏幕上,它们都是要经过非常科学的绘制流程后才能显示出来的。每一个视图的绘制过程都必须经历三个最主要的阶段,即onMeasure()、onLayout()和onDraw(),三个阶段的详尽分析: Android视图绘制流程完全解析 View(二)。
附带LayoutInflater的分析: Android LayoutInflater原理分析 View(一)
自定义View
1.为什么要自定义View?
- 现有的View满足不了你的需求,也没有办法从已有控件派生一个出来;界面元素需要自己绘制;
- 现有View可以满足要求,把它做成自定义View只是为了抽象:为这个自定义View提供若干方法,方便调用着操纵View。通常做法是派生一个已有View,或者结合xml文件直接inflate;
- 在多个应用并行开发的团队,将公用的交互效果提取成自定义控件,方便复用,减少不必要的重复劳动。
常见的Android自定义View主要有两种类型:
- 组合控件:通过Android的基础控件(TextView、CheckBox、Button、ProgressBar等)组合而成,比如试题控件(TextView+VideoGroup)、下拉刷新、瀑布流控件、带左/右滑功能的控件、视频控件等,这种自定义View的难点在于程序的逻辑处理;
- 完全自定义控件:继承自View、TextureView或SurfaceView,然后重写核心的回调方法,以View为例,按需复写其构造函数、onMeasure、onLayout、onTouchEvent、onDraw、onAttachedToWindow、onDetachedFromWindow等方法,这种自定义View的难点在于程序的设计、效率优化和排版,比如输入法中的手写控件、图文混排控件(现在很多都是通过webview加载网页实现了)、词典取词控件、图表控件、个性化进度条、弹幕显示控件、Markdown控件、IDE代码编辑控件等。
2.基础知识点储备
(1)了解下核心知识点View、SurfaceView、TextureView的区别:
- View:普通View,与宿主窗口共享同一个绘图表面,UI在主线程中绘制,在有无硬件加速的情况下都能工作(没有硬件加速时,canvas的有些方法会失效);
- SurfaceView:继承自View,绘制和显示效率高,因为拥有独立的绘图表面,UI在一个独立的线程中进行绘制,不会占用主线程的资源。-
SurfaceView的使用和普通的View不一样,需要结合SurfaceHodler一起使用。因为和宿主窗口不是共享同一个绘图表面的原因,笔者在实际使用SurfaceView的过程中发现对其做动画操作会达不到想要的效果(一坨黑); - TextureView:继承自View,与SurfaceView相比,TextureView不会创建一个单独的绘图表面,这使得它可以像一般的View一样执行一些变换操作,比如移动、动画等等,但TextureView必须在硬件加速开启的窗口中才能正常工作。
(2)自义定属性;
对于自定义View的一些属性设置,除了可以在自定义View中提供公开接口外,还可以通过自定义属性,在对自定义View布局时就指定,这样可以简化用户使用控件的复杂度,实现自定义属性的步骤如下:
- 在res/values文件夹下新建一个attrs.xml的文件,在里面定义自定义属性的ID、属性和属性对应的类型
<declare-styleable name="TipView">
<attr name="singleLine" format="boolean"/>
<attr name="styleMode">
<flag name="white" value="1" />
<flag name="orange" value="2" />
<flag name="front" value="3" />
</attr>
<attr name="showMore" format="boolean" />
</declare-styleable>
- 在自定义View带attrs参数的构造方法中解析自定义属性值
int styleMode;
boolean singleLine = false;
boolean showMore = false;
if (null != attrs) {
TypedArray tArray = context.obtainStyledAttributes(attrs, R.styleable.TipView);
styleMode = tArray.getInt(R.styleable.TipView_styleMode, STYLE_MODE_WITHE);
singleLine = tArray.getBoolean(R.styleable.TipView_singleLine, false);
showMore = tArray.getBoolean(R.styleable.TipView_showMore, false);
tArray.recycle();
}
对自定义属性的解析需要注意两点:
a. TypedArray使用后一定要调用其recycle方法,否则会有内存泄露的问题;
b. 如果自定义View在一个单独的module中(不属于主工程),对attr的获取不能使用switch-case语句,要用if...else,具体原因之前有介绍过,详见:在Android library中不能使用switch-case语句访问资源ID的原因分析及解决方案
(3)SpannableString
可以通过它将同一串字符中的不同文字做不同的处理,比如某些文字的颜色、字体、背景色、大小等有变化,都可以通过它来设置,熟练掌握SpannableString对于灵活自定义View会有很大地帮助。
3.自定义View的步骤又是什么?
先看张自定义View的函数调用流程图:
其中需注意的是(具体分析见:安卓自定义View进阶 - 分类和流程):
- 几种重载的构造函数,在什么情况下调用哪种构造函数
- 如何自定义属性和使用attrs
- 几个重要的函数onMeasure、onLayout、onDraw等
继续深一步的学习:
- 安卓自定义View进阶 - Canvas之绘制图形
- 安卓自定义View进阶 - Canvas之画布操作
- 安卓自定义View进阶 - Canvas之图片文字
- 安卓自定义View进阶 - Path之基本操作
- 安卓自定义View进阶 - Path之贝塞尔曲线
- 安卓自定义View进阶 - Path完结篇
- 安卓自定义View进阶 - PathMeasure
- 安卓自定义View进阶 - Matrix原理
- 安卓自定义View进阶 - Matrix详解
- 安卓自定义View进阶 - Matrix Camera
- 安卓自定义View进阶 - 事件分发机制原理
- 安卓自定义View进阶 - 事件分发机制详解
- 安卓自定义View进阶 - MotionEvent详解
- 安卓自定义View进阶 - 特殊控件的事件处理方案
- 安卓自定义View进阶 - 多点触控详解
- 安卓自定义View进阶 - 手势检测(GestureDecetor)
布局性能优化
在定义布局时,难免会有一些不必要的嵌套和View节点,那怎样优化减少不必要的infalte呢?
使用抽象布局标签(include, viewstub, merge)可进行相应的优化,还可以使用一些布局调优相关工具(hierarchy viewer和lint)等。具体分析见:性能优化之布局优化、布局技巧:合并布局
- <include/>标签:常用于将布局中的公共部分提取出来供其他layout共用,以实现布局模块化,这在布局编写方便提供了大大的便利。
include标签唯一需要的属性是layout属性,指定需要包含的布局文件。可以定义android:id和android:layout_*属性来覆盖被引入布局根节点的对应属性值。 - <viewstub/>标签:viewstub标签同include标签一样可以用来引入一个外部布局,不同的是,viewstub引入的布局默认不会扩张,即既不会占用显示也不会占用位置,从而在解析layout时节省cpu和内存。 viewstub常用来引入那些默认不会显示,只在特殊情况下显示的布局,如进度布局、网络失败显示的刷新布局、信息出错出现的提示布局等。
- <merge/>标签:使用了include后可能导致布局嵌套过多,多余不必要的layout节点,从而导致解析变慢,不必要的节点和嵌套可通过hierarchy viewer或设置->开发者选项->显示布局边界查看。merge标签在UI的结构优化中起着非常重要的作用,它可以删减多余的层级,优化UI。 merge标签可用于两种典型情况:
a. 布局顶结点是FrameLayout且不需要设置background或padding等属性,可以用merge代替,因为Activity内容视图的parent view就是个FrameLayout,所以可以用merge消除只剩一个。
b. 某布局作为子布局被其他布局include时,使用merge当作该布局的顶节点,这样在被引入时顶结点会自动被忽略,而将其子节点全部合并到主布局中。