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

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

涉及到了ViewPager,MapView,ListView,就需要ScrollView来做一下支援,这篇文章主要介绍了浅谈Android实践之ScrollView中滑动冲突处理解决方案,有需要的可以来了解一下。

1. 前言

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

2. 项目起始

项目有一个页面,涉及到了ViewPager,MapView,ListView,也就是说在一个页面中,会有这三个View,很明显,屏幕无法完全显示,需要ScrollView来做一下支援,就引入了ScrollView作为外层的容器。但是由于这个页面的数据展示需要做到用户手动下拉刷新,于是又引入了官方的SwipeRefreshLayout。

于是这个页面的布局就成了这样子。如下图(细节布局忽略)。

图-1 布局图

加入了ScrollView和SwipeRefreshLayout之后引入了新的问题,就是各个控件之间的事件冲突,嵌套在ScrollView中的ViewPager、MapView、ListView都需要能够正确的处理点击事件,特别是ListView,需求要求它在ScrollView中可以滑动,两种滑动混淆在一起,不是特别好处理。

问题提出来了,下面直接看解决思路。

3. 解决滑动冲突的思路

在ViewGroup中有个方法叫requestDisallowInterceptTouchEvent(boolean disallowIntercept),这个方法可以用来控制该ViewGroup是否截断点击事件。我们解决滑动冲突的时候,其实就是在某个时机去调用这个方法,让父布局不截断点击事件,将点击事件传递到子View,让相关的子View去处理。 

下面就是关于在项目中处理各种点击事件冲突的一些例子和思考。处理的方法只是提供一种思路,可能并不是最优的方法,肯定存在其他思路的解决方案。 

以下处理滑动冲突的方案都是在子View的OnTouchListener里面进行处理,并没有去复写控件的点击事件处理过程,在使用中还是比较方便的。

3.1 MapView地图页面滑动冲突

MapView与ScrollView的冲突主要在于,当用户点击到MapView地图并且滑动的时候,希望由地图Map去处理点击事件,并包括后续的滑动事件、双手指缩放地图等等。 

在ScrollView中,是会默认截断点击事件的,导致用户点击到地图后,地图基本是没有反应,更别谈双手指缩放地图了。 

用户手指点击到地图,并且滑动的时候,很难确定用户是要ScrollView上下滑动还是操控地图内容滑动,所以我简单的认为,只要用户手指点击到地图,就是要对地图进行操作;当用户手指抬起,就认为用户不需要操作地图了。 

解决思路也很简单,就是在用户点击到地图或者滑动地图时候,让ScrollView不截断点击事件,并传递给子View处理,也就是地图去处理点击事件;当用户手指抬起的时候,将ScrollView的状态恢复至之前的状态,也就是ScrollView可以截断点击事件。

我使用的是百度地图,直接上代码,更容易理解。

mMapView.getChildAt(0).setOnTouchListener(new View.OnTouchListener() {
      @Override
      public boolean onTouch(View v, MotionEvent event) {
        if(event.getAction() == MotionEvent.ACTION_UP){
          //允许ScrollView截断点击事件,ScrollView可滑动
          mScrollView.requestDisallowInterceptTouchEvent(false);
        }else{
          //不允许ScrollView截断点击事件,点击事件由子View处理
          mScrollView.requestDisallowInterceptTouchEvent(true);
        }
        return false;
      }
    });

3.2 ViewPager滑动冲突解决

在这个项目中,ViewPager在页面最顶层,如果只是ScrollView里面嵌套了ViewPager,因为这两个控件是不同方向的滑动事件,所以基本不会出现冲突。 

但是由于引入了SwipeRefreshLayout,我发现在滑动ViewPager的时候,很容易就触发了SwipeRefreshLayout的下来刷新,进而有可能阻断了ViewPager的左右滑动效果,体验很不好。而且在滑动ViewPager的过程中,用户滑动肯定不是一直水平的,会有一定程度向上向下的滑动。 

ViewPager处理冲突和地图处理冲突有些不同,因为当用户点击到ViewPager,在滑动过程中,基本就可以猜测到用户是想左右滑动ViewPager还是上下滑动ScrollView(或者下拉刷新),这就不能像地图一样,在点击到ViewPager就禁止ScrollView截断点击事件(或者SwipeRefreshLayout下拉刷新功能),需要在滑动过程中做出判断。 

解决思路就是,设定一个阈值,一旦用户在X轴也就是横向滑动距离超过这个阈值,我就认为用户是要左右滑动ViewPager,就禁止ScrollView截断点击事件同时设置SwipeRefreshLayout不能下拉刷新。当用户抬起手指,就认为用户对ViewPager的操作已经完毕,将ScrollView和SwipeRefreshLayout状态恢复。

 mViewPager.setOnTouchListener(new View.OnTouchListener() {
  @Override
  public boolean onTouch(View v, MotionEvent event) {
    int action = event.getAction();

    if(action == MotionEvent.ACTION_DOWN) {
      // 记录点击到ViewPager时候,手指的X坐标
      mLastX = event.getX();
    }
    if(action == MotionEvent.ACTION_MOVE) {
      // 超过阈值
      if(Math.abs(event.getX() - mLastX) > 60f) {
        mRefreshLayout.setEnabled(false);
        mScrollView.requestDisallowInterceptTouchEvent(true);
      }
    }
    if(action == MotionEvent.ACTION_UP) {
      // 用户抬起手指,恢复父布局状态
      mScrollView.requestDisallowInterceptTouchEvent(false);
      mRefreshLayout.setEnabled(true);
    }
    return false;
  }
});

