热门标签 | 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();
    }
  }
}

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

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


推荐阅读
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 胡蜂能进行逻辑推理的研究成果
    最新研究表明,胡蜂具备一定的逻辑推理能力,能够进行传递性推理。研究人员通过实验发现,胡蜂在避免电击的测试中,能够正确选择符合逻辑的选项。这项研究成果对于了解无脊椎动物的认知能力具有重要意义,也为解析胡蜂社会结构的进化提供了线索。 ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • 本文介绍了Java的集合及其实现类,包括数据结构、抽象类和具体实现类的关系,详细介绍了List接口及其实现类ArrayList的基本操作和特点。文章通过提供相关参考文档和链接,帮助读者更好地理解和使用Java的集合类。 ... [详细]
  • 本文介绍了电流源并联合并的方法,以及谐振电路的原理。谐振电路具有很强的选频能力,通过将电感和电容连接在一起,电流和电压会产生震荡。谐振频率的大小取决于电感和电容的大小,而电路中的电阻会逐渐降低震荡的幅度。电阻和电容组成的电路中,当电容放完电后,电阻两端的电压为0,电流不再流过电容。然而,电感是一种特殊的器件,当有电流流过时,线圈会产生感应磁场,阻止电流的流动,从而使电流不会减小。 ... [详细]
  • 标题: ... [详细]
  • 单点登录原理及实现方案详解
    本文详细介绍了单点登录的原理及实现方案,其中包括共享Session的方式,以及基于Redis的Session共享方案。同时,还分享了作者在应用环境中所遇到的问题和经验,希望对读者有所帮助。 ... [详细]
  • 本文介绍了在Docker容器技术中限制容器对CPU的使用的方法,包括使用-c参数设置容器的内存限额,以及通过设置工作线程数量来充分利用CPU资源。同时,还介绍了容器权重分配的情况,以及如何通过top命令查看容器在CPU资源紧张情况下的使用情况。 ... [详细]
  • 集合的遍历方式及其局限性
    本文介绍了Java中集合的遍历方式,重点介绍了for-each语句的用法和优势。同时指出了for-each语句无法引用数组或集合的索引的局限性。通过示例代码展示了for-each语句的使用方法,并提供了改写为for语句版本的方法。 ... [详细]
  • Python SQLAlchemy库的使用方法详解
    本文详细介绍了Python中使用SQLAlchemy库的方法。首先对SQLAlchemy进行了简介,包括其定义、适用的数据库类型等。然后讨论了SQLAlchemy提供的两种主要使用模式,即SQL表达式语言和ORM。针对不同的需求,给出了选择哪种模式的建议。最后,介绍了连接数据库的方法,包括创建SQLAlchemy引擎和执行SQL语句的接口。 ... [详细]
  • position属性absolute与relative的区别和用法详解
    本文详细解读了CSS中的position属性absolute和relative的区别和用法。通过解释绝对定位和相对定位的含义,以及配合TOP、RIGHT、BOTTOM、LEFT进行定位的方式,说明了它们的特性和能够实现的效果。同时指出了在网页居中时使用Absolute可能会出错的原因,即以浏览器左上角为原始点进行定位,不会随着分辨率的变化而变化位置。最后总结了一些使用这两个属性的技巧。 ... [详细]
  • 开发笔记:Docker 上安装启动 MySQL
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Docker上安装启动MySQL相关的知识,希望对你有一定的参考价值。 ... [详细]
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 本文介绍了Java的公式汇总及相关知识,包括定义变量的语法格式、类型转换公式、三元表达式、定义新的实例的格式、引用类型的方法以及数组静态初始化等内容。希望对读者有一定的参考价值。 ... [详细]
author-avatar
mobiledu2502874965
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有