绘制View的相关知识点:让绘图更加炫酷的Paint、让View动起来的动画、与用户交互的触控事件
一:事件分发机制原理(责任链模式,事件层层传递,直到被消费)
事件分为:分发、拦截、消费
如果分发下的事件没有任何View消费,那么会反向回传,最终传给Activity,最终Activity也没有处理,本事件才会被抛弃
回传都是直接在询问onTouch是否执行,false回传上去
注意:
1. 事件被消费 -- 事件信息停止传递
2. 事件一直没有被消费,最后传给activity,activity不需要则抛弃
3. 判断事件是否被消费是根据返回值,而不是根据你是否使用了事件
二:事件分发机制详解
1. 常见事件(事件被封装在MotionEvent对象中):
ACTION_DOWN:手指初次接触屏幕时触发
ACTION_MOVE:手指在屏幕上滑动时触发(会多次触发)
ACTION_UP:手指离开屏幕时触发
ACTION_CANCEL:事件被上层拦截时触发
2. 相关方法:
dispatchTouchEvent:事件分发机制中的核心(View存在分发机制,是用来管理单击、长按、触摸灯相关事件)
View调用顺序:
onTouchListener - onTouchEvent - onLongClickListener - onClickListener
返回ture,事件被消费,false不消费,与是否使用了事件无关
不论View是否注册点击事件,只要View是可点击的就会消费事件
ViewGroup的分发流程:
判断自身是否需要(询问onInterceptTouchEvent是否拦截),需要调用onTouchEvent
不需要/不确定:询问childView(手机当前触摸的点所在的view)
childView不需要则调用自身的onTouchEvent
注意:为了保证所有事件被同一个View消费,View只有消费了ACTION_DOWN事件,才能接收到后续事件(除非被上层View拦截,此时会收到ACTION_CANCEL,表示当前事件已结束,后续事件不会再传递)
要点:
1. 分发原理:责任链模式,事件层层传递,直到被消费
2. View的dispatchTouchEvent主要用于调度自身的监听器和onTouchEvent
3. View事件调度顺序:onTouchListener - onTouchEvent - oLongClickListener - onClickListener
4. 不论View是否注册点击事件,只要是可点击的就会消费事件
5. 事件是否被消费由返回值决定,true 表示消费,false 表示不消费,与是否使用了事件无关。
6. ViewGroup 中可能有多个 ChildView 时,将事件分配给包含点击位置的 ChildView。
7. ViewGroup 和 ChildView 同时注册了事件监听器(onClick等),由 ChildView 消费。
8. 一次触摸流程中产生事件应被同一 View 消费,全部接收或者全部拒绝。
9. 只要接受 ACTION_DOWN 就意味着接受所有的事件,拒绝 ACTION_DOWN 则不会收到后续内容。
10. 如果当前正在处理的事件被上层 View 拦截,会收到一个 ACTION_CANCEL,后续事件不会再传递过来
三:MotionEvent
1. 单点触控
事件:初次接触屏幕 - ACTION_DOWN
在屏幕上滑动(执行多次) - ACTION_MOVE
离开屏幕 - ACTION_UP
被上层拦截 - ACTION_CANCEL
不在控件区域 - ACTION_OUTSIDE
方法: 获取事件类型 - getAction()
获取触摸点在当前View的X坐标 - getX()
获取触摸点在当前View的Y坐标 - getY()
获取触摸点在整个屏幕的X坐标 - getRawX()
获取触摸点在整个屏幕的Y坐标 - getRawY()
ACTION_CANCEL:正常被上层V皆为拦截后,当前View是收不到任何事件的
触发条件:上层View回收事件处理权的时候,当前View会收到Cancel事件
例:listView滚动事件,item收到down事件,但是listView收到滚动事件,所以收回事件处理权,此时item会收到cancel
ACTION_OUTSIDE:设置视图WindowManager布局参数的flag为FLAG_WATCH_OUTSIDE_TOUCH,如此会接收到该事件
2. 多点触控
注意:
* 多点触控必须使用getActionMasked获取事件类型
* 点击触控,由于事件数值不变所以,使用getAction和getActionMasked都可以
* 使用getActionIndex可以获取到index数值,不过只有在down和up时有效,move无效
* pointId是唯一不变的标识,在按下时产生,在抬起或 取消时消失
获取事件类型:
取得事件索引:
** index和pointId变化规则:
index --- * 从0开始自动增长
* 如果之前落下的手指抬起,后面手指的index会随之减小
* 对move事件无效
pointId --- pointId是不变的,始终为第一次落下时生成的数值,不会收其他手指抬起和落下的影响
** move相关事件
pointerIndex和pointId
3. 历史数据处理:
注意:
* pin是指pointerIndex,表示第几根手指
* 历史数据只有ACTION_MOVE
* 历史数据单点触控和多点触控均可以用
4. 获取事件发生的时间
* pos表示历史数据中第几个数据
* 返回值类型为long,单位是毫秒
5. 获取压力(接触面积大小)
* 获取接触面积大小和获取压力大小时需要硬件支持的
* 大多数电容屏不支持压力测试,但可以大致测试出接触面积
* 大部分设备的getPressure是使用接触面积来模拟的
* 有些设备不支持该方法(系统版本和硬件问题导致的)
6. 鼠标事件
* 4.0以上才添加的
* 使用geyActionMasked获取事件类型
* 事件不会传递到onTouchEvent,而是会传递到onGenericMotionEvent
7. 输入设备类型判断
使用getToolType(int pointerIndex)获取对应输入类型,可以为0,但必须小于getPointCount
四:特殊控件事件处理方法
1. Region区域检测(是一个封闭区域,可以通过contains判断是否在该区域)
2. Matrix的坐标映射
五:手势检测GestureDetector
1. 使用:
2. 构造函数:
handler为了给GestureDetector提供一个looper(如果是在主线程中,不存在该问题,因为它内部自动创建handler用于处理数据; 如果是在没有创建looper的子线程中,则需要传递handler,否则无法获取到looper导致创建失败)
3. 手势监听器
3.1 OnContextClickListener:用于检测外部设备按钮
注意:如果侦听 onContextClick(MotionEvent),则必须在 View 的 onGenericMotionEvent(MotionEvent)中调用 GestureDetector 的 OnGenericMotionEvent(MotionEvent)
3.2 OnDoubleTapListener:检测双击
回调接口:onDoubleTap - 只是监听双击事件
onSingleTapConfirmed - 同时监听单击事件(发生300ms后才触发)
因为单击事件不是立即发生,所以在执行点击操作后单击监听和双击监听只会有一个被触发,不会出现冲突
onDoubleTapEvent - 双击事件发生时会对第二次按下产生的MationEvent信息进行回调
如果需要细微的控制双击事件,使用onDoubleTagEvent更好
3.3 OnGestureListener(检测以下事件类型)
按下Down(View能够接收到事件: view设置为可点击,因为可点击状态会默认消费down事件、手动消费down事件) onFling(手指抬起后还会滚动一段时间才会停止,参数:e1 - 按下event、e2 - 抬起event、velocityX - X上运动速度像素/秒、velocityY - Y轴上运动速度像素/秒)
onLongPress(检测长按事件)
onScroll监听滚动事件(后俩个参数distanceX和distanceY是指滚动距离)
onShowPress按下时的一种回调(作用:给用户提供视觉反馈),延时180ms回调
onSingTagUp用户单击提起时回调(双击第一次抬起时触发)
3.4 SimpleOnGestureListener:对以上三种监听的空实现(OnGestureListener 创建会出现无用的空实现,所以一般是会用改监听)
4. 相关方法
六:缩放手势检测ScaleGestureDetector
1. 构造方法和GestureDetector 类似
2. 手势监听器
方法:
3. 原理:计算缩放中心点 - 所有坐标加起来/数量
计算缩放比例 - 各个手指到焦点的平均距离(新的平均距离 - 旧的平均距离)
移动距离超过一定书之后,会触发onScaleBegin方法,如果在onScaleBegin方法里范湖里true,表示接收事件后,就会重置缩放相关数值,并且开始积累缩放因子
七:画笔基础
1. 基础
内部类:Paint.Cap - 描边线和路径的开始和结束显示效果三种效果叠加(butt中间线区域,round圆形线区域,square红色和绿色组成部分边是直角)
Paint.Join - 线条和曲线段在描边路径上连接处理
Paint.Style - 绘制的图元是否被填充,描边或者俩者均有
常量:ANIT_ALIAS_FLAG - 开启矿锯齿功能
DITHER_FLAG - 绘制时启用抖动的标志
FILTER_BITMAP_FLAG - 绘制标志,在缩放的位图上启用双线性采样
构造方法:Paint() - 默认设置创建一个新画笔
Paint(int flag) - 创建新画笔并提供特殊设置通过flag参数
Paint(Paint paint) - 使用指定画笔参数初始化新画笔
公开方法:获取和设置画笔相关的一些设置:getFlags/setFlags(会覆盖之前设置的内容)
复制画笔设置和回复默认设置:set(Paint src)/reset()
获取和设置alpha值:getAlpha()/setAlpha(int a)
获取和设置画笔颜色:getColor()/setColor(int color)
设置带透明通道颜色:setARGB(int a,int r,int g,int b)
获取和设置描边宽度:getStrokeWidth()/setStrokeWidth(float width)
获取(控制如何处理描边线和路径的开始和结束)和设置cap:getStrokeCap()/setStrokeCap(Paint.Cap cap)
获取和设置画笔连接方式:getStrokeMiter()/setStrokeMiter(float miter)
获取和设置画笔的patheffect:getPathEffect()/setPathEffect(PathEffect effect)
将不同效果应用于src,并将结果返回到dst:getFillPath(Path src,Path dst)
2. 画笔设置
创建方式:
画笔颜色:和
画笔宽度:(宽度向俩边延伸
,值为0,画布如何放大值不变,值为1,会随画布而增大)
画笔模式:
画笔线帽:包含以下三种:
注意:
线段连接方式(拐角类型):包含三种方式
衔接模式长度限制
线段连接方式默认是 MITER,即在拐角处延长外边缘,直到相交位置
为了避免这种情况,如果连接模式为 MITER(尖角),当连接角度小于一定程度时会自动将连接模式转换为 BEVEL(平角)
mater是对长度的限制,它可以通过这个公司计算:miter = 1/sin(angle/2),angle是俩条线夹角,miter默认是4,必须大于0才有效
3. CornerPathEffect:可以将线段之间的任何锐角替换为指定半径的圆角(适用于STROKE或FILL样式,半径越大越平滑)
4. 实现虚线效果DashPathEffect(适用于STROKE或FILL_AND_STROKE)
参数:float intervals[] - 显示长度和隐藏长度
float phase - 相位
注意:intervals[] 中是允许设置多组数据的,每两个为一组,第一个表示显示长度,第二个表示隐藏长度