用户点击到ViewPager时候,记录下点击位置的X坐标,当用户滑动过程中,如果在X轴上面的滑动超过阈值(我写的是60f,这个可以在实际使用中自行设置最佳的阈值),就禁止ScrollView截断点击事件,同时设置不可下拉刷新。当用户手指离开屏幕,将ScrollView和SwipeRefreshLayout的状态恢复。

3.3 ListView滑动冲突解决

在ScrollView中嵌套ListView,会出现各种各样奇怪的问题。比如说ListView显示有问题,可能才一两个Item那么高,并没有完全的展开。网上流传解决这种问题的方法会有两种。

  • 根据展示数据的个数乘以每一个Item的高度,计算出ListView的总体高度,然后动态的设置ListView的高度
  • 复写ListView的onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法,让ListView完全展开

这两种方法都可以解决ListView展示不完全的问题,而且也可以滑动(其实是使用ScrollView的滑动效果),但是有一个最大的遗憾,就是ListView里面的View不能复用了。因为这两种方法都是算出了ListView的全部高度,然后将ListView控件的高度设置成这个高度,这样的话,ListView就相当于一个LinearLayout的布局了,失去了复用View的优势,而且在某些场景可能还没有LinearLayout好用,更甚的是,如果有大量图片的话,很容易就OOM了,这是在研发过程中最不希望看见的。 

可以参考一下美团,美团的首页,就是一个ScrollView,下滑的时候会发现,并不能无限向下滑动,到了底部会提醒跳转到一个二级页面去查看全部的团购信息。这是处理ScrollView里面嵌套类似ListView列表布局的时候的一种解决方案。
但是在我遇见的这个项目里面,并不能这样处理。 

上面的提到的两种解决思路很明确,如果想要ListView正常展示就需要确定ListView的高度,这个很重要。 

所以首先,我需要在布局文件中设置ListView的高度,是一个明确的数值。设置高度之后,如果ListView中的数据的Item总高度超过ListView所设置的高度,就可以复用View了。但是这只是解决了ListView的显示问题,ListView与ScrollView的滑动冲突,并没有解决。 

要解决滑动的冲突,最主要的是确定禁止ScrollView截断点击事件的时机,然后来分析有哪些时机。

  • ScrollView在未滑动到底部时候,如果点击并滑动ListView时候,ListView是不能滑动的,不禁止。
  • 如果ScrollView滑动到底部,且ListView已经到顶部,继续下拉ListView,其实会拉动ScrollView,不禁止。
  • 如果ScrollView滑动到底部,用户向上滑,ListView滑动,禁止ScrollView截断点击事件能力

很明显,在判断禁止ScrollView截断点击事件时机的时候,需要知道ScrollView是否滑动到了底部。于是,重写了ScrollView的ScrollChanged()方法,来判断ScrollView是否滑动到底部(SDK API 23版本中ScrollView可以设置setOnScrollChangeListener()来监听滑动的变化,但是之前版本不支持,为了兼容,自己需要重写)。

@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt){
  super.onScrollChanged(l,t,oldl,oldt);
  // 滑动的距离加上本身的高度与子View的高度对比
  if(t + getHeight() >= getChildAt(0).getMeasuredHeight()){
    // ScrollView滑动到底部
    if(mOnScrollToBottomListener != null) {
      mOnScrollToBottomListener.onScrollToBottom();
    }
  } else {
    if(mOnScrollToBottomListener != null) {
      mOnScrollToBottomListener.onNotScrollToBottom();
    }
  }
}

public void setScrollToBottomListener(OnScrollToBottomListener listener) {
  this.mOnScrollToBottomListener= listener;
}

public interface OnScrollToBottomListener {
  void onScrollToBottom();
  void onNotScrollToBottom();
}

有了思路,而且ScrollView滑动到底部的标识也可以拿到,下面就可以直接来解决滑动冲突了,直接看代码。

mScrollView.setScrollToBottomListener(new BottomScrollView.OnScrollToBottomListener() {
  @Override
  public void onScrollToBottom() {
    isSvToBottom = true;
  }

  @Override
  public void onNotScrollToBottom() {
    isSvToBottom = false;
  }
});

