ViewGroup事件处理机制
在上一篇中实现了ViewPagerIndicator来绑定ViewPager动态转换小标题的功能,这一篇会通过事件的角度,去实现滑动ViewPager,转换切换不同的小标题,并在恰当的时候,拉出SlidingMenu。
Android系统中的每个ViewGroup的子类都具有下面三个和TouchEvent处理密切相关的方法:
1)public boolean dispatchTouchEvent(MotionEvent ev) 这个方法用来分发TouchEvent
2)public boolean onInterceptTouchEvent(MotionEvent ev) 这个方法用来拦截TouchEvent
3)public boolean onTouchEvent(MotionEvent ev) 这个方法用来处理TouchEvent
他们之间的关系如图:
首先触摸事件发生时(ACTION_DOWN),由系统调用Activity的dispatchTouchEvent方法,分发该事件。根据触摸事件的坐标,将此事件传递给out的dispatchTouchEvent处理,out则调用onInterceptTouchEvent 判断事件是由自己处理,还是继续分发给子View。此处由于out不处理Touch事件,故根据事件发生坐标,将事件传递给out的直接子View(即middle)。
Middle及Center中事件处理过程同上。但是由于Center组件是clickable 表示其能处理Touch事件,故center中的onInterceptTouchEvent方法将事件传递给center自己的onTouchEvent方法处理。至此,此Touch事件已被处理,不继续进行传递。
事件处理小结:
总之我们可以理解为父控件(out)包裹子空间(Middle)包裹孙子控件(center),在这个传递过程中一旦有一个调用了onTouchEvent,事件被处理,传递结束。在dispatchTouchEvent分派任务时,只要OnInterCeptTouchEvent不拦截,事件就会一直传递下去。
有了这个概念之后,我们可以总结出:
子控件处理事件角度
子控件不想让父控件和祖先控件拦截事件时,只需要在dispatchtouchEvent中通知父控件不需要拦截即可:
例如:
1 在一个继承了ViewPager类中可使用子控件请求父控件不拦截事件:
/*
* 事件分发,请求父控件以及祖先控件不阻拦事件
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
getParent().requestDisallowInterceptTouchEvent(true);
return super.dispatchTouchEvent(ev);
}
父控件处理事件角度
2 同样在一个继承了ViewPager的父 控件不想拦截子控件的事件:/*
* 设置为false,表示不拦截事件,事件会继续向下传递
*
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent arg0) {
// TODO Auto-generated method stub
return false;
}
通常在这种情况下,会将onTouchEvent设置返回false
首先触摸事件发生时(ACTION_DOWN),由系统调用Activity的dispatchTouchEvent方法,分发该事件。/*
* 重新ViewPager 使其不产生左右滑动事件
*/
@Override
public boolean onTouchEvent(MotionEvent arg0) {
// TODO Auto-generated method stub
return false;
}
/*
智慧北京实例:
在智慧北京的项目中,由于SlidingMenu和ViewPagerIndictor的使用,出现了多层的控件包裹关系,与其相关的ViewPager关系图:
其中ViewPagerIndicator与ViewPager2进行了绑定,通过填充Fragment嵌套在ViewPager1中,在应用中发现SlidingMenu是作为这些ViewPager的父控件存在的。
步骤 1:
1 整个项目进行编写时,是先添加了slidingmenu,再添加了ViewPager1 ,实现了如下左边的效果;
当向右滑动时,可以进行切换不同的ViewPager,但是向左滑动时经常将Slidingmenu画出。
分析:slidingmenu是一个父控件,当向右滑动时,拦截了ViewPager1的事件,因此从父控件的角度来讲,不去阻拦事件即可,但是这里需要更改SlidingMenu的源代码,一种更为简单的方式是使用RadionGroup进行监听,使用按钮切换不同的ViewPager界面,这时只需要在自定义一个ViewPager1,复写onTouchEvent即可。
@Override
public boolean onTouchEvent(MotionEvent arg0) {
// TODO Auto-generated method stub
return false;
}
步骤 2:
在添加了ViewpagerIndicator并与ViewPager2进行关联之后的效果如图:
问题 1
到这里的第一个问题就是无法滑动ViewPager2,因为父控件ViewPager1已经将onTouchEvent返回false,并且控件默认是阻拦事件下发的.
解决方法:
在自定义父控件ViewPager1中允许向子控件传递事件:
/*
* 设置为false,表示不拦截事件,事件会继续向下传递
*
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent arg0) {
// TODO Auto-generated method stub
return false;
}
问题 2
这个问题解决之后,可以发现可以通过滑动ViewPager2向右来切换IndicatorViewPager标签了,向左滑动时,同样会出现拉出Slidngmenu的情况:
解决方法:(更改Slingmenu源码太麻烦了)
更改IndicatorViewPager的源码,在ViewPagerIndicator的源码中复写dispatchTouchEvent,使子控件(IndicatorViewPager)请求父控件(slidingmenu)不拦截事件。
/*
* 事件分发,请求父控件以及祖先控件不拦截事件
*
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
getParent().requestDisallowInterceptTouchEvent(true);
return super.dispatchTouchEvent(ev);
}
对于ViewPager2 的结局方法方式相同,自定义个ViewPager,复写一下dispatchTouchEvent,然后把源码粘进去就行了。
ViewPager2与ViewpagerIndicator都可以理解为ViewPager
问题 3:
我们现在解决了不能滑动ViewPager2 以及向左滑动ViewPager2和ViewPagerIndicator出现SlidingMenu的BUG,
现在新的BUG是除非手动点击ViewPagerIndicator标签切换ViewPager2,否则无法自动切换到下一个页面。
解决方法:
监听ViewPager2滑动事件,在以下三种情况时,需要父控件进行拦截:
1 当滑动到第一个ViewPager时,如果此时依旧向左滑动,则跳转到上一个标签;
2 当滑动到当前ViewPager的最后一个画面时,如果还是向右滑动,则切换到下一个标签;
3 上下进行滑动
同样自定义一个ViewPager控件,复写dispatchTouchEvent:
public boolean dispatchTouchEvent(MotionEvent ev) {
// startX,startY;
int startx=0,starty=0;
int endx,endy;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: //检测到手指按下
getParent().requestDisallowInterceptTouchEvent(true); //子控件请求父控件不拦截事件
startx=(int) ev.getRawX();
starty=(int) ev.getRawY();
break;
case MotionEvent.ACTION_MOVE: //检测到手指移动
endx=(int) ev.getRawX();
endy=(int) ev.getRawY();
if(Math.abs(endx-startx) > Math.abs(endy-starty))//水平滑动
{
if(endx>startx) //向右滑动ViewPager
{
if(getCurrentItem()==0)//第一个页面,需要父控件进行拦截,
{
getParent().requestDisallowInterceptTouchEvent(false); //需要父控件拦截
}
}
else //左滑动ViewPager
{
if(getCurrentItem()==(getAdapter().getCount()-1))
{
getParent().requestDisallowInterceptTouchEvent(false); //需要父控件拦截
}
}
}
else//上下滑动
{
getParent().requestDisallowInterceptTouchEvent(false); //需要父控件拦截
}
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
}
搞明白的过程真的是好痛苦!!!