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

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

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


推荐阅读
  • 本文详细介绍了 Dockerfile 的编写方法及其在网络配置中的应用,涵盖基础指令、镜像构建与发布流程,并深入探讨了 Docker 的默认网络、容器互联及自定义网络的实现。 ... [详细]
  • 深入解析Spring Cloud Ribbon负载均衡机制
    本文详细介绍了Spring Cloud中的Ribbon组件如何实现服务调用的负载均衡。通过分析其工作原理、源码结构及配置方式,帮助读者理解Ribbon在分布式系统中的重要作用。 ... [详细]
  • Hadoop入门与核心组件详解
    本文详细介绍了Hadoop的基础知识及其核心组件,包括HDFS、MapReduce和YARN。通过本文,读者可以全面了解Hadoop的生态系统及应用场景。 ... [详细]
  • 本文详细探讨了Java中StringBuffer类在不同情况下的扩容规则,包括空参构造、带初始字符串和指定初始容量的构造方法。通过实例代码和理论分析,帮助读者更好地理解StringBuffer的内部工作原理。 ... [详细]
  • 本文探讨了领域驱动设计(DDD)的核心概念、应用场景及其实现方式,详细介绍了其在企业级软件开发中的优势和挑战。通过对比事务脚本与领域模型,展示了DDD如何提升系统的可维护性和扩展性。 ... [详细]
  • 深入了解 Windows 窗体中的 SplitContainer 控件
    SplitContainer 控件是 Windows 窗体中的一种复合控件,由两个可调整大小的面板和一个可移动的拆分条组成。本文将详细介绍其功能、属性以及如何通过编程方式创建复杂的用户界面。 ... [详细]
  • 实体映射最强工具类:MapStruct真香 ... [详细]
  • 深入解析 Apache Shiro 安全框架架构
    本文详细介绍了 Apache Shiro,一个强大且灵活的开源安全框架。Shiro 专注于简化身份验证、授权、会话管理和加密等复杂的安全操作,使开发者能够更轻松地保护应用程序。其核心目标是提供易于使用和理解的API,同时确保高度的安全性和灵活性。 ... [详细]
  • 本文探讨了在Linux系统上使用Docker时,通过volume将主机上的HTML5文件挂载到容器内部指定目录时遇到的403错误,并提供了解决方案和详细的操作步骤。 ... [详细]
  • 探讨如何真正掌握Java EE,包括所需技能、工具和实践经验。资深软件教学总监李刚分享了对毕业生简历中常见问题的看法,并提供了详尽的标准。 ... [详细]
  • 作为一名专业的Web前端工程师,掌握HTML和CSS的命名规范是至关重要的。良好的命名习惯不仅有助于提高代码的可读性和维护性,还能促进团队协作。本文将详细介绍Web前端开发中常用的HTML和CSS命名规范,并提供实用的建议。 ... [详细]
  • 本文探讨了在 ASP.NET MVC 5 中实现松耦合组件的方法。通过分离关注点,应用程序的各个组件可以更加独立且易于维护和测试。文中详细介绍了依赖项注入(DI)及其在实现松耦合中的作用。 ... [详细]
  • Startup 类配置服务和应用的请求管道。Startup类ASP.NETCore应用使用 Startup 类,按照约定命名为 Startup。 Startup 类:可选择性地包括 ... [详细]
  • 网易严选Java开发面试:MySQL索引深度解析
    本文详细记录了网易严选Java开发岗位的面试经验,特别针对MySQL索引相关的技术问题进行了深入探讨。通过本文,读者可以了解面试官常问的索引问题及其背后的原理。 ... [详细]
  • 自己用过的一些比较有用的css3新属性【HTML】
    web前端|html教程自己用过的一些比较用的css3新属性web前端-html教程css3刚推出不久,虽然大多数的css3属性在很多流行的浏览器中不支持,但我个人觉得还是要尽量开 ... [详细]
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社区 版权所有