mListView.setOnTouchListener(new View.OnTouchListener() {
  @Override
  public boolean onTouch(View v, MotionEvent event) {
    int action = event.getAction();

    if(action == MotionEvent.ACTION_DOWN) {
      mLastY = event.getY();
    }
    if(action == MotionEvent.ACTION_MOVE) {
      int top = mListView.getChildAt(0).getTop();
      float nowY = event.getY();
      if(!isSvToBottom) {
        // 允许scrollview拦截点击事件, scrollView滑动
        mScrollView.requestDisallowInterceptTouchEvent(false);
      } else if(top == 0 && nowY - mLastY > THRESHOLD_Y_LIST_VIEW) {
        // 允许scrollview拦截点击事件, scrollView滑动
        mScrollView.requestDisallowInterceptTouchEvent(false);
      } else {
        // 不允许scrollview拦截点击事件, listView滑动
        mScrollView.requestDisallowInterceptTouchEvent(true);
      }
    }
    return false;
  }
});

相对于其他的控件来说,ListView和ScrollView之间的滑动冲突更难解决,但其实在实际使用中并不推荐ScrollView里面嵌套ListView,一旦业务复杂,很容易出现各种UI和业务逻辑冲突的错误。

4. 运行效果

由于地图加入比较麻烦,所以在Demo中并没有引入地图。看一下运行效果。

图-2 运行效果

5. 总结

本篇文章只是提供一种解决方法的思路,在具体的场景下,交互往往是贴合具体业务需求的。但是不管怎么样,找出点击事件截断和处理的时机是最重要的,围绕这个关键点,总能找出相应的解决方法。

附上Demo工程地址:Demo

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


推荐阅读
  • 深入解析Android自定义View面试题
    本文探讨了Android Launcher开发中自定义View的重要性,并通过一道经典的面试题,帮助开发者更好地理解自定义View的实现细节。文章不仅涵盖了基础知识,还提供了实际操作建议。 ... [详细]
  • 深入理解Cookie与Session会话管理
    本文详细介绍了如何通过HTTP响应和请求处理浏览器的Cookie信息,以及如何创建、设置和管理Cookie。同时探讨了会话跟踪技术中的Session机制,解释其原理及应用场景。 ... [详细]
  • 360SRC安全应急响应:从漏洞提交到修复的全过程
    本文详细介绍了360SRC平台处理一起关键安全事件的过程,涵盖从漏洞提交、验证、排查到最终修复的各个环节。通过这一案例,展示了360在安全应急响应方面的专业能力和严谨态度。 ... [详细]
  • 2023年京东Android面试真题解析与经验分享
    本文由一位拥有6年Android开发经验的工程师撰写,详细解析了京东面试中常见的技术问题。涵盖引用传递、Handler机制、ListView优化、多线程控制及ANR处理等核心知识点。 ... [详细]
  • 在维护公司项目时,发现按下手机的某个物理按键后会激活相应的服务,并在屏幕上模拟点击特定坐标点。本文详细介绍了如何使用ADB Shell Input命令来模拟各种输入事件,包括滑动、按键和点击等。 ... [详细]
  • Startup 类配置服务和应用的请求管道。Startup类ASP.NETCore应用使用 Startup 类,按照约定命名为 Startup。 Startup 类:可选择性地包括 ... [详细]
  • 自己用过的一些比较有用的css3新属性【HTML】
    web前端|html教程自己用过的一些比较用的css3新属性web前端-html教程css3刚推出不久,虽然大多数的css3属性在很多流行的浏览器中不支持,但我个人觉得还是要尽量开 ... [详细]
  • 百度搜索结果链接提取工具 UrlGetter V1.43
    该工具专为获取百度搜索引擎的结果页面中的网址链接而设计,能够解析并转换为原始URL。通过正则表达式匹配技术,精准提取网页链接,并提供详细的使用说明和下载资源。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 本文详细介绍了如何构建一个高效的UI管理系统,集中处理UI页面的打开、关闭、层级管理和页面跳转等问题。通过UIManager统一管理外部切换逻辑,实现功能逻辑分散化和代码复用,支持多人协作开发。 ... [详细]
  • 本文详细探讨了HTTP 500内部服务器错误的成因、解决方案及其在Web开发中的影响。通过对具体案例的分析,帮助读者理解并解决此类问题。 ... [详细]
  • 百度服务再次遭遇技术问题,疑似DNS解析故障
    近日晚间,百度多项在线服务出现加载异常,包括移动端搜索在内的多个功能受到影响。初步迹象表明,问题可能与DNS服务器解析有关。 ... [详细]
  • 探讨如何使用工具或方法来自定义百度网盘的提取码,以提高文件分享的安全性和便捷性。 ... [详细]
  • 本文探讨了在 ASP.NET MVC 5 中实现松耦合组件的方法。通过分离关注点,应用程序的各个组件可以更加独立且易于维护和测试。文中详细介绍了依赖项注入(DI)及其在实现松耦合中的作用。 ... [详细]
  • 网易严选Java开发面试:MySQL索引深度解析
    本文详细记录了网易严选Java开发岗位的面试经验,特别针对MySQL索引相关的技术问题进行了深入探讨。通过本文,读者可以了解面试官常问的索引问题及其背后的原理。 ... [详细]
author-avatar
丽sd园印章
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有