Android解决View的滑动冲突的方法

关于 Android 的 TouchEvent 事件分发机制可以看这里:Java_Android_Learn,本文讲解的是如何去解决 View 之间的滑动冲突

当父容器与子 View 都可以滑动时,就会产生滑动冲突。例如 ViewPager 中包含了 ListView 时,ViewPager 可以横向滑动,而 ListView 可以竖向滑动,此时就会产生滑动冲突。而我们之所以在使用的过程中没有发现这个问题,是因为 ViewPager 内部已经处理好滑动冲突了

解决 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;
  }
  • 根据实际的业务需求,判断是否需要处理 ACTION_MOVE 事件,如果父 View 需要处理则返回 true,否则返回 false 并交由子 View 去处理
  • ACTION_DOWN 事件需要返回 false,父容器不能进行拦截,否则根据 View 的事件分发机制,后续的 ACTION_MOVE 与 ACTION_UP 事件都将默认交由父容器进行处理
  • 原则上 ACTION_UP 事件也需要返回 false,如果返回 true,那么子 View 将接收不到 ACTION_UP 事件,子 View 的onClick 事件也无法触发

二、内部拦截法

内部拦截法则是要求父容器不拦截任何事件,所有事件都传递给子 View,子 View 根据需求判断是自己消费事件还是传回给父容器进行处理,思路可以看以下伪代码:

子 View 修改其 dispatchTouchEvent 方法

public boolean dispatchTouchEvent(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 - mLastX;
        int deltaY = y - mLastY;
        if (父容器需要此类点击事件) {
          parent.requestDisallowInterceptTouchEvent(false);
        }
        break;
      }
      case MotionEvent.ACTION_UP: {
        break;
      }
      default:
        break;
    }
    mLastX = x;
    mLastY = y;
    return super.dispatchTouchEvent(event);
  }

父容器修改其 onInterceptTouchEvent 方法

public boolean onInterceptTouchEvent(MotionEvent event) {
    int action = event.getAction();
    if (action == MotionEvent.ACTION_DOWN) {
      return false;
    } else {
      return true;
    }
  }
  • 内部拦截法要求父容器不能拦截 ACTION_DOWN 事件,否则一旦父容器拦截 ACTION_DOWN 事件,那么后续的触摸事件都不会传递给子View
  • 滑动策略的逻辑放在子 View 的 dispatchTouchEvent 方法的 ACTION_MOVE 事件中,如果父容器需要处理事件则调用 parent.requestDisallowInterceptTouchEvent(false) 方法让父容器去拦截事件

三、滑动冲突实例

这里以 ViewPager 作为父容器,看看 ViewPager 与其内部 View 之间的滑动冲突情况

为了使 ViewPager 不处理滑动冲突,这里来重写其 onInterceptTouchEvent() 方法

/**
 * 作者:叶应是叶
 * 时间:2018/7/15 10:26
 * 描述:
 */
public class MyViewPager extends ViewPager {

  public MyViewPager(@NonNull Context context) {
    super(context);
  }

  public MyViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
  }

  @Override
  public boolean onInterceptTouchEvent(MotionEvent ev) {
    return false;
  }

}

这里用一个布尔变量来控制 ViewPager 中每一个页面包含的是 ListView 还是 TextView

public class MainActivity extends AppCompatActivity {

  private List<View> viewList;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ViewPager viewPager = findViewById(R.id.viewPager);
    viewList = new ArrayList<>();
    initData(false);
    viewPager.setAdapter(new MyPagerAdapter(viewList));
  }

  private void initData(boolean flag) {
    for (int j = 0; j < 4; j++) {
      View view;
      if (flag) {
        ListView listView = new ListView(this);
        List<String> dataList = new ArrayList<>();
        for (int i = 0; i < 30; i++) {
          dataList.add("leavesC " + i);
        }
        ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, dataList);
        listView.setAdapter(adapter);
        view = listView;
      } else {
        TextView textView = new TextView(this);
        textView.setGravity(Gravity.CENTER);
        textView.setText("leavesC " + j);
        view = textView;
      }
      viewList.add(view);
    }
  }

}

当子 View 为 TextView 时

然而此时还是没有发生滑动冲突,ViewPager 还是可以正常使用。这是因为 TextView 默认是不可点击的,因此 TextView 并不会消费触摸事件,触摸事件最后还是传回给 ViewPager 进行处理,因为此时还是可以正常使用

如果为 TextView 设置 textView.setClickable(true); ,就会使得 ViewPager 无法滑动

当子 View 为 ListView 时,则只能上下滑动,而无法左右滑动

四、通过外部拦截法解决滑动冲突

