Android 触摸事件监听(Activity层,ViewGroup层,View层)详细介绍

Android不同层次的触摸事件监听

APP开发中,经常会遇到有关手势处理的操作,比如向右滑动返回上一个页面。关于触摸事件的处理,我们可以大概处理在不同的层次上。

Activity层:可以看做触摸事件获取的最顶层
ViewGroup层:ViewGroup层可以自主控制是否让子View获取触摸事件
View层:可以决定自己是否真正的消费触摸事件,如果不消费抛给上层ViewGroup

Activity级别的手势监听:(右滑动返回上层界面)

Activity层手势监听的使用场景:一般用于当前页面中没有过多的手势需要处理的时候,至多存在点击事件。对于右滑返回上层界面这种需求,可以将其定义在一个BaseActivity中,子Activity如果需要实现,通过某个开关打开即可。

注意事项 :

1、Activity层,用dispatch可以抓取所有的事件 。

2、对于滑动,要设定一个距离阈值mDistanceGat,用于标记手势是否有效,并且注意往回滑动的处理。

3、如果底层存在点击Item,为了防止滑动过程中变色,可以适时地屏蔽触摸事件:手动构造Cancle事件主动下发,这是为了兼容最基本的点击效果,不过,满足点击的手势判定前, Move事件要正常下发。具体实现如下:

 @Override
  public boolean dispatchTouchEvent(MotionEvent event) {  case MotionEvent.ACTION_MOVE:
          if (Math.abs(event.getX() - down_X) > 10
              && flagDirection == MotionDirection.HORIZION) {
            MotionEvent e = MotionEvent.obtain(event.getEventTime(),
                event.getEventTime(),
                MotionEvent.ACTION_CANCEL,
                event.getX(),
                event.getY(), 0);
            super.dispatchTouchEvent(e);
          } else {
            super.dispatchTouchEvent(event);//不符合条件正常下发
         }

4、防止手势的往回滑动,最好利用GestureDectetor来判断,如果存在往回滑动,则手势无效,使用方式如下:

mDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
  @Override
  public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 

    if (!slideReturnFlag && distanceX > 5) {
      slideReturnFlag = true;
    }} 

5、如何处理Up事件:dispatch是否往下派发。具体的做法是,根据手势是否有效,如果手势无效,那么Up肯定是需要往下派发的。如果有效,根据后续操作进行,因为有时候为了防止子View获取到不必要的点击事件。具体实现如下

