热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

浅谈AndroidView滑动冲突的解决方法

引言 这一篇文章我们就通过介绍滑动冲突的规则和一个实例来更加深入的学习View的事件分发机制。 1、外部滑动方向和内部滑动方向不一致

引言

这一篇文章我们就通过介绍滑动冲突的规则和一个实例来更加深入的学习View的事件分发机制。

1、外部滑动方向和内部滑动方向不一致

考虑这样一种场景,开发中我们经常使用ViewPager和Fragment配合使用所组成的页面滑动效果,很多主流的应用都会使用这样的效果。在这种效果中,可以使用左右滑动来切换界面,而每一个界面里面往往又都是ListView这样的控件。本来这种情况是存在滑动冲突的,只是ViewPager内部处理了这种滑动冲突。如果我们不使用ViewPager而是使用ScrollView,那么滑动冲突就需要我们自己来处理,否者造成的后果就是内外两层只有一层能滑动。

情况1的解决思路

对于第一种情况的解决思路是这样的:当用户左右滑动时,需要让外层的View拦截点击事件。当用户上下滑动时,需要让内部的View拦截点击事件(外层的View不拦截点击事件),这时候我们就可以根据它们的特性来解决滑动冲突。在这里我们可以根据滑动时水平滑动还是垂直滑动来判断谁来拦截点击事件。下面先介绍一种通用的解决滑动冲突的方法。

外部拦截法

外部拦截法是指:点击事件都经过父容器的拦截处理,如果父容器需要处理此事件就进行拦截,否者不拦截交给子View进行处理。这种方法比较符合点击事件的分发机制。外部拦截法需要重写父容器的onInterceptTouchEvent方法,在内部做相应的拦截即可。这种方法的伪代码如下:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
  int x=(int)ev.getX();
  int y=(int)ev.getY();
  boolean intercept=false;
  switch (ev.getAction()){
    //按下事件不要拦截,否则后续事件都会给ViewGroup处理
    case MotionEvent.ACTION_DOWN:
      intercept=false;
      break;
    case MotionEvent.ACTION_MOVE:
      //如果是横向移动就进行拦截,否则不拦截
      int deltaX=x-mLastX;
      int deltaY=y-mLastY;
      if(父容器需要当前点击事件){
        intercept=true;
      }else {
        intercept=false;
      }
      break;
    case MotionEvent.ACTION_UP:
      intercept=false;
      break;
  }
  mLastX = x;
  mLastY = y;
  return intercept;
}

上面代码是外部拦截法的典型逻辑,针对不同的滑动冲突,只需要修改父容器需要当前点击事件的条件即可,其他均不需要修改。我们在描述下:在onInterceptTouchEvent方法中,首先是ACTION_DOWN事件,父容器必须返回false,即不拦截ACTION_DOWN事件,这是因为一旦父容器拦截ACTION_DOWN,那么后续的ACTION_MOVE和ACTION_UP都会直接交给父容器处理,这时候事件就没法传递给子元素了;其次是ACTION_MOVE事件,这个事件可以根据需要来决定是否需要拦截。

下面来看一个具体的实例,这个实现模拟ViewPager的效果,我们定义一个全新的控件,名称叫HorizontalScrollView。具体代码如下:

1、我们先看Activity中的代码:

public class MainActivity extends Activity{

  private HorizontalScrollView mListContainer;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    initView();
  }

  private void initView() {
    LayoutInflater inflater = getLayoutInflater();
    mListCOntainer= (HorizontalScrollView) findViewById(R.id.container);
    final int screenWidth = MyUtils.getScreenMetrics(this).widthPixels;
    for (int i = 0; i <3; i++) {
      ViewGroup layout = (ViewGroup) inflater.inflate(
          R.layout.content_layout, mListContainer, false);
      layout.getLayoutParams().width = screenWidth;
      TextView textView = (TextView) layout.findViewById(R.id.title);
      textView.setText("page " + (i + 1));
      layout.setBackgroundColor(Color.rgb(255 / (i + 1), 255 / (i + 1), 0));
      createList(layout);
      mListContainer.addView(layout);
    }
  }

  private void createList(ViewGroup layout) {
    ListView listView = (ListView) layout.findViewById(R.id.list);
    ArrayList datas = new ArrayList<>();
    for (int i = 0; i <50; i++) {
      datas.add("name " + i);
    }

    ArrayAdapter adapter = new ArrayAdapter<>(this, R.layout.content_list_item, R.id.name, datas);
    listView.setAdapter(adapter);
  }
}

在这个代码中,我们创建了3个ListView然后将其添加到我们自定义控件的。这里HorizontalScrollView是父容器,ListView是子View。下面我们就使用外部拦截法来实现HorizontalScrollView,代码如下:

/**
 * 横向布局控件
 * 模拟经典滑动冲突
 * 我们此处使用ScrollView来模拟ViewPager,那么必须手动处理滑动冲突,否则内外两层只能有一层滑动,那就是滑动冲突。另外内部左右滑动,外部上下滑动也同样属于该类
 */