外部拦截法仅需要修改父容器的 onInterceptTouchEvent() 方法即可,通过滑动时横向滑动距离与竖向滑动距离之间的大小,判断是否在进行左右滑动,如果判断出当前是滑动操作,则使 ViewPager 消费该事件

/**
 * 作者:叶应是叶
 * 时间:2018/7/15 10:26
 * 描述:
 */
public class MyViewPager extends ViewPager {

  public MyViewPager(@NonNull Context context) {
    super(context);
  }

  public MyViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
  }

  private int lastXIntercept;

  private int lastYIntercept;

  @Override
  public boolean onInterceptTouchEvent(MotionEvent ev) {
    boolean intercepted = false;
    int x = (int) ev.getX();
    int y = (int) ev.getY();
    final int action = ev.getAction() & MotionEvent.ACTION_MASK;
    switch (action) {
      case MotionEvent.ACTION_DOWN:
        //不拦截此事件
        intercepted = false;
        //调用 ViewPager的 onInterceptTouchEvent 方法用于初始化 mActivePointerId
        super.onInterceptTouchEvent(ev);
        break;
      case MotionEvent.ACTION_MOVE:
        //横向位移增量
        int deltaX = x - lastXIntercept;
        //竖向位移增量
        int deltaY = y - lastYIntercept;
        //如果横向滑动距离大于竖向滑动距离,则认为使用者是想要左右滑动
        //此时就使 ViewPager 拦截此事件
        intercepted = Math.abs(deltaX) > Math.abs(deltaY);
        break;
      case MotionEvent.ACTION_UP:
        //不拦截此事件
        intercepted = false;
        break;
      default:
        break;
    }
    lastXIntercept = x;
    lastYIntercept = y;
    return intercepted;
  }

}

五、通过内部拦截法解决滑动冲突

内部拦截法需要重写 ListView 的 dispatchTouchEvent 方法

/**
 * 作者:叶应是叶
 * 时间:2018/7/15 12:40
 * 描述:
 */
public class MyListView extends ListView {

  public MyListView(Context context) {
    super(context);
  }

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

  private int lastX;

  private int lastY;

  @Override
  public boolean dispatchTouchEvent(MotionEvent ev) {
    int x = (int) ev.getX();
    int y = (int) ev.getY();
    final int action = ev.getAction() & MotionEvent.ACTION_MASK;
    switch (action) {
      case MotionEvent.ACTION_DOWN:
        getParent().requestDisallowInterceptTouchEvent(true);
        break;
      case MotionEvent.ACTION_MOVE:
        //横向位移增量
        int deltaX = x - lastX;
        //竖向位移增量
        int deltaY = y - lastY;
        //如果横向滑动距离大于竖向滑动距离,则认为使用者是想要左右滑动
        //此时就通知父容器 ViewPager 处理此事件
        if (Math.abs(deltaX) > Math.abs(deltaY)) {
          getParent().requestDisallowInterceptTouchEvent(false);
        }
        break;
      case MotionEvent.ACTION_UP:
        break;
      default:
        break;
    }
    lastX = x;
    lastY = y;
    return super.dispatchTouchEvent(ev);
  }

}

同时也需要修改 MyViewPager 的 onInterceptTouchEvent 方法

/**
 * 作者:叶应是叶
 * 时间:2018/7/15 10:26
 * 描述:
 */
public class MyViewPager extends ViewPager {

  public MyViewPager(@NonNull Context context) {
    super(context);
  }

  public MyViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
  }

  @Override
  public boolean onInterceptTouchEvent(MotionEvent ev) {
    final int action = ev.getAction() & MotionEvent.ACTION_MASK;
    if (action == MotionEvent.ACTION_DOWN) {
      super.onInterceptTouchEvent(ev);
      return false;
    }
    return true;
  }

}

更多的学习笔记看这里: Java_Android_Learn

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

(0)

