这是我参与「第四届青训营 」笔记创作活动的第5天
常规&高级UI编程
本堂课内容
- UI组件
- 布局
- 渲染
- 交互
- 动画
- 自定义UI
知识点介绍
UI组件
什么是UI
UI:User Interface,图形用户界面
| 组件 | JavaClass | Package |
|---|---|---|
| 文本组件 | TextView | android.widget.TextView |
| 图片组件 | ImageView | android.widget.ImageView |
| 按钮组件 | Button | android.widget.Button |
| 输入框组件 | EditText | android.widget.EditText |
| 复选框组件 | CheckBox | android.widget.CheckBox |
| 单选组件 | RadioButton | android.widget.Button |
如何学习?
常规view的属性和方法包括通用的属性和方法以及特定的属性和方法。
常规UI组件的使用
屏幕的原点在左上角。
margin:组件距离边距的距离。
gravity:子元素在该容器内的对齐方式。
layout_gravity:组件相对于父容器对其方式。
高级UI组件
| 组件 | JavaClass |
|---|---|
| 滑动组件 | ScrollView |
| 列表组件 | ListView/RecyclerView |
| 下拉刷新组件 | PullToRefresh |
| 分页组件 | ViewPager |
| 布局组件 | LinearLayout/RealtiveLayout |
常规UI组件大多是View,高级UI组件大多是ViewGroup,比常规UI组件有更多的功能。
布局
如何将多个UI组件组成一个页面?包含大小,位置,层级等等因素
LinearLayout
常用的,很简单的布局方式——线性布局。按照单一的方向来布局,例如水平、垂直等等方向。
| 属性 | 功能描述 |
|---|---|
| orientation | 布局内组件的排列方向 |
| layout_weight | 布局内组件大小权重 |
| divider | 布局内组件间分割线 |
| showDividers | 布局内组件间分割线位置 |
| dividerPadding | 布局内分割线padding |
RelativeLayout
- 相对位置,通过与别的组件或者父容器的相对位置来确定。
- 可以消除嵌套视图组并使布局层次结构保持扁平化,从而提高性能
- 一个RelativeLayout就可以替换多个嵌套的LinearLayout
- 适用于复杂场景
FrameLayout
层级布局,与前两种布局方式不同。以层级叠加的方式排列组件,适用于层级排列场景。
| 属性 | 功能描述 |
|---|---|
| android:foreground | 前景图像 |
| android:foregroundGravity | 设置前景图像Gravity |
ConstraintLayout
- 通过约束组件位置排列组件
- 扩展布局方式
- 所有视图均根据同级与父布局之间的关系进行布局
- 可以使用扁平视图层次结构(无嵌套视图组)创建复杂的大型布局
- 适用于复杂场景
- 为获得更好的性能和工具支持,推荐使用ConstrainLayout
渲染
页面是如何渲染出来的?分为布局加载、布局解析和UI渲染等部分。
布局加载
- 编写布局文件
- 注册Manifest
- 设置布局文件
布局解析
通过setContentView来创建DecorView,最终有LayoutInflater来加载了XML文件,而LayoutInflater通过解析XML文件,并且根据XML文件生成View实例,并将实例添加到了ViewGroup之中
UI渲染
页面绘制的起点是在onResume之中。
根据Activity的生命周期,在onCreate之中setContentView创建了DecorView,并将layout中的View添加到了DecorView之中,在onResume的ViewRootImpl.requestLayout中触发了页面绘制的部分。
这其中很重要的部分便是Vsync信号,在一个信号之内cpu和gpu需要将整个也页面的布局部分计算出来,并且完成UI渲染的部分,如果在一个信号周期之内没有完成计算,那么将会出现丢帧的现象。这也就解释了为什么手机在高负载的运算条件下会出现丢帧的现象。
交互
分为两个部分:获取View实例,添加相应监听器
| 回调方法 | 事件监听器 |
|---|---|
| onClick() | View.OnClickListener 当用户轻触项目,系统就会调用此方法 |
| onLongClick() | View.OnLongClickListener 当用户轻触并按住项目时,系统会调用此方法 |
| onFocusChange() | View.OnFocusChangeListener 当用户使用导航键或轨迹球转到或离开项目时,系统会调用此方法 |
| onKey() | View.OnFocusChangeListener 当用户聚焦于项目或按下硬件按键时,系统会调用此方法 |
| onTouch() | View.OnTouchListener 当用户执行可视为触摸事件的操作时,系统会调用此方法 |
所有的交互事件都来自于对屏幕触摸信号的处理,View.OnClickListener()等常用点击事件是对交互事件的二次封装
触摸事件
当用户触摸屏幕时,系统将建立一系列的MotionEvent对象,MotionEvent包含关于发生触摸的位置和时间等细节信息,MotionEvent对象被传递到相应的捕获函数中。
View的事件响应
- 在onTouchEvent()的ACTION_DOWN设置了一个延时Runnable,用于处理onLongClickListener
- 在onTouchEvent()的ACTION_DOWN中,判断onLongClick是否执行,未执行则移除,然后执行onClickListener
事件分发
顺序:Activity->ViewGroup->View
核心方法:
- dispatchTouchEvent
- onInterceptTouchEvent
- onTouchEvent
动画
分为帧动画,补间动画,属性动画
帧动画
作用于视图控件(View),原理为将动画分解为帧的形式,类似于GIF
优点:使用简单、方便,但缺点为功能单一
常常应用于连续性动画
补间动画
常常作用于View,通过确定开始视图样式和结束视图样式,中间样式由系统根据插值器确定来形成完整动画
补间动画常有以下几类
- 透明度动画
- 旋转动画
- 缩放动画
- 平移动画
优点:简单,方便
缺点:只能控制视图效果,无法控制属性
常应用于转场动画和视图基础动画
补间动画中的插值器是一个接口,是用来设置属性值从初始值过渡到结束值的变化规律
属性动画
属性动画可以作用于任意Java对象,不在限于视图控件
原理是在指定时间间隔内,通过不断对值的改变与不断将值赋给对象的属性,从而实现该对象在该属性上的动画效果。
特点便是作用对象进行了扩充,不再限于View对象,而且动画效果丰富,不只是补间动画对应的4种动画效果。
常常用于与属性相关、复杂的动画
视图动画和属性动画
- 视图动画:不改变动画的属性,在动画过程中仅对图像进行变换来达到动画效果。无论动画结果在哪,该View的位置和响应区域都是在原地
- 属性动画:改变了动画属性,因属性动画在动画过程中对动态改变了对象属性,从而达到了动画效果
自定义View
创建一个自定义View
| 构造器 | 应用场景 |
|---|---|
| 1个参数 | Java代码中创建View |
| 2个参数 | 通过XML声明创建View |
| 3个参数 | 通过XML声明创建View+Style |
| 4个参数 | 通过XML声明创建View+Style+Theme |
处理一个View布局
需要通过重写方法来处理View布局
| 复写方法 | 应用场景 |
|---|---|
| onMeasure | 一般需要复写宽高设置为wrap_content场景以及组件宽高有比例限制 |
| onLayout | 继承自ViewGroup时必须复写,继承自View时一般不用 |
| onSizeChanged | 视图大小发生改变时候调用 |
绘制View
通过两个核心类:Paint和Canvas来实现绘制
处理用户交互
| 交互事件 | 操作逻辑 |
|---|---|
| ACTION_DOWN | 设置操作状态 |
| ACTION_MOVE | 处理拖拽,触发UI更新 |
| ACTION_UP | 处理开关状态,播放动画 |
| ACTION_CANCEL | 复位操作状态 |
课后个人总结
通过本堂课认识到了有关UI界面的相关知识,给我的一大感触就是与UI有关的部分内容比之前两节课加起来都要多,感觉对于UI部分的吸收不是很充分,而且老师在上课的时候为了深入讲解有关UI界面的逻辑与内核,放上了许多源码,这对于初学Android的我来说算是比较难理解的部分了。
但是对于常用的布局与UI组件,页面交互与动画的常用知识还是有深刻的了解的。比如说ClickListener有关的部分以及动画相关的部分,只有了解到这些差异才可以在项目开发时候对于使用哪一个有自己的认识与判断。