android父子控件手势冲突的解决

本文介绍了Android中父子控件事件分发的原理,包括dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent的作用。重点讨论了ScrollView和ViewPager的冲突问题,当ScrollView拦截了事件导致ViewPager无法正常工作。解决方案是通过重写ScrollView的onInterceptTouchEvent方法,自定义水平和垂直的事件拦截条件,从而避免手势冲突。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在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         /*
363          * This method JUST determines whether we want to intercept the motion.
364          * If we return true, onMotionEvent will be called and we do the actual
365          * scrolling there.
366          */
367 
368         /*
369         * Shortcut the most recurring case: the user is in the dragging
370         * state and he is moving his finger.  We want to intercept this
371         * motion.
372         */
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                 /*
388                  * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
389                  * whether the user has moved far enough from his original down touch.
390                  */
391 
392                 /*
393                 * Locally do absolute value. mLastMotionY is set to the y value
394                 * of the down event.
395                 */
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                 /* Remember location of down touch */
404                 mLastMotionY = y;
405 
406                 /*
407                 * If being flinged and user touches the screen, initiate drag;
408                 * otherwise don't.  mScroller.isFinished should be false when
409                 * being flinged.
410                 */
411                 mIsBeingDragged = !mScroller.isFinished();
412                 break;
413 
414             case MotionEvent.ACTION_CANCEL:
415             case MotionEvent.ACTION_UP:
416                 /* Release the drag */
417                 mIsBeingDragged = false;
418                 break;
419         }
420 
421         /*
422         * The only time we want to intercept motion events is if we are in the
423         * drag mode.
424         */
425         return mIsBeingDragged;
426     }
427 

  397-399可以看到,在scrollview中,当其捕捉到手指上下滑动的距离大于mTouchSlop时,onInterceptTouchEvent方法返回true,拦截事件至ontouchEvent中并消费,内层的viewpager将得不到触摸事件的传递。

    虽然onscroll对事件是否拦截有一定的处理,但是有时仅仅这种效果不能满足需要。这种处理策略意味着,我们水平动viewpager时,必须保持上下滑动的距离小于mTouchSlop,否则,触摸事件会被上层的scrollview直接消费。为了解决这种冲突,个人想法是上层的scrollview不仅要限制竖直方向的TouchSlop,同时要限制水平方向的TouchSlop,避免viewpager的冲突。这样需要我们重写scrollview的onInterceptTouchEvent()方法。

具体的实现方法如下:

public class MScrollView extends ScrollView {
	private float FistXLocation;
	private float FistYlocation;
	private boolean Istrigger = false;
	public Animation animationUp;
	public Animation animationDown;
	   private final int TRIGER_LENTH = 50;
	   private final int HORIZOTAL_LENTH = 20;
	private TMHDItemsGridsAvtivity faAvtivity;
	public TMHDScrollView(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
	}

	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		// TODO Auto-generated method stub
		
		
		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);
			//拦截这个手势剩下的部分  ,使他不会响应viewpager的相关手势
			}

			return false;//没有触发拦截条件,不拦截事件,继续分发至viewpager

		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);
		
	}
}


下面这部分就是自己定义的水平和垂直的事件拦截触发条件。

	private final int TRIGER_LENTH = 50;
	private final int HORIZOTAL_LENTH = 20;

重写的MScrollView,自定义了onInterceptTouchEvent()方法,从而自定义了拦截的条件。解决了父子两个控件之间相互冲突的问题。











评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值