android中view手势滑动冲突的解决方法

所属分类: 软件编程 / Android 阅读数: 39
收藏 0 赞 0 分享

Android手势事件的冲突跟点击事件的分发过程息息相关,由三个重要的方法来共同完成,分别是:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent。

public boolean dispatchTouchEvent(MotionEvent ev)

这个方法用来进行事件的分发。如果事件传递到view,那么这个方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是否消耗当前事件。

   

 public boolean onInterceptTouchEvent(MotionEvent event)

在上述方法内部调用,用来判断是拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件。

 public boolean onTouchEvent(MotionEvent event)

 在dispathcTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接到事件。

例:

  public boolean dispatchTouchEvent(MotionEvent ev){

       boolean consume = false;

      if(onInterceptTouchEvent(ev)){

          consume = onTouchEvent(ev);

       }  else {

         consum = child.dispathcTouchEvent(ev);

      }

    return consume;

   }

手势冲突的解决方法就是用上面的三个方法;主要分为两种解决方法:·1外部拦截法 2内部拦截法

1.常见的滑动冲突场景

1.1 外部滑动方向和内部滑动的方向不一致

这种情况我们经常遇见,比如使用viewpaper+listview时,在这种效果中,可以通过左右滑动切换页面,而每一个页面往往又是一个listview,本来在这种情况下是有冲突的,但是Viewpaper内部处理了这个滑动冲突,因此采用viewpaper我们无需关注这个问题,如果我们采用的不是Viewpaper而是ScrollView等,那么必须手动处理滑动冲突,否则内外两层只能有一层滑动,那就是滑动冲突。另外内部左右滑动,外部上下滑动也同样属于该类。

1.2 外部滑动方向和内部滑动方向一致

这种情况就比较复杂,当内外两层都在同一个方向可以滑动的时候,显然存在逻辑问题,因为当手指开始滑动的时候,系统无法知道用户到底是想让那一层动,所以当手指滑动的时候就会出现问题,要么只能一层动,要么内外两成动的都很卡顿。

2.给出解决方案

2.1 外部拦截法

针对场景1,我们可以发现外部和内部的滑动方向不一样也就是说只要判断当前dy和dx的大小,如果dy>dx,那么当前就是竖直滑动,否则就是水平滑动。明确了这个我就就可以根据当前的手势开始拦截了。


从上一节中我们分析了view的事件分发,我们知道点击事件的分发顺序是 通过父布局分发,如果父布局没有拦截,即onInterceptTouchEvent返回false,才会传递给子View。所以我们就可以利用onInterceptTouchEvent()这个方法来进行事件的拦截。来看一下代码:

 public boolean onInterceptTouchEvent(MotionEvent event) {
    boolean intercepted = false;
    int x = (int) event.getX();
    int y = (int) event.getY();

    switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN: {
      intercepted = false;
      break;
    }
    case MotionEvent.ACTION_MOVE: {
      if(父容器拦截的规则){
        intercepted=true;
      }else{
        intercepted=false;
      }
      break;
    }
    case MotionEvent.ACTION_UP: {
      intercepted = false;
      break;
    }
    default:
      break;
    }
    mLastXIntercept=x;
    mLastYIntercept=y;
    return intercepted;
  }

上面的代码差多就是外部拦截的通用模板了,在onInterceptTouchEvent方法中,

首先是ACTION_DOWN这个事件,父容器必须返回false,即不拦截事件,因为一旦父容器拦截了ACTION_DOWN这个事件,那么后续的ACTION_MOVE和ACTION_UP事件将直接交给父容器处理,这个时候事件没法继续传递给子元素了;

然后是ACTION_MOVE这个事件,这个事件可以根据需要决定是否拦截,如果父容器需要拦截就返回true,否则返回false;

最后是ACTION_UP这个事件,这里必须返回false,因为这个事件本身也没有太多意义。

下面我们来具体做一下拦截的操作,我们需要在水平滑动的时候父容器拦截事件。

public boolean onInterceptTouchEvent(MotionEvent event) {
    boolean intercepted = false;
    int x = (int) event.getX();
    int y = (int) event.getY();

    switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN: {
      intercepted = false;
      break;
    }
    case MotionEvent.ACTION_MOVE: {
      int deltaX=x-mLastXIntercept;
      int deltaY=y=mLastYIntercept;
      if(Math.abs(deltaX)>Math.abs(deltaY)){
        intercepted=true;
      }else{
        intercepted=false;
      }
      break;
    }
    case MotionEvent.ACTION_UP: {
      intercepted = false;
      break;
    }
    default:
      break;
    }
    mLastXIntercept=x;
    mLastYIntercept=y;
    return intercepted;
  }

 从上面的代码来看,我们只是修改了一下拦截条件而已,所以说外部拦截还是很简单方便的。在滑动的过程中,当水平方向的距离大时就判定水平滑动。

还是一贯我们做实验来证明理论的风格,我们来自定义一个HorizontalScrollView来体现一下用外部拦截法解决冲突的快感。

