前言
此文涉及界面绘制流程、如何自定义View、如何刷新View、如何处理事件分发、如何定义动画、如何代码变换控件等。
手动刷新View
调用 requestLayout() 方法时,view 只会执行 onMeasure(先)及 onLayout(后)方法。
调用 invalidate ()方法时,view 会调用 onDraw()方法。
若只改变宽高调用 requestLayout() 方法即可,若只更新内容调用 invalidate ()方法。
关闭硬件加速时,调用任何一个View的invalidate()
方法都会导致整个View树的重新绘制;开启硬件加速时,调用哪一个View的invalidate()
方法就会重绘哪一个View。
界面绘制流程
setContentView将布局添加到DecorView里,形成一个树形结构。之后调动RootView.preformMeasure、preformLayout、preformDraw进行测量、布局、以及绘制。然后绘图数据讲给GPU计算、计算了好了交给硬件显示。
手机也是存在显卡的,显卡集成在cpu上,显卡主要是GPU(图像处理单元)
cpu跟GPU区别:GPU擅图像处理,所以cpu会通过接口将图像处理的一些计算等交给GPU
通过windowManger管理窗口,之后有windowManager调用系统服务WindowManagerService对窗口进行操作。
自定义View
常见分为组合控件以及非组合控件。
组合控件:通常是自定义布局,然后将布局添加到自定义控件里。
非组合控件:
自定义ViewGroup一般重写onMeasure跟onLayout。
自定义Viw一般再重写onDraw。
onMeasure
onMeausre为测量空间measureSpace,受父容器测量空间以及当前空间LayoutParams影响。根据测量空间可获得测量模式以及测量尺,重写onMeasure就是根据测量模式以及测量尺寸,设置自定义控件的尺寸。测量模式分为三种
EXCEATLY:当前size就应该是当前View取得尺寸,对应match_parent、固定尺寸。
AT_MOST:当前size是当前View能取得最大值,对应wrap_parent。
UNSPECIFIED:父容器对当前View没有任何限制,一般当前View可以取默认值。
onLayout
onLayout参数为当前View位置跟尺寸是否有变化boolean值,子控件到当前控件的上下左右边距。
onDraw
Canvas有各种绘制方法:drawLine、drawText等。
canvas.save:保存当前画布。可理解为ps里的锁定当前图层,然后新建一个图层,后续操作在新图层上。比如旋转、平移图层等,不影响原有图层。
canvas.resotre:恢复当前画布。可理解为将新图层跟已有图片合并。save跟restore是成对出现的。
事件分发机制
https://www.cnblogs.com/Android-Alvin/p/13557877.html
概述
滑动屏幕会产生一系列事件,如action_down、action_move、action_up等,这一系列事件成为事件序列。
View类的dispatchTouchEvent负责处理事件
ViewGroup类的dispatchTouchEvent负责分发事件
处理事件优先级onTouchListener > onClickListener > onTouchEvent
dispatchTouchEvent方法主要分为三块
第一块:判断是否拦截
第二块:如果不拦截的话,down事件的话遍历点击到的子控件,看子控件是否处理事件。
如果有子控件处理,则新建mFirstTouchTarget对象,mFirstTouchTarget.next为null。
第三块:如果mFirstTouchTarget为null,则询问自己是否处理。
如果mFirstTouchTarget不为null,当前控件拦截的话,就给子控件发送cancel事件并设置mFirstTouchTarget为null,后续事件询问自己是否处理。如果不拦截的话就讲事件交给子控件处理。
主要逻辑
down事件分发从父控件递归传给子控件,如果没有子控件处理(View默认onTouchEvent是会消费事件的,除TextView、ImageView等重写了onTouchEvent默认不消费外。所以子控件一般是会处理的),则询问自己是否处理,后续move事件不会再传给子控件。
所以如果控件都不做处理的话,事件一般是由最底层子控件消费了,如果想让父控件消费,一般就得父控件重写onIntercepterTouchEvent拦截事件。拦截后会向子控件发送cancel事件,后续事件就不会再传给子控件了。
滑动冲突解决方法
内部拦截法:
重写子控件的onIntercepterTouchEvent方法
action_down:调用禁止父容器拦截为true。
aciton_move:如果子控件要处理则返回true拦截。如果要父控件处理则调用禁止父控件拦截为false
重写父控件的onIntercepterTouchEvent方法
action_down:返回为false
action_move:返回为true
外部拦截法:
重写父布局的onIntercepterTouchEvent方法
action_down:返回为false,down事件不能拦截,拦截了子控件就收不到事件了。
action_move:如果需要拦截则返回为true,否则返回false。
action_up、action_cancel:返回为false,up事件不能拦截,拦截了子控件就收不到点击事件了。
滑动的方式
1、layout方法:毁掉onLayout方法了设置显示的位置。
2、offsetLeftAndRight与offsetTopAndBcottom:系统提供的一个上下左右移动的api
3、LayoutParams
4、scrollTo与scrollBy:移动的是View的内容,如果是ViewGroup,移动的是子View
5、Scroller:相比scrollTo与scrollBy,可以实现平滑的移动的。
分三步
第一步:初始化Scroller对象
第二步:调用scroller.startScroll(int startX, int startY, int dx, int dy, int duration),并调用invalidate()刷线View
第三部:重写View的computeScroll方法
调用scrollTo(mScroller.getCurrX(), mScroller.getCurrY())以及invalidate()重绘
6、属性动画:改变控件属性来滑动
7、ViewDragHelper:解决了android中手势处理过于复杂的问题,视图拖动助手。一般定义再ViewGroup里
分三步
第一步:初始化mDragHelper = ViewDragHelper.create(this, 1.0f, new DragHelperCallback());其中1.0f是灵敏度系数,系数越大越敏感。第一个参数为this,表示要拖动子View所在的Parent View
第二步:将触摸事件传递给ViewDragHelper
重写onIntercepterTouchEvent以及onTouchEvent,调用mDragHelper.shouldInterceptTouchEvent(event)以及mDragHelper.processTouchEvent(event);
第三步:再DragHelperCallback会掉方法里处理拖拽行为。
动画
Tween(补间)动画(View动画)
透明度、旋转、位移、缩放、动画集合
缺点不具备交互性
原理:每次绘制视图时,View所在的ViewGroup中的drawChild函数获取该View的Animation的Transformation值,然后调用canvas.concat(tanstormToApply.getMatrix()),通过矩阵完成动画,如果动画没有完成,就继续调用invalidate()函数。
自定义补间动画:继承Animation
标准的动画效果
补间动画常用于视图View的一些标准动画效果:平移、旋转、缩放 & 透明度;
特殊的应用场景
Activity
的切换效果
Fragement
的切换效果视图组(
ViewGroup
)中子元素的出场效果
属性动画:Animator
ValueAnimator
原理:类是先改变值,然后手动赋值给对象的属性从而实现动画;是间接对对象属性进行操作
ObjectAnimator
对象:不单单针对View,需要有set和get方法
继承自ValueAnimator
原理:类是先改变值,然后自动赋值给对象的属性从而实现动画;是直接对对象属性进行操作
自定义插值器:
补间动画 实现 Interpolator接口;属性动画实现TimeInterpolator接口。
实现float getInterpolation(float input)
根据动画的进度(0%-100%)计算出当前属性值改变的百分比。
自定义估值器:
继承TypeEvaluator,实现public T evaluate(float fraction, T startValue, T endValue)
根据 插值器计算出当前属性值改变的百分比 & 初始值 & 结束值 来计算 当前属性具体的数值
帧动动画