@Override
  public boolean dispatchTouchEvent(MotionEvent event) {
      case MotionEvent.ACTION_UP:
          if (mGestureListener != null && !slideReturnFlag
              && flagDirection == MotionDirection.HORIZION) {
            if (stateMotion == CurrentMotionState.SlideRight) {
              mGestureListener.onSlideRight();
            }
          } else { super.dispatchTouchEvent(event);  //无效的手势
          }
          flagDirection = MotionDirection.NONE;
          stateMotion = CurrentMotionState.NONE;
          slideReturnFlag=false;
          break; 

6、在disPatch中最好记录down_X、down_Y ,为了后面的处理与判断,因为dispatch中最能保证你获取到该事件。同时要保证Dispatch事件的下发,

第二:父容器级别的手势监听

注意事项:容器级别的监听至少要使得当前容器强制获取手势的焦点,至于如何获取焦点,可以自己编写onTouch事件,并且reture true。不过我们把判断处理放在dispatch里面,这样能够保证事件完全获取。因为,如果底层消费了事件,onTouch是无法完整获取事件的,但是我们有足够的能力保证dispatch获取完整的事件。无论在本层onTouch消费,还是底层消费,dispatch是用于不会漏掉的。对于手势的容器,最好用padding,而不采用Magin,为什么呢,因为Margin不在容器内部。

1、父容器监听的使用场景

  • 容器中,子View是否存在交互事件,是否存在滑动
  • 上层容器是否存在拦截事件的可能,比如SrollView

2、实现

子View不存在交互事件:

这类容器可以采用Dispatch来实现,不过需要强制获取焦点,同时也要适时的释放焦点。具体实现如下:
如何保证本层一定接收到Down后续事件。dispatch的Down事件能够返回True即可。

如何保证本层不被偶然的屏蔽,使用 getParent().requestDisallowInterceptTouchEvent(true)即可。当然,有强制获取也要适时的释放,当手势判定为无效的时候就要释放,具体实现如下:

@Override
 public boolean dispatchTouchEvent(MotionEvent ev) { 

 getParent().requestDisallowInterceptTouchEvent(true);</strong></span>
   mGestureDetector.onTouchEvent(ev); 

   switch (ev.getActionMasked()) {
     case MotionEvent.ACTION_DOWN:
       down_X = ev.getX();
       down_Y = ev.getY();
       slideReturnFlag = false;
       break;
     case MotionEvent.ACTION_CANCEL:
     case MotionEvent.ACTION_MOVE:
       if (Math.abs(down_X - ev.getX()) < Math.abs(down_Y - ev.getY())
           && Math.abs(ev.getY() - down_Y) > mDistanceGate / 2) {
     getParent().requestDisallowInterceptTouchEvent(false);</span></strong>
       }
     default:
       break;
   }
   return super.dispatchTouchEvent(ev);
 } 

子View存在交互事件:子View存在交互事件,就要通过dispatch与onTouch的配合使用,dispatch为了判断手势的有效性,同时既然从容器层开始,强制获取焦点是必须的,底层如何强制获取焦点,不关心。这里如果没有消费Down,则说明底层View消费了。同时要兼容无效手势强制焦点获取的释放,防止上传滚动View,具体实现如下:

  @Override
  public boolean dispatchTouchEvent(MotionEvent ev) { 

    mGestureDetector.onTouchEvent(ev); 

    switch (ev.getActionMasked()) {
      case MotionEvent.ACTION_DOWN:
        down_X = ev.getX();
        down_Y = ev.getY();
        slideReturnFlag = false;
        break;
      default:
        break;
    }
    return super.dispatchTouchEvent(ev);
  }

onTouch中处理响应事件,主要是为了防止底层获取后,上层还处理

// ACTION_CANCEL 嵌套如其他scrowView 可能屏蔽
@Override
public boolean onTouchEvent(MotionEvent ev) { 

  switch (ev.getActionMasked()) {
    case MotionEvent.ACTION_DOWN:
// ACTION_CANCEL 嵌套如其他scrowView 可能屏蔽
 @Override
 public boolean onTouchEvent(MotionEvent ev) { 

   switch (ev.getActionMasked()) {
     case MotionEvent.ACTION_DOWN:
       getParent().requestDisallowInterceptTouchEvent(true);
       return true;
     case MotionEvent.ACTION_CANCEL:
       return true;
     case MotionEvent.ACTION_UP:
       if (Math.abs(down_X - ev.getX()) > Math.abs(down_Y - ev.getY()) && !slideReturnFlag
           && ev.getX() - down_X > mDistanceGate) { 

         // 返回上个Activity,也有可能是返回上一个Fragment
         FragmentActivity mContext = null;
         if (getContext() instanceof FragmentActivity) {
           mContext = (FragmentActivity)getContext();
           FragmentManager fm = mContext.getSupportFragmentManager(); 

           if (fm.getBackStackEntryCount() > 0) {
             fm.popBackStack();
           } else {
             mContext.finish();
           }
         }
       }
       return true;
     case MotionEvent.ACTION_MOVE: 

       if (Math.abs(down_X - ev.getX()) < Math.abs(down_Y - ev.getY())
           && Math.abs(ev.getY() - down_Y) > mDistanceGate / 2) {
         getParent().requestDisallowInterceptTouchEvent(false);
       }
       return true;
     default:
       break;
   }
   return super.onTouchEvent(ev);
 }

3、父容器手势的拦截,有些时候,子View具有点击事件,点击变颜色。给予一定容错空间后,强制拦截事件。dispatch返回true保证事件下传,不必担心

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) { 

  if (ev.getActionMasked() == MotionEvent.ACTION_MOVE && Math.abs(down_X - ev.getX()) > 20)
    return true; 

  return super.onInterceptTouchEvent(ev);
}

第四:HorizontalScrollView边缘状态下,滑动手势的监听,具体实现如下,主要是边缘处的手势判断。

@Override
  public boolean dispatchTouchEvent(MotionEvent ev) { 

    getParent().requestDisallowInterceptTouchEvent(true);
    mGestureDetector.onTouchEvent(ev); 

    switch (ev.getActionMasked()) {
      case MotionEvent.ACTION_DOWN:
        slideReturnFlag = false;
        down_X = ev.getX();
        down_Y = ev.getY();
        oldScrollX = getScrollX();
        break;
      case MotionEvent.ACTION_UP:
        if (Math.abs(down_X - ev.getX()) > Math.abs(down_Y - ev.getY())
            && ev.getX() - down_X > mDistanceGate && !slideReturnFlag
            && oldScrollX == 0) {
          // 返回上个Activity,也有可能是返回上一个Fragment
          FragmentActivity mContext = null;
          if (getContext() instanceof FragmentActivity) {
            mContext = (FragmentActivity)getContext();
            FragmentManager fm = mContext.getSupportFragmentManager(); 

            if (fm.getBackStackEntryCount() > 0) {
              fm.popBackStack();
            } else {
              mContext.finish();
            }
          }
        }
        break;
      case MotionEvent.ACTION_MOVE:
        if (Math.abs(down_X - ev.getX()) < Math.abs(down_Y - ev.getY())
            && Math.abs(ev.getY() - down_Y) > mDistanceGate / 2) {
          getParent().requestDisallowInterceptTouchEvent(false);
        }
      default:
        break;
    } 

    return super.dispatchTouchEvent(ev);
  }

第五:防止垂直滚动的ScrollView过早的屏蔽事件:重写拦截函数即可:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
  if (Math.abs(ev.getY() - down_Y) < getResources().getDimensionPixelSize(R.dimen.slide_gesture_vertical_gate)) {
    super.onInterceptTouchEvent(ev);
    return false;
  }
  return super.onInterceptTouchEvent(ev);
} 

@Override
public boolean dispatchTouchEvent(MotionEvent ev) { 

  switch (ev.getAction()) {
  case MotionEvent.ACTION_DOWN:
    down_X = ev.getX();
    down_Y = ev.getY();
    break;

第六:Viewpager第一页滑动手势;

1、防止过早拦击

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
  getParent().requestDisallowInterceptTouchEvent(true); 

  mGestureDetector.onTouchEvent(ev); 

  switch (ev.getActionMasked()) {
    case MotionEvent.ACTION_DOWN:
      down_X = ev.getX();
      down_Y=ev.getY();
      slideReturnFlag=false;
      break; 

    case MotionEvent.ACTION_MOVE:
      if (Math.abs(down_X - ev.getX()) < Math.abs(down_Y - ev.getY())
          && Math.abs(ev.getY() - down_Y) > mDistanceGate / 2) {
        getParent().requestDisallowInterceptTouchEvent(false);
      }
      break;
    default:
      break;
  } 

  return super.dispatchTouchEvent(ev);
}

2、防止往回滑动等

/*
 * 触摸事件的处理,要判断是否是ViewPager不可滑动的时候
 */
@Override
public boolean onTouchEvent(MotionEvent arg0) { 

  // 防止跳动
  boolean ret = super.onTouchEvent(arg0); 

  switch (arg0.getActionMasked()) {
    case MotionEvent.ACTION_DOWN:
      Log.v("lishang", "down");
      break;
    case MotionEvent.ACTION_CANCEL:
    case MotionEvent.ACTION_UP: 

      Log.v("lishang", "up");
      if (slideDirection == SlideDirection.RIGHT) { 

        if (slideReturnFlag || getCurrentItem() != 0 || arg0.getX() - down_X < mDistanceGate || mPercent > 0.01f)
          break;
      } else if (slideDirection == SlideDirection.LEFT) { 

        if (getAdapter() != null) { 

          if (slideReturnFlag||getCurrentItem() != getAdapter().getCount() - 1
              || down_X - arg0.getX() < mDistanceGate || mPercent > 0.01f)
            break;
        } 

      } else {

第七:getParent().requestDisallowInterceptTouchEvent

这个函数的的作用仅仅能够保证事件不被屏蔽,但是倘若本层dispatch在down的时候返回false,那么事件的处理就无效了,就算强制获取焦点

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

(0)

相关推荐

  • Android自定义ViewGroup实现带箭头的圆角矩形菜单

    本文和大家一起做一个带箭头的圆角矩形菜单,大概长下面这个样子: 要求顶上的箭头要对准菜单锚点,菜单项按压反色,菜单背景色和按压色可配置. 最简单的做法就是让UX给个三角形的图片往上一贴,但是转念一想这样是不是太low了点,而且不同分辨率也不太好适配,干脆自定义一个ViewGroup吧! 自定义ViewGroup其实很简单,基本都是按一定的套路来的. 一.定义一个attrs.xml 就是声明一下你的这个自定义View有哪些可配置的属性,将来使用的时候可以自由配置.这里声明了7个属性,分别是:箭头宽

  • Android中Toolbar随着ScrollView滑动透明度渐变效果实现

    Android中Toolbar随着ScrollView滑动透明度渐变效果实现 一.思路:监听ScrollView的滑动事件 不断的修改Toolbar的透明度 二.注意 1.ScrollView 6.0以前没有scrollView.setOnScrollChangeListener(l)方法  所以要自定义ScrollView 在onScrollChanged()中监听 2.ScrollView 6.0(23)以前没有scrollView.setOnScrollChangeListener()方法

  • 详解Android应用开发中Scroller类的屏幕滑动功能运用

    今天给大家介绍下Android中滑屏功能的一个基本实现过程以及原理初探,最后给大家重点讲解View视图中scrollTo 与scrollBy这两个函数的区别 .   首先 ,我们必须明白在Android View视图是没有边界的,Canvas是没有边界的,只不过我们通过绘制特定的View时对Canvas对象进行了一定的操作,例如 : translate(平移).clipRect(剪切)等,以便达到我们的对该Canvas对象绘制的要求 ,我们可以将这种无边界的视图称为"视图坐标"----

  • Android中实现监听ScrollView滑动事件

    时候我们需要监听ScroView的滑动情况,比如滑动了多少距离,是否滑到布局的顶部或者底部.可惜的是SDK并没有相应的方法,不过倒是提供了一个 复制代码 代码如下: protected void onScrollChanged(int x, int y, int oldx, int oldy) 方法,显然这个方法是不能被外界调用的,因此就需要把它暴露出去,方便使用.解决方式就是写一个接口, 复制代码 代码如下: package com.example.demo1;    public inter

  • Android自定义ViewGroup实现标签浮动效果

    前面在学习鸿洋大神的一些自定义的View文章,看到了自定义ViewGroup实现浮动标签,初步看了下他的思路以及结合自己的思路完成了自己的浮动标签的自定义ViewGroup.目前实现的可以动态添加标签.可点击.效果图如下: 1.思路  首先在onMeasure方法中测量ViewGroup的宽和高,重点是处理当我们自定义的ViewGroup设置为wrap_content的情况下,如何去测量其大小的问题.当我们自定义的ViewGroup设置为wrap_content时,我们需要让子View先去测量自

  • Android App开发中自定义View和ViewGroup的实例教程

    View Android所有的控件都是View或者View的子类,它其实表示的就是屏幕上的一块矩形区域,用一个Rect来表示,left,top表示View相对于它的parent View的起点,width,height表示View自己的宽高,通过这4个字段就能确定View在屏幕上的位置,确定位置后就可以开始绘制View的内容了. View绘制过程 View的绘制可以分为下面三个过程: Measure View会先做一次测量,算出自己需要占用多大的面积.View的Measure过程给我们暴露了一个

  • Android ScrollView滑动实现仿QQ空间标题栏渐变

    今天来研究的是ScrollView-滚动视图,滚动视图又分横向滚动视图(HorizontalScrollView)和纵向滚动视图(ScrollView),今天主要研究纵向的.相信大家在开发中经常用到,ScrollView的功能已经很强大了,但是仍然满足不了我们脑洞大开的UI设计师们,所以我们要自定义-本篇文章主要讲监听ScrollView的滑动实现仿QQ空间标题栏渐变,先看一下效果图: 好了我们切入主题. 有可能你不知道的那些ScrollView属性  •android:scrollbars 设

  • Android编程重写ViewGroup实现卡片布局的方法

    本文实例讲述了Android编程重写ViewGroup实现卡片布局的方法.分享给大家供大家参考,具体如下: 实现效果如图: 实现思路 1. 重写onMeasure(int widthMeasureSpec, int heightMeasureSpec)设置每个子View的大小 2. 重写onLayout(boolean changed, int l, int t, int r, int b) 设置每个子View的位置 第一步:新建FlowLayout继承ViewGroup package com

  • Android中ScrollView实现滑动距离监听器的方法

    前言 众所周知ScrollView是我们经常使用的一个UI控件,也许你在使用ScrollView的过程中会发现,当你想监听ScrollView滑动的距离时却没有合适的监听器!当然在API 23中有setOnScrollChangeListener(View.OnScrollChangeListener l)可以使用,但是并不兼容低版本的API.那怎么办呢?只好重写ScrollView来实现对滑动距离的监听了. 话不多说,直接上代码: public class MyScrollView exten

  • Android继承ViewGroup实现Scroll滑动效果的方法示例

    本文实例讲述了Android继承ViewGroup实现Scroll滑动效果的方法.分享给大家供大家参考,具体如下: extends ViewGroup需要重写onMeasure和onLayout方法 onMeasure方法是去测量ViewGroup需要的大小以及包含的子View需要的大小. 执行完上面的方法后,再执行onLayout方法去设置子View的摆放位置. 实现Scroll滑动效果需要去检测滑动速率,即要知道每个单位时间滑动了多少像素值,根据这个像素值去判断Scroll滑动到下一页还是上

  • Android HorizontalScrollView左右滑动效果

    本文实例为大家分享了Android HorizontalScrollView左右滑动的具体代码,供大家参考,具体内容如下 效果图 一.什么是HorizontalScrollView HorizontalScrollView实际上是一个FrameLayout ,这意味着你只能在它下面放置一个子控件 ,这个子控件可以包含很多数据内容.有可能这个子控件本身就是一个布局控件,可以包含非常多的其他用来展示数据的控件.这个布局控件一般使用的是一个水平布局的LinearLayout.TextView也是一个可

  • Android自定义ViewGroup之实现FlowLayout流式布局

    整理总结自鸿洋的博客:http://blog.csdn.net/lmj623565791/article/details/38352503/  一.FlowLayout介绍  所谓FlowLayout,就是控件根据ViewGroup的宽,自动的往右添加,如果当前行剩余空间不足,则自动添加到下一行.有点像所有的控件都往左飘的感觉,第一行满了,往第二行飘~所以也叫流式布局.Android并没有提供流式布局,但是某些场合中,流式布局还是非常适合使用的,比如关键字标签,搜索热词列表等,比如下图: git

随机推荐