先上一下代码:

package com.gxl.viewtest;

import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.text.LoginFilter;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;

/**
 * s
 * Created by GXL on 2016/7/25 0025.
 */
public class HorizontalScrollView extends ViewGroup {

  private final String TAG = "HorizontalScrollView";
  private VelocityTracker mVelocityTracker;
  private Scroller mScroller;
  private int mChildrenSize;
  private int mChildWidth;
  private int mChildIndex;
  //上次滑动的坐标
  private int mLastX = 0;
  private int mLastY = 0;
  //上次上次拦截滑动的坐标
  private int mLastXIntercept = 0;
  private int mLastYIntercept = 0;

  public HorizontalScrollView(Context context) {
    super(context);
    init(context);
  }

  public HorizontalScrollView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init(context);
  }

  public HorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init(context);
  }

  public void init(Context context) {
    mVelocityTracker = VelocityTracker.obtain();
    mScroller = new Scroller(context);
  }

  public boolean onInterceptTouchEvent(MotionEvent event) {
    boolean intercepted = false;
    int x = (int) event.getX();
    int y = (int) event.getY();
    switch (event.getAction()) {
      case MotionEvent.ACTION_DOWN: {
        intercepted = false;
        break;
      }
      case MotionEvent.ACTION_MOVE: {
        int deltaX = x - mLastXIntercept;
        int deltaY = y - mLastYIntercept;
        if (Math.abs(deltaX) > Math.abs(deltaY)) {
          intercepted = true;
        } else {
          intercepted = false;
        }
        break;
      }
      case MotionEvent.ACTION_UP: {
        intercepted = false;
        break;
      }
      default:
        break;
    }
    mLastX = x;
    mLastY = y;
    mLastXIntercept = x;
    mLastYIntercept = y;
    return intercepted;
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    mVelocityTracker.addMovement(event);
    int x = (int) event.getX();
    int y = (int) event.getY();
    switch (event.getAction()) {
      case MotionEvent.ACTION_DOWN:
        break;
      case MotionEvent.ACTION_MOVE:
        int deltaX = x - mLastX;
        if((getScrollX()-deltaX)>=0&&(getScrollX()-deltaX)<=(getMeasuredWidth()-ScreenUtils.getScreenWidth(getContext()))) {
          scrollBy(-deltaX, 0);
        }
        break;
      case MotionEvent.ACTION_UP:
        mVelocityTracker.computeCurrentVelocity(1000);
        float xVelocityTracker = mVelocityTracker.getXVelocity();
        if (Math.abs(xVelocityTracker) > 50) {
          if (xVelocityTracker > 0) {
            Log.i(TAG, "快速向右划");
          } else {
            Log.i(TAG, "快速向左划");
          }
        }
        mVelocityTracker.clear();
        break;
    }
    mLastX = x;
    mLastY = y;
    return true;
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int measuredWidth = 0;
    int measureHeight = 0;
    final int childCount = getChildCount();
    measureChildren(widthMeasureSpec, heightMeasureSpec);
    int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
    int widthSpaceMode = MeasureSpec.getMode(widthMeasureSpec);
    int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
    int heightSpaceMode = MeasureSpec.getMode(heightMeasureSpec);

    if (childCount == 0) {
      setMeasuredDimension(0, 0);
    } else if (heightSpaceMode == MeasureSpec.AT_MOST && widthSpaceMode == MeasureSpec.AT_MOST) {
      final View childView = getChildAt(0);
      measuredWidth = childView.getMeasuredWidth() * childCount;
      measureHeight = childView.getMeasuredHeight();
      setMeasuredDimension(measuredWidth, measureHeight);
    } else if (heightSpaceMode == MeasureSpec.AT_MOST) {
      measureHeight = getChildAt(0).getMeasuredHeight();
      setMeasuredDimension(widthSpaceSize, measureHeight);
    } else if (widthSpaceMode == MeasureSpec.AT_MOST) {
      final View childView = getChildAt(0);
      measuredWidth = childView.getMeasuredWidth() * childCount;
      setMeasuredDimension(measuredWidth, heightSpaceSize);
    }
  }

  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b) {
    Log.i(TAG, "onLayout: " + getMeasuredWidth());
    int childleft = 0;
    final int childCount = getChildCount();
    mChildrenSize = childCount;
    for (int i = 0; i < mChildrenSize; i++) {
      final View childView = getChildAt(i);
      if (childView.getVisibility() != View.GONE) {
        final int childWidth = childView.getMeasuredWidth();
        mChildWidth = childWidth;
        childView.layout(childleft, 0, childleft + mChildWidth, childView.getMeasuredHeight());
        childleft += childWidth;
      }
    }
  }

  private void smoothScrollTo(int destX,int destY)
  {
    int scrollX=getScrollX();
    int delta=destX-scrollX;
    mScroller.startScroll(scrollX,0,delta,0,1000);
  }

  @Override
  public void computeScroll() {
    if (mScroller.computeScrollOffset()) {
      scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
      postInvalidate();
    }
  }
}