相关推荐

  • Android 中SwipeRefreshLayout与ViewPager滑动事件冲突解决方法

    Android 中SwipeRefreshLayout与ViewPager滑动事件冲突解决方法 问题描述: 开发中发现,SwipeRefreshLayout的下拉刷新,与ViewPager开发的banner的左右滑动事件有一点冲突,导致banner的左右滑动不够顺畅.很容易在banner的左右滑动的过程中,触发SwipeRefreshLayout的下拉刷新,从而导致banner左右滑动的体验很差. 解决方案: 可以在ViewPager的滑动时候设置SwipeRefreshLayout暂时不可用,

  • Android listview的滑动冲突解决方法

    Android listview的滑动冲突解决方法 在Android开发的过程中,有时候会遇到子控件和父控件都要滑动的情况,尤其是当子控件为listview的时候.就比如在一个ScrollView里有一个listview,这种情况较常见,就会出现这种滑动冲突的情况.这种情况也比较常见,有时候就是这样,没法,但是,了解事件分发的我们知道应该怎么处理这样的事情 有两点需要注意: 一般来说,view的onTouchEvent返回true,即消耗点击事件,viewgroup的onInterceptTou

  • 浅谈Android View滑动冲突的解决方法

    引言 这一篇文章我们就通过介绍滑动冲突的规则和一个实例来更加深入的学习View的事件分发机制. 1.外部滑动方向和内部滑动方向不一致 考虑这样一种场景,开发中我们经常使用ViewPager和Fragment配合使用所组成的页面滑动效果,很多主流的应用都会使用这样的效果.在这种效果中,可以使用左右滑动来切换界面,而每一个界面里面往往又都是ListView这样的控件.本来这种情况是存在滑动冲突的,只是ViewPager内部处理了这种滑动冲突.如果我们不使用ViewPager而是使用ScrollVie

  • Android App中ViewPager所带来的滑动冲突问题解决方法

    叙述 滑动冲突可以说是日常开发中比较常见的一类问题,也是比较让人头疼的一类问题,尤其是在使用第三方框架的时候,两个原本完美的控件,组合在一起之后,忽然发现整个世界都不好了. 关于滑动冲突 滑动冲突分类: 滑动冲突,总的来说就是两类. 1.同方向滑动冲突 比如ScrollView嵌套ListView,或者是ScrollView嵌套自己 2.不同方向滑动冲突 比如ScrollView嵌套ViewPager,或者是ViewPager嵌套ScrollView,这种情况其实很典型.现在大部分应用最外层都是

  • Android中DrawerLayout+ViewPager滑动冲突的解决方法

    DrawerLayout 是 Android 官方的侧滑菜单控件,而 ViewPager 相信大家都很熟悉了.今天这里就讲一下当在 DrawerLayout 中嵌套 ViewPager 时,要如何解决滑动冲突的问题,效果如下: 首先,让我们先来解决 DrawerLayout 和 ViewPager 的侧滑事件冲突.当 DrawerLayout 中嵌套 ViewPager 时,侧滑默认是执行 DrawerLayout 的侧滑事件,因为 Android 的事件分发是从 外层 ViewGroup 向里

  • Android中RecyclerView嵌套滑动冲突解决的代码片段

    在纵向RecyclerView嵌套横向RecyclerView时,如果纵向RecyclerView有下拉刷新功能,那么内部的横向RecyclerView的横向滑动体验会很差.(只有纯横向滑动时,才能滑动内部的横向RecyclerView,否则滑动事件就会影响到下拉刷新),添加拦截判断. public class MySwipeRefreshLayout extends SwipeRefreshLayout { private boolean mIsVpDragger; private final

  • 浅谈Android实践之ScrollView中滑动冲突处理解决方案

    1. 前言 在Android开发中,如果是一些简单的布局,都很容易搞定,但是一旦涉及到复杂的页面,特别是为了兼容小屏手机而使用了ScrollView以后,就会出现很多点击事件的冲突,最经典的就是ScrollView中嵌套了ListView.我想大部分刚开始接触Android的同学们都踩到过这个坑,这一篇文章就从最近做的一个项目讲起,然后在过程中提供一些解决冲突的思路. 2. 项目起始 项目有一个页面,涉及到了ViewPager,MapView,ListView,也就是说在一个页面中,会有这三个V

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

    Android手势事件的冲突跟点击事件的分发过程息息相关,由三个重要的方法来共同完成,分别是:dispatchTouchEvent.onInterceptTouchEvent和onTouchEvent. public boolean dispatchTouchEvent(MotionEvent ev) 这个方法用来进行事件的分发.如果事件传递到view,那么这个方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是

  • Android ScrollView嵌套横向滑动控件时冲突问题

    前言:今天在开发的时候遇到这样的问题,最外层是ScrollView,里面嵌套了一个横向滑动的日历控件,在滑动日历的时候很卡顿.看到这种问题,自然而然的就会想到scrollview和其他可滑动控件的冲突问题. 解决思路 用户的左右滑动操作被最外层的scrollView控件处理掉了,所以只要让scrollview对左右滑动事件不监听,让其子控件处理左右滑动事件 .重写scrollview的onInterceptTouchEvent方法,当上下滑动时不处理即可. 代码如下 public void se

  • Android解决viewpager嵌套滑动冲突并保留侧滑菜单功能

    重写子pagerview的dispatchTouchEvent方法,在返回前添加一句getParent().requestDisallowInterceptTouchEvent(true)中断掉事件的传递,类如下 public class SupperViewPager extends ViewPager { private int screenWidth;//屏幕宽度 public SupperViewPager(Context context) { super(context); } pub

随机推荐