在android界面开发中,经常可以遇到一些多层控件嵌套的情况,如果父子控件都有对应的手势操作(如scrollview中嵌套pageview),那么他们都手势操作就有可能相互干扰,影响界面的流畅性和体验。
首先,要谈一下android父子控件之间事件的分发,对于事件的分发有几个原则需要了解。
(1) android事件分发是从父控件向子控件逐级分发传递的。
(2) 每一层控件都可能消费这个事件,消费后不再向下传递(这也是父子控件对于手势操作冲突的主要原因)。
(3) android系统中的每个ViewGroup的子类都具有下面三个和事件分发处理密切相关的方法:
1.public boolean
dispatchTouchEvent(MotionEvent ev)
这个方法用来分发TouchEvent
2.public boolean
onInterceptTouchEvent(MotionEvent
ev)
这个方法用来拦截TouchEvent
3.public boolean
onTouchEvent(MotionEvent ev)
这个方法用来处理TouchEvent
(4)
android的触摸是由一个ACTION_DOWN(按下),多个ACTION_MOVE(移动),一个ACTION_UP(抬起)组成。
父子控件间的事件传递,用一张图可以更好的体现:
1
(1)、distachTouchEvent用于分发事件,true直接消费事件并不在分发,false向intercertTouchEvent分发
(2)、internceptTouchEvent用于拦截事件,true直接分发事件至自己view的onTouchEvent方法经行处理,false继续分发
到子view的dispathcTouchEvent。
(3)、onTouchEevent作为事件处理的部分,响应由多个touchevent所组成的手势。
下面我们用这些内容来处理一个具体的问题,在竖直滑动的scrollview中嵌套水平滑动的viewpager的问题。viewpager中嵌套listview也可以用类似的方法解决。
由于scrollview处于布局的外层,来自系统的触摸事件会先传递至外层的scrollview,scrolliew对于事件的消将直接影响事件继续分发至内层的viewpager。
先来看一下scrollview的distachTouchEvent源码:
- @Override
- 361 public boolean More ...onInterceptTouchEvent(MotionEvent ev) {
- 362
-
-
-
-
- 367
- 368
-
-
-
-
- 373 final int action = ev.getAction();
- 374 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
- 375 return true;
- 376 }
- 377
- 378 if (!canScroll()) {
- 379 mIsBeingDragged = false;
- 380 return false;
- 381 }
- 382
- 383 final float y = ev.getY();
- 384
- 385 switch (action) {
- 386 case MotionEvent.ACTION_MOVE:
- 387
-
-
-
- 391
- 392
-
-
-
- 396 final int yDiff = (int) Math.abs(y - mLastMotionY);
- 397 if (yDiff > mTouchSlop) {
- 398 mIsBeingDragged = true;
- 399 }
- 400 break;
- 401
- 402 case MotionEvent.ACTION_DOWN:
- 403
- 404 mLastMotionY = y;
- 405
- 406
-
-
-
-
- 411 mIsBeingDragged = !mScroller.isFinished();
- 412 break;
- 413
- 414 case MotionEvent.ACTION_CANCEL:
- 415 case MotionEvent.ACTION_UP:
- 416
- 417 mIsBeingDragged = false;
- 418 break;
- 419 }
- 420
- 421
-
-
-
- 425 return mIsBeingDragged;
- 426 }
- 427
397-399可以看到,在scrollview中,当其捕捉到手指上下滑动的距离大于mTouchSlop时,onInterceptTouchEvent方法返回true,拦截事件至ontouchEvent中并消费,内层的viewpager将得不到触摸事件的传递。
虽然onscroll对事件是否拦截有一定的处理,但是有时仅仅这种效果不能满足需要。这种处理策略意味着,我们水平滑动viewpager时,必须保持上下滑动的距离小于mTouchSlop,否则,触摸事件会被上层的scrollview直接消费。为了解决这种冲突,个人想法是上层的scrollview不仅要限制竖直方向的TouchSlop,同时要限制水平方向的TouchSlop,避免和viewpager的冲突。这样需要我们重写scrollview的onInterceptTouchEvent()方法。
具体的实现方法如下:
- <span style="font-size: 18px;">public class MScrollView extends ScrollView {
- private float FistXLocation;
- private float FistYlocation;
- private boolean Istrigger = false;
- public Animation animationUp;
- public Animation animationDown;
- </span> private final int TRIGER_LENTH = 50;
- private final int HORIZOTAL_LENTH = 20;<span style="font-size: 18px;">
- private TMHDItemsGridsAvtivity faAvtivity;
- public TMHDScrollView(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
-
-
-
- int deltaX = 0;
- int deltaY = 0;
-
- final float x = ev.getX();
- final float y = ev.getY();
-
- switch (ev.getAction()) {
- case MotionEvent.ACTION_MOVE:
- deltaX = (int)(FistXLocation - x);
- deltaY = (int)(FistYlocation - y);
- if (Math.abs(deltaY) > TRIGER_LENTH
- && Math.abs(deltaX) < HORIZOTAL_LENTH) {
-
- Istrigger = true;
- return super.onInterceptTouchEvent(ev);
-
- }
-
- return false;
-
- case MotionEvent.ACTION_DOWN:
- FistXLocation = x;
- FistYlocation = y;
- if(getScaleY()<-400){
- System.out.println(getScaleY());
- }
- requestDisallowInterceptTouchEvent(false);
- return super.onInterceptTouchEvent(ev);
-
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- if (Istrigger) {
-
- Istrigger = false;
- return super.onInterceptTouchEvent(ev);
- }
-
- break;
- }
- return super.onInterceptTouchEvent(ev);
-
- }
- }</span>
下面这部分就是自己定义的水平和垂直的事件拦截触发条件。
- private final int TRIGER_LENTH = 50;
- private final int HORIZOTAL_LENTH = 20;
重写的MScrollView,自定义了onInterceptTouchEvent()方法,从而自定义了拦截的条件。解决了父子两个控件之间相互冲突的问题。
控件手势冲突
原文:http://www.cnblogs.com/starblogs/p/3557297.html