最近在准本找工作的同时,也在慢慢的看android开发艺术探索这本书感觉写的真是太好了,有空下来就做个笔记,下面进入正题:
在页面中要有内外两层勇士滑动,这个时候就会产生滑动冲突了,比如scrollerView嵌套listview这种情况。
场景一:外部滑动方向和内部滑动方向的不一致(eq: horizontalScrollview和listview的嵌套)
场景二:外部滑动方向和内部滑动方向一致(eq: verticalScrollView和listview的嵌套)
场景三:上面两种情况的嵌套
根据滑动时水平滑动还是竖直滑动来判断到底是由外部的view拦截触摸事件还是内部的view拦截触摸事件
如何判断水平滑动还是竖直滑动?
1.根据滑动路径和水平方向的夹角进行判断
2.根据水平方向和竖直方向的距离差进行判断
3.特殊时候可以根据水平和竖直方向的速度差来做判断
1.内部拦截法
2.外部拦截法
指点击事情都先经过父容器的拦截处理,如果父容器需要此事件则拦截,否则下发到子控件中。外部拦截法需要重写父容器的onInterceptTouchEvent方法,做相应的拦截即可
伪代码:
父容器:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercept=false;
int x= (int) ev.getX();
int y= (int) ev.getY();
switch (ev.getAction())
{
case MotionEvent.ACTION_DOWN:
intercept=false;
break;
case MotionEvent.ACTION_MOVE:
if (父容器需要当前点击事件)
{
intercept=true;
}else
{
intercept=false;
}
break;
case MotionEvent.ACTION_UP:
intercept=false;
break;
}
return intercept;
}
MotionEvent.ACTION_UP这里必须返回false,考虑一种情况,如果在父容器ACTION_UP返回了true,会导致子元素无法接受到ACTION_UP事件,那么子元素中的onClick事件就无法促发。
所有事件都传递给子元素,如果子元素需要此事件则就消耗,否则交由给父容器进行处理,这种方法和android中的事件分发不一样,需要配合requestDisallowInterceptTouchEvent(boolean)进行处理,相对于外部拦截法来说,稍显复杂,我们需要重写子元素的dispatchTouchEvent():
伪代码:
子元素:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int x= (int) ev.getX();
int y= (int) ev.getY();
switch (ev.getAction())
{
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
int deltaX=x-mLastX;
int deltaY=x-mLastY;
if (父容器需要当前点击事件)
{
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
}
mLastX=x;
mLastY=y;
return super.dispatchTouchEvent(ev);
}
}
除了子元素需要修改以外,父容器也要默认拦截除了ACTION_DOWN以外的其他事件,这样当子元素调用getParent().requestDisallowInterceptTouchEvent(false)方法时候,父元素才能继续拦截所需的事件。
为什么父容器不能拦截ACTION_DOWN事件呢,因为ACTION_DOWN事件不受FLAG_DISALLOW_INTERCEPT这个标记位控制,所以一旦拦截了ACTION_DOWN事件,则子元素根本接收不到传递的事件,因为父容器把他拦截了。
父容器:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action=ev.getAction();
if (action==MotionEvent.ACTION_DOWN)
{
return false;
}else
{
return true;
}
}
这里详细讲一下requestDisallowInterceptTouchEvent(boolean)这个方法:
源码:
View源码
/**
* Called when a child does not want this parent and its ancestors to
* intercept touch events with
* {@link ViewGroup#onInterceptTouchEvent(MotionEvent)}.
*
* This parent should pass this call onto its parents. This parent must obey
* this request for the duration of the touch (that is, only clear the flag
* after this parent has received an up or a cancel.
*
* @param disallowIntercept True if the child does not want the parent to
* intercept touch events.
*/
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept);
我们看到里面写着,如果传入进去的值为true,说明孩子元素不想让父容器去拦截触摸事件,这是一个对外的公开方法。
我们再到Viewgroup里面查看,因为一般而言顶级View都是一个ViewGroup,我们查看Viewgroup的dispatchTouchEvent,可以在里面找到如下的源码:
// Check for interception.
final boolean intercepted;
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;
}
/**
* {@inheritDoc}
*/
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
我们可以清楚地看到里面的逻辑:
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
说明这个标志确实是如我们上述形容的作用,决定父容器是否拦截触摸事件。