View的事件分发
public static dispatchTouchEvent(MotionEvent e){
boolean consume = false;
if(onInterceptTouchEvent(e)) {
consume = onTouchEvent(e);
} else {
consume = child.dispatchTouchEvent(e);
}
return consume;
}
- dispatchTouchEvent:是否消耗该事件。由当前View的onTouchEvent和下级View的dispatchTouchEvent共同决定
- onInterceptTouchEvent:是否拦截该事件。外部拦截法便是通过重写父容器的onInterceptTouchEvent,返回true拦截事件。被拦截后,同一个事件序列中这个View的onInterceptTouchEvent不会再被调用
- onTouchEvent:处理事件并返回是否消耗。处理事件优先级onTouch > onTouchEvent > onClick。若View不消耗ACTION_DOWN事件,即返回false,则同一事件序列的其他事件如UP,MOVE等重新交给父容器的onTouchEvent处理。
View的滑动
- 使用ScrollerTo/by:只能改变View内容位置,无法改变View在布局中的位置
- 使用动画:View动画只是操作影像,属性动画则可以真正改变布局位置属性,可以应对需要处理交互的场景。
- 改变LayoutParams:比如设置marginLeft等,实现较麻烦但也可以满足需要交互的场景。
View的弹性滑动
- Scroller:View在onDraw方法中调用computeScroller(我们在里面实现弹性滑动代码),它会通过Scroller对象获取当前的ScrollX、ScrollY,通过时间流逝计算新的滑动距离,和是否滑动停止,若还未停止,在通过scrollTo实现滑动,调用postInvalidate进行第二次重绘,如此反复。
- 使用动画:属性动画ValueAnimator
- 使用延时策略:通过Scrollto+Handler,或View的postDelayed
滑动冲突解决
- 外部拦截法:重写父容器的onInterceptTouchEvent方法,写入拦截逻辑,注意ACTION_DOWN和ACTION_UP都要返回false,拦截应该在ACTION_MOVE中设置。
- 内部拦截法:子容器需要事件则消耗,不交给父容器处理。步骤如下:
首先,重写子元素的dispatchTouchEvent
ACTION_DOWN: parent.requestDisallowInterceptionTouchEvent(true) //不拦截ACTION_DOWN事件
ACTION_MOVE:if(有拦截父容器业务需求) {
parent.requestDisallowInterceptionTouchEvent(false)
}
return super.dispatchTouchEvent(e);
其次,修改父容器的onInterceptTouchEvent
ACTION_DOWN: return false; //确保ACTION_DOWN不拦截,其他的事件则都要拦截
else return true;