public class HorizontalScrollView extends ViewGroup {

  //记录上次滑动的坐标
  private int mLastX = 0;
  private int mLastY = 0;
  private WindowManager wm;
  //子View的个数
  private int mChildCount;
  private int mScreenWidth;
  //自定义控件横向宽度
  private int mMeasureWidth;
  //滑动加载下一个界面的阈值
  private int mCrital;
  //滑动辅助类
  private Scroller mScroller;
  //当前展示的子View的索引
  private int showViewIndex;

  public HorizontalScrollView(Context context){
    this(context,null);
  }

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

  /**
   * 初始化
   * @param context
   */
  public void init(Context context) {
    //读取屏幕相关的长宽
    wm = ((Activity)context).getWindowManager();
    mScreenWidth = wm.getDefaultDisplay().getWidth();
    mCrital=mScreenWidth/4;
    mScroller=new Scroller(context);
    showViewIndex=1;
  }

  /**
   * 重新事件拦截机制
   * 我们分析了view的事件分发,我们知道点击事件的分发顺序是 通过父布局分发,如果父布局没有拦截,即onInterceptTouchEvent返回false,
   * 才会传递给子View。所以我们就可以利用onInterceptTouchEvent()这个方法来进行事件的拦截。来看一下代码
   * 此处使用外部拦截法
   * @param ev
   * @return
   */
  @Override
  public boolean onInterceptTouchEvent(MotionEvent ev) {
    int x=(int)ev.getX();
    int y=(int)ev.getY();
    boolean intercept=false;
    switch (ev.getAction()){
      //按下事件不要拦截,否则后续事件都会给ViewGroup处理
      case MotionEvent.ACTION_DOWN:
        intercept=false;
        if(!mScroller.isFinished()){
          mScroller.abortAnimation();
          intercept=true;
        }
        break;
      case MotionEvent.ACTION_MOVE:
        //如果是横向移动就进行拦截,否则不拦截
        int deltaX=x-mLastX;
        int deltaY=y-mLastY;
        if(Math.abs(deltaX)>Math.abs(deltaY)){
          intercept=true;
        }else {
          intercept=false;
        }
        break;
      case MotionEvent.ACTION_UP:
        intercept=false;
        break;
    }
    mLastX = x;
    mLastY = y;
    return intercept;
  }


  @Override
  public boolean onTouchEvent(MotionEvent event) {
    int x = (int) event.getX();
    int y = (int) event.getY();
    switch (event.getAction()) {
      case MotionEvent.ACTION_DOWN:
        if(!mScroller.isFinished()){
          mScroller.abortAnimation();
        }
        break;
      case MotionEvent.ACTION_MOVE:
        int deltaX = x - mLastX;
        /**
         * scrollX是指ViewGroup的左侧边框和当前内容左侧边框之间的距离
         */
        int scrollX=getScrollX();
        if(scrollX-deltaX>0
            && (scrollX-deltaX)<=(mMeasureWidth-mScreenWidth)) {
          scrollBy(-deltaX, 0);
        }
        break;
      case MotionEvent.ACTION_UP:
        scrollX=getScrollX();
        int dx;
        //计算滑动的差值,如果超过1/4就滑动到下一页
        int subScrollX=scrollX-((showViewIndex-1)*mScreenWidth);
        if(Math.abs(subScrollX)>=mCrital){
          boolean next=scrollX>(showViewIndex-1)*mScreenWidth;
          if(showViewIndex<3 && next) {
            showViewIndex++;
          }else {
            showViewIndex--;
          }
        }
        dx=(showViewIndex - 1) * mScreenWidth - scrollX;
        smoothScrollByDx(dx);
        break;
    }
    mLastX = x;
    mLastY = y;
    return true;
  }


