Android 自定义view
Android自定义view有如下步骤:
- 创建view(View子类)
处理view的布局
- 绘制view
与用户进行交互
android view视图层次
view的事件分发机制(与用户进行交互):
MotionEvent
事件分发主要传递的其实就是一系列MotionEvent,看一下他的主要方法:
getX()/getY()
获取该触摸点相对于当前view的左上角坐标getRawX()/getRawY()
获取触摸点相对于整个手机屏幕的坐标getAction()
获取事件类型这里注意一下,其实,Android里有两套坐标系:
Android坐标系:以屏幕左上角为顶点
getRawY()
,getRawX()
view坐标系 :以view左上角为起点,
getX()
,getY()
其实他是一个概念,更好的帮助我们开发
事件分发就是将一个MotionEvent传递给一个具体的view。
事件传递的主要过程:
activity—>
window—->
viewgroup(view)
事件分发三大方法:
boolean dispatchTouchEvent(MotionEvent ev)
事件分发的主要方法
如果事件能够传递给当前view,那么他的dispatchTouchEvent方法一定会被调用
伪代码描述:
1
2
3
4
5
6
7
8
9
10
11public boolean onDispatchTouchEvent(MotionEvent ev){
boolean result=false;
if(onInterceptTouchEvent(ev)){
//也就是调用了view的方法,viewgroup继承自view
result=onTouchEvent;
}else{
result=child.dispatchTouchEvent(ev)
}
return result;
}优先级顺序
(view)的dispatchTouchEvent—>
onTouch()—->
onTouchEvent()—–>
onclick()
boolean onInterceptTouchEvent(MotionEvent ev)
表示是否拦截此次事件 ,默认返回falseboolean onTouchEvent(MotionEvent ev)
处理触摸事件的主要方法,(如果设置有onTouchListener设置了onTouch,则会调用onTouch,且返回true则不会调用onTouchEvent),里面调用了onClick,onLongClick,(且只要LONG_CLICKABLE或者CLICKABLE这两个任意一个为true,就会返回true),即便是不可用状态
dispatchTouchEvent主要流程
(一次viewGroup到view的传递过程,其他的也是这样,递归着来):
- (viewGroup)判断是否拦截,如果拦截 (onterceptTouchEvent返回true),则接下来的事件序列都有其来处理(调用他的onTouchEvent),否则进行下一步
- 遍历viewGroup里所有的子view(viewGroup),找到符合的view(在点击坐标是否在view内,且view是否在播放动画),如果找到,则调用他的dispatch方法,即完成一次传递(通过
dispatchTransformedTouchEvent()
) - 如果遍历所有子元素后事件都没有被处理,则包含两种情况:
- viewgroup没有合适子元素
- 子元素处理了点击事件,但dispatchTouchEvent返回了false(一般是onTouchEvent返回了false)
则继续交由该viewgroup处理
总结一下:
- 一但viewGroup拦截了这个事件,则这一整个序列都将交由他来处理,并且不会再次调用onInterceptTouchEvent()方法。所以如果viewGroup想要处理事件,就一是拦截了事件,二是没有找到合适的子view,从而转去调用它自己的onTouchEvent。
- view一旦开始处理事件,如果他不消耗ACTION_DOWN,则其他事件也不会交由他处理,
view如果不消耗除了ACTION_DOWN以外的其他事件,则这事件会消失,最后将传到activity处理 - 注意onclick的调用时机是view接收到了ACTION_DOWN和ACTION_UP这两个事件。
- onInterceptTouchEvent的调用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// ViewGroup
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
- 也就是说如果被拦截,那么mfirstTouchTarget就为null,则跳出去,就不会再次执行onInterceptTouchEvent,所以一定注意这个方法只会执行一次。
disallowIntercept 而且从上面一段源码也可看到有一个特殊的标记量
disallowIntercept
,这个变量可以由requestDisallowInterceptTouchEvent()
设置,他可以不允许viewgroup拦截事件,所以,如果disallowIntercept==true
,就直接跳过,不执行 onInterceptTouchEvent(ev),他的作用是可以由子view去调用getParent().requestDisallowInterceptTouchEvent(true);
从而处理滑动冲突总的来说,view的事件传递就是利用了责任链设计模式,从activity开始忘viewGroup,再往下级view传递,(
从DOWN开始
,down事件确定了该view是否要消费他)如果能执行,能消费,则该view返回true,表示交由他来处理,并且停止向下传递;而如果返回了false则交由上级view来处理,而上级viewgroup可以拦截下事件,从而将接下来的事件交由他来处理。