当前位置: 首页 > 编程笔记 >

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

卢深
2023-03-14
本文向大家介绍浅谈Android View滑动冲突的解决方法,包括了浅谈Android View滑动冲突的解决方法的使用技巧和注意事项,需要的朋友参考一下

引言

这一篇文章我们就通过介绍滑动冲突的规则和一个实例来更加深入的学习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<String> datas = new ArrayList<>();
    for (int i = 0; i < 50; i++) {
      datas.add("name " + i);
    }

    ArrayAdapter<String> 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();
    }
  }
}

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

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

 类似资料:
  • 本文向大家介绍Android listview的滑动冲突解决方法,包括了Android listview的滑动冲突解决方法的使用技巧和注意事项,需要的朋友参考一下 Android listview的滑动冲突解决方法 在Android开发的过程中,有时候会遇到子控件和父控件都要滑动的情况,尤其是当子控件为listview的时候。就比如在一个ScrollView里有一个listview,这种情况较常见

  • 本文向大家介绍Android滑动事件冲突的解决方法,包括了Android滑动事件冲突的解决方法的使用技巧和注意事项,需要的朋友参考一下 滑动是Android中不可缺少的一部分,多个滑动必然会产生冲突,比如我们最常见的是ScrollView中嵌套了ListView,一般做法是计算出ListView的总高度,这样就不用去滑动ListView了。又比如一个ViewPager嵌套Fragment,Frag

  • 本文向大家介绍浅谈spring boot 集成 log4j 解决与logback冲突的问题,包括了浅谈spring boot 集成 log4j 解决与logback冲突的问题的使用技巧和注意事项,需要的朋友参考一下 现在很流行springboot的开发,小编闲来无事也学了学,开发过程中遇见了log4j日志的一个小小问题,特此记载。 首先在pox.xml中引入对应的maven依赖: 然后在src/r

  • 本文向大家介绍解决ViewPager和SlidingPaneLayout的滑动事件冲突问题,包括了解决ViewPager和SlidingPaneLayout的滑动事件冲突问题的使用技巧和注意事项,需要的朋友参考一下 问题描述: ViewPager和SlidingPaneLayout的滑动事件冲突。 问题分析: 在手指左右滑动时,SlidingPaneLayout会屏蔽ViewPager的滑动事件。

  • Windows 用tutorial进行的操作 若要进行pull操作,请右击tutorial目录,并选择‘拉取’。 用tutorial进行的操作 在以下画面点击‘确定’。 用tutorial进行的操作 我们看到画面上的警告信息表示自动合并失败。请点击‘关闭’以退出窗口。 用tutorial进行的操作 若您确认变更,请点击‘Yes’。 用tutorial进行的操作 TortoiseGit告诉我们:因"

  • 在上一个页面我们提及到,执行合并即可自动合并Git修改的部分。但是,也存在无法自动合并的情况。 如果远程数据库和本地数据库的同一个地方都发生了修改的情况下,因为无法自动判断要选用哪一个修改,所以就会发生冲突。 Git会在发生冲突的地方修改文件的内容,如下图。所以我们需要手动修正冲突。 ==分割线上方是本地数据库的内容, 下方是远程数据库的编辑内容。 如下图所示,修正所有冲突的地方之后,执行提交。