  /**
   * 缓慢滚动到指定位置
   * @param dx
   */
  private void smoothScrollByDx(int dx) {
    //在1000毫秒内滑动dx距离,效果就是慢慢滑动
    mScroller.startScroll(getScrollX(), 0, dx, 0, 1000);
    invalidate();
  }

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

从上面代码中,我们看到我们只是很简单的采用横向滑动距离和垂直滑动距离进行比较来判断滑动方向。在滑动过程中,当水平方向的距离大时就判断为水平滑动,否者就是垂直滑动。

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


推荐阅读
  • 本文详细探讨了Java集合框架的使用方法及其性能特点。首先,通过关系图展示了集合接口之间的层次结构,如`Collection`接口作为对象集合的基础,其下分为`List`、`Set`和`Queue`等子接口。其中,`List`接口支持按插入顺序保存元素且允许重复,而`Set`接口则确保元素唯一性。此外,文章还深入分析了不同集合类在实际应用中的性能表现,为开发者选择合适的集合类型提供了参考依据。 ... [详细]
  • C语言中按位取反与按位与运算符的使用方法及应用场景解析
    位运算是一种基于二进制的计算方式,在系统软件开发中经常用于处理二进制位的相关问题。C语言提供了六种位操作运算符,专门用于对整型数据(包括带符号和无符号的char、short等)进行操作。本文详细解析了按位取反和按位与运算符的使用方法及其典型应用场景,帮助开发者更好地理解和应用这些运算符。 ... [详细]
  • Spring框架入门指南:专为新手打造的详细学习笔记
    Spring框架是Java Web开发中广泛应用的轻量级应用框架,以其卓越的功能和出色的性能赢得了广大开发者的青睐。本文为初学者提供了详尽的学习指南,涵盖基础概念、核心组件及实际应用案例,帮助新手快速掌握Spring框架的核心技术与实践技巧。 ... [详细]
  • 在稀疏直接法视觉里程计中,通过优化特征点并采用基于光度误差最小化的灰度图像线性插值技术,提高了定位精度。该方法通过对空间点的非齐次和齐次表示进行处理,利用RGB-D传感器获取的3D坐标信息,在两帧图像之间实现精确匹配,有效减少了光度误差,提升了系统的鲁棒性和稳定性。 ... [详细]
  • 进程(Process)是指计算机中程序对特定数据集的一次运行活动,是系统资源分配与调度的核心单元,构成了操作系统架构的基础。在早期以进程为中心的计算机体系结构中,进程被视为程序的执行实例,其状态和控制信息通过任务描述符(task_struct)进行管理和维护。本文将深入探讨进程的概念及其关键数据结构task_struct,解析其在操作系统中的作用和实现机制。 ... [详细]
  • 深入解析Tomcat:开发者的实用指南
    深入解析Tomcat:开发者的实用指南 ... [详细]
  • 如何在Ubuntu系统中直接使用Snap安装软件
    Canonical与Opera Software近日宣布,基于Chromium的Opera浏览器现已作为Snap包提供给Ubuntu用户,显著提升了在Linux操作系统上的安装便捷性和兼容性。通过Snap,用户可以在Ubuntu系统中轻松安装和更新Opera浏览器,享受更流畅的浏览体验。此外,Snap的容器化特性还确保了应用的安全性和稳定性,为用户提供更加可靠的软件环境。 ... [详细]
  • 从用户转型为开发者:一场思维升级的旅程 | 专访 StarRocks Committer 周威
    从用户转变为开发者,不仅是一次角色的转换,更是一场深刻的思维升级之旅。本次专访中,StarRocks Committer 周威分享了他如何在这一过程中逐步提升技术能力与思维方式,为开源社区贡献自己的力量。 ... [详细]
  • 在处理大规模并发请求时,传统的多线程或多进程模型往往无法有效解决性能瓶颈问题。尽管它们在处理小规模任务时能提升效率,但在高并发场景下,系统资源的过度消耗和上下文切换的开销会显著降低整体性能。相比之下,Python 的 `asyncio` 模块通过协程提供了一种轻量级且高效的并发解决方案。本文将深入解析 `asyncio` 模块的原理及其在实际应用中的优化技巧,帮助开发者更好地利用协程技术提升程序性能。 ... [详细]
  • Jedis接口分类详解与应用指南
    本文详细解析了Jedis接口的分类及其应用指南,重点介绍了字符串数据类型(String)的接口功能。作为Redis中最基本的数据存储形式,字符串类型支持多种操作,如设置、获取和更新键值对等,适用于广泛的应用场景。 ... [详细]
  • 深入解析Spring Boot自动配置机制及其核心原理
    Spring Boot 的自动配置机制是其核心特性之一,旨在简化开发过程并提高效率。本文将深入探讨这一机制的工作原理,解释其如何通过智能化的类路径扫描和条件注解实现自动装配。通过对 Spring Boot 自动配置的详细解析,读者将能够更好地理解和应用这一强大功能,从而在实际项目中更加高效地利用 Spring Boot。 ... [详细]
  • 个人学习进阶:深入解析Tomcat架构体系(第一部分)
    大家好,欢迎来到X的技术分享。近期我一直在深入研究Tomcat的架构体系,收获颇丰。作为一款广泛使用的应用服务器,Tomcat的架构设计非常精妙,对理解和优化Web应用具有重要意义。在本系列的第一部分中,我将详细解析Tomcat的核心组件及其工作原理,帮助读者建立坚实的基础。希望这些内容能为大家的学习和实践带来启发。 ... [详细]
  • 如何正确配置与使用日志组件:Log4j、SLF4J及Logback的连接与整合方法
    在当前的软件开发实践中,无论是开源项目还是日常工作中,日志框架都是不可或缺的工具之一。本文详细探讨了如何正确配置与使用Log4j、SLF4J及Logback这三个流行的日志组件,并深入解析了它们之间的连接与整合方法,旨在帮助开发者高效地管理和优化日志记录流程。 ... [详细]
  • 如何解决Windows 7系统中电脑公司遇到的文件夹访问权限问题
    如何解决Windows 7系统中电脑公司遇到的文件夹访问权限问题 ... [详细]
  • Spring注解开发指南:@Resource与@Component详解 ... [详细]
author-avatar
annieduoduo
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有