再来看一下布局文件
  

 <com.gxl.viewtest.HorizontalScrollView
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:background="#00ff00"
    >

    <ListView
      android:id="@+id/listview1"
      android:layout_width="600dp"
      android:layout_height="match_parent"
      android:background="@color/colorPrimary"
      >
    </ListView>

    <ListView
      android:id="@+id/listview2"
      android:layout_width="600dp"
      android:layout_height="match_parent"
      android:background="@color/colorAccent"
      >
    </ListView>

    <ListView
      android:id="@+id/listview3"
      android:layout_width="600dp"
      android:layout_height="match_parent"
      android:background="#ff0000"
      >
    </ListView>

  </com.gxl.viewtest.HorizontalScrollView>

以上就是外部处理滑动冲突的代码,认真看一下,思路还是很清晰的。里面还涉及了一些自定义View的知识,我会在后面的博文中认真分析一下代码,你先看一下onInterceptTouchEvent处理滑动冲突的部分。
看一下效果图哈。

2.2 内部拦截法

内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交给父容器去处理,这种方法和Android中的事件分发机制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,这个方法的大体解释就是:

requestDisallowInterceptTouchEvent是ViewGroup类中的一个公用方法,参数是一个boolean值,官方介绍如下

Called when a child does not want this parent and its ancestors to intercept touch events with 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.

android系统中,一次点击事件是从父view传递到子view中,每一层的view可以决定是否拦截并处理点击事件或者传递到下一层,如果子view不处理点击事件,则该事件会传递会父view,由父view去决定是否处理该点击事件。在子view可以通过设置此方法去告诉父view不要拦截并处理点击事件,父view应该接受这个请求直到此次点击事件结束。

使用起来外部拦截事件略显复杂一点。下面我也先来看一下它的通用模板(注意下面的代码是定义在子View中的):

public boolean onInterceptTouchEvent(MotionEvent event) {
    int x = (int) event.getX();
    int y = (int) event.getY();

    switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN: {
      parent.requestDisallowInterceptTouchEvent(true); //父布局不要拦截此事件
      break;
    }
    case MotionEvent.ACTION_MOVE: {
      int deltaX=x-mLastXIntercept;
      int deltaY=y=mLastYIntercept;
      if(父容器需要拦截的事件){
        parent.requestDisallowInterceptTouchEvent(false); //父布局需要要拦截此事件
      }
      break;
    }
    case MotionEvent.ACTION_UP: {
      intercepted = false;
      break;
    }
    default:
      break;
    }
    mLastXIntercept=x;
    mLastYIntercept=y;
    return super.dispathTouchEvent(event);
  }

上述代码是内部拦截法的典型代码,当面对不同的滑动策略时只需要修改里面的条件即可,其他不需要修改做改动而且也不能改动。

 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

更多精彩内容其他人还在看

android byte[] 和short[]转换的方法代码

这篇文章主要介绍了android byte[] 和short[]转换的方法代码,有需要的朋友可以参考一下
收藏 0 赞 0 分享

Android获取应用程序大小的方法

这篇文章主要介绍了Android获取应用程序大小的方法,有需要的朋友可以参考一下
收藏 0 赞 0 分享

Android获取其他包的Context实例代码

这篇文章主要介绍了Android获取其他包的Context实例代码,有需要的朋友可以参考一下
收藏 0 赞 0 分享

Android放大镜的实现代码

这篇文章主要介绍了Android放大镜的实现代码,有需要的朋友可以参考一下
收藏 0 赞 0 分享

Android 读取Properties配置文件的小例子

这篇文章主要介绍了Android 读取Properties配置文件的小例子,有需要的朋友可以参考一下
收藏 0 赞 0 分享

Android通讯录开发之删除功能的实现方法

这篇文章主要介绍了Android通讯录开发之删除功能的实现方法,有需要的朋友可以参考一下
收藏 0 赞 0 分享

使用ViewPager实现android软件使用向导功能实现步骤

现在的大部分android软件,都是使用说明,就是第一次使用该软件时,会出现向导,可以左右滑动,然后就进入应用的主界面了,下面我们就实现这个功能
收藏 0 赞 0 分享

android在异步任务中关闭Cursor的代码方法

android在异步任务中如何关闭Cursor?在我们开发应用的时候,很多时候会遇到这种问题,下面我们就看看代码如何实现
收藏 0 赞 0 分享

Android自定义桌面功能代码实现

android自定义桌面其实很简单,看一个例子就明白了
收藏 0 赞 0 分享

android将图片转换存到数据库再从数据库读取转换成图片实现代码

有时候我们想把图片存入到数据库中,尽管这不是一种明智的选择,但有时候还是不得以会用到,下面说说将图片转换成byte[]数组存入到数据库中去,并从数据库中取出来转换成图像显示出来
收藏 0 赞 0 分享
查看更多