`

Android 滑动冲突处理

阅读更多
常见的有两种
  •      一个控件横向滑动,另一个控件竖向滑动。比如:类似ViewPager,每个页面里面是ListView,不过,我们不用去处理滑动处理,ViewPager内部已经处理好了。
  •      一个控件竖向滑动,另一个控件也竖向滑动。比如:ScollView里面包裹着ListView,这也是需要着重理解掌握的。



 

Android内置了Scoller,用于实现渐进式的滑动。
  •  创建Scroller对象:Scroller mScroller = new Scroller(context);
  •  重写computeScroll()方法;
  •  最后,在我们的smoothScrollTo方法中调用startScroll方法;
Scroller mScroller = new Scroller(context);
@Override
public void computeScroll() {
    if (mScroller.computeScrollOffset()){
        scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
        postInvalidate();
    }
}
//这里smoothScrollTo实现的是x方向的平滑
public void smoothScrollTo(int destX,int destY){
    int scrollX=getScrollX();
    int deltaX=destX-scrollX;
    mScroller.startScroll(scrollX,0,deltaX,0,1000);//startScroll函数的形参分别表示:起始位置的x坐标、起始位置的y坐标、x方向要移动的距离、y方向上要移动的距离以及整个滑动过程完成所需的时间。
    invalidate();
}
 
Scroller.computeScrollOffset():这个方法返回true,表示滑动还未结束,还要继续滑动,false表示滑动已结束。
scrollTo:注意,这里其实用的本质是scrollTo(...),所以要求View是有内容的View,平滑移动的也是其内容,而非View本身位置的改变。
postInvalidate():用在非UI线程,其内部最后也是调用invalidate()(同理,invalidate用在UI线程)。
startScroll:当我们构造一个Scroller对象并且调用它的startScroll方法时,Scroller内部其实什么都没做,它只是保存了我们传递的几个参数。
原理:
     Scroller到底时如何让View渐进滑动的呢?答案就是startScroll方法下面的invalidate方法。invalidate方法会导致View重绘,在View的draw方法中又会去调用computeScroll方法,computeScroll方法在View中是一个空实现,因此需要我们自己去实现。我们代码中已经实现了computeScroll方法,所以正是如此,它才能动起来。
总结:
     Scroller本身不能实现View的滑动,我们需要借助View的computeScroll方法,并让它不断的刷新重绘。
 
滑动冲突解决方式:
  •      外部拦截法
    •       通过onInterceptTouchEvent(MotionEvent event)
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
  boolean intercepted = false;
  int x = (int) event.getX();
  int y = (int) event.getY();
  switch (event.getAction()) {
  case MotionEvent.ACTION_DOWN:
        intercepted = false;
        break;
  case MotionEvent.ACTION_MOVE:
  if (父容器需要当前点击事件) {
     intercepted = true;
  } else {
     intercepted = false;
  }
  break;
  case MotionEvent.ACTION_UP:
  intercepted = false;
  break;
  default:
        break;            
  }
  mLastXIntercept = x;
  mLastYIntercept = y;
  return intercepted;
}
/*
  注意:
  1. ACTION_DOWN 一定返回false,不要拦截它,否则根据View事件分发机制,后续ACTION_MOVE与ACTION_UP事件都将默认交给父View去处理!
  2. 原则上ACTION_UP 也要返回false,如果返回true,并且滑动事件交给子View处理,那么子View将接收不到ACTION_UP事件,子View的onClick事件也就无法触发。
  而父View不一样,如果父View在ACTION_MOVE中开始拦截事件,那么后续ACTION_UP也将默认交给父View处理。
  */
 
  •      内部拦截法
    •      通过dispatchTouchEvent(MotionEvent event)
    •      内部拦截法就是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交由父容器进行处理,这种方法和Android的事件分发机制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,使用起来较外部拦截法较复杂。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
  int x= (int) ev.getX();
  int y= (int) ev.getY();
  switch (ev.getAction()){
  case MotionEvent.ACTION_DOWN:
        parent.requestDisallowInterceptTouchEvent(true);
        break;
  case MotionEvent.ACTION_MOVE:
  int deltaX=x-mLastX;
  int deltaY=y-mLastY;
  if (父容器需要此类点击事件){
  parent.requestDisallowInterceptTouchEvent(false);
  }
        break;
  case MotionEvent.ACTION_UP:
        break;
  default:
        break;
  }
  mLastX=x;
  mLastY=y;
  return super.dispatchTouchEvent(ev);
}
/*
内部拦截法要求父View不能拦截ACTION_DOWN事件,由于ACTION_DOWN不受FLAG_DISALLOW_INTERCEPT标志位控制,一旦父容器拦截ACTION_DOWN那么所有的事件都不会传递给子View。
*/
 
 
ViewPager源码分析
ViewPager的滑动冲突处理:
     我们知道,ViewGroup是在onInterceptTouchEvent函数中决定是否拦截触摸事件,那么我们就去学习一下ViewPager的onInterceptTouchEvent函数。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
 
    //1. 触摸动作
    final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
 
    //2. 时刻要注意触摸是否已经结束
    if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
        //3. Release the drag.
        if (DEBUG) Log.v(TAG, "Intercept done!");
        //4. 重置一些跟判断是否拦截触摸相关变量
        resetTouch();
        //5. 触摸结束,无需拦截
        return false;
    }
 
    //6. 如果当前不是按下事件,我们就判断一下,是否是在拖拽切换页面
    if (action != MotionEvent.ACTION_DOWN) {
        //7. 如果当前是正在拽切换页面,直接拦截掉事件,后面无需再做拦截判断
        if (mIsBeingDragged) {
            if (DEBUG) Log.v(TAG, "Intercept returning true!");
            return true;
        }
        //8. 如果标记为不允许拖拽切换页面,我们就"放过"一切触摸事件
        if (mIsUnableToDrag) {
            if (DEBUG) Log.v(TAG, "Intercept returning false!");
            return false;
        }
    }
    //9. 根据不同的动作进行处理
    switch (action) {
        //10. 如果是手指移动操作
        case MotionEvent.ACTION_MOVE: {
 
            //11. 代码能执行到这里,就说明mIsBeingDragged==false,否则的话,在第7个注释处就已经执行结束了
 
            //12.使用触摸点Id,主要是为了处理多点触摸
            final int activePointerId = mActivePointerId;
            if (activePointerId == INVALID_POINTER) {
                //13.如果当前的触摸点id不是一个有效的Id,无需再做处理
                break;
            }
            //14.根据触摸点的id来区分不同的手指,我们只需关注一个手指就好
            final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
            //15.根据这个手指的序号,来获取这个手指对应的x坐标
            final float x = MotionEventCompat.getX(ev, pointerIndex);
            //16.在x轴方向上移动的距离
            final float dx = x - mLastMotionX;
            //17.x轴方向的移动距离绝对值
            final float xDiff = Math.abs(dx);
            //18.同理,参照16、17条注释
            final float y = MotionEventCompat.getY(ev, pointerIndex);
            final float yDiff = Math.abs(y - mInitialMotionY);
            if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
 
            //19.判断当前显示的页面是否可以滑动,如果可以滑动,则将该事件丢给当前显示的页面处理
            //isGutterDrag是判断是否在两个页面之间的缝隙内移动
            //canScroll是判断页面是否可以滑动
            if (dx != 0 && !isGutterDrag(mLastMotionX, dx) &&
                    canScroll(this, false, (int) dx, (int) x, (int) y)) {
                mLastMotionX = x;
                mLastMotionY = y;
                //20.标记ViewPager不去拦截事件
                mIsUnableToDrag = true;
                return false;
            }
            //21.如果x移动距离大于最小距离,并且斜率小于0.5,表示在水平方向上的拖动
            if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) {
                if (DEBUG) Log.v(TAG, "Starting drag!");
                //22.水平方向的移动,需要ViewPager去拦截
                mIsBeingDragged = true;
                //23.如果ViewPager还有父View,则还要向父View申请将触摸事件传递给ViewPager
                requestParentDisallowInterceptTouchEvent(true);
                //24.设置滚动状态
                setScrollState(SCROLL_STATE_DRAGGING);
                //25.保存当前位置
                mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop :
                        mInitialMotionX - mTouchSlop;
                mLastMotionY = y;
                //26.启用缓存
                setScrollingCacheEnabled(true);
            } else if (yDiff > mTouchSlop) {//27.否则的话,表示是竖直方向上的移动
                if (DEBUG) Log.v(TAG, "Starting unable to drag!");
                //28.竖直方向上的移动则不去拦截触摸事件
                mIsUnableToDrag = true;
            }
            if (mIsBeingDragged) {
                // 29.跟随手指一起滑动
                if (performDrag(x)) {
                    ViewCompat.postInvalidateOnAnimation(this);
                }
            }
            break;
        }
        //30.如果手指是按下操作
        case MotionEvent.ACTION_DOWN: {
 
            //31.记录按下的点位置
            mLastMotionX = mInitialMotionX = ev.getX();
            mLastMotionY = mInitialMotionY = ev.getY();
            //32.第一个ACTION_DOWN事件对应的手指序号为0
            mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
            //33.重置允许拖拽切换页面
            mIsUnableToDrag = false;
            //34.标记开始滚动
            mIsScrollStarted = true;
            //35.手动调用计算滑动的偏移量
            mScroller.computeScrollOffset();
            //36.如果当前滚动状态为正在将页面放置到最终位置,
            //且当前位置距离最终位置足够远
            if (mScrollState == SCROLL_STATE_SETTLING &&
                    Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) {
                //37. 如果此时用户手指按下,则立马暂停滑动
                mScroller.abortAnimation();
                mPopulatePending = false;
                populate();
                mIsBeingDragged = true;
                //38.如果ViewPager还有父View,则还要向父View申请将触摸事件传递给ViewPager
                requestParentDisallowInterceptTouchEvent(true);
                //39.设置当前状态为正在拖拽
                setScrollState(SCROLL_STATE_DRAGGING);
            } else {
                //40.结束滚动
                completeScroll(false);
                mIsBeingDragged = false;
            }
 
            if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
                    + " mIsBeingDragged=" + mIsBeingDragged
                    + "mIsUnableToDrag=" + mIsUnableToDrag);
            break;
        }
 
        case MotionEventCompat.ACTION_POINTER_UP:
            onSecondaryPointerUp(ev);
            break;
    }
 
    //41.添加速度追踪
    if (mVelocityTracker == null) {
        mVelocityTracker = VelocityTracker.obtain();
    }
    mVelocityTracker.addMovement(ev);
 
 
    //42.只有在当前是拖拽切换页面时我们才会去拦截事件
    return mIsBeingDragged;
}
 
我们看看ViewPager是如何决定是拦截还是不拦截,从源码上面看出,但斜率小于0.5时,则要拦截,否则不拦截,斜率是什么情况呢?高中数学可知,在第一象限中,越靠近y轴的直线,斜率越大,越靠近x轴直线斜率越小,先看简单图示:
(何为斜率:斜率就是倾斜程度,斜率一般用k表示,斜率k值为直线与x轴正方向夹角的正切值,若直线上任意两点为(x1,y1)、(x2,y2)则直线斜率k=(y2-y1)/(x2-x1).直线平行于y轴,斜率不存在,平行于x轴,斜率为0)


 
  • 大小: 11.1 KB
  • 大小: 7.4 KB
分享到:
评论

相关推荐

    Android滑动冲突解决的例子

    在Android开发中,滑动冲突是一个常见的问题,尤其是在设计复杂用户界面时。滑动冲突通常发生在两个或多个可滑动的视图(如ScrollView、HorizontalScrollView、ViewPager等)重叠或者相邻时,用户可能尝试同时在这些...

    Android listview和viewpager解决冲突 滑动冲突

    Android listview viewpager 滑动 跳动 冲突解决 ListView中嵌套ViewPage有或者滑动手势冲突解决 blog 地址 : http://blog.csdn.net/aaawqqq/article/details/43824631

    Android滑动冲突

    在Android开发中,滑动冲突是一个常见的问题,尤其是在布局复杂,包含多个可滑动组件时。滑动冲突指的是两个或更多视图同时响应用户的滑动操作,导致用户体验不佳或者功能失效。本文将深入探讨Android中的滑动冲突...

    滑动冲突处理示例代码

    总的来说,滑动冲突处理是Android开发中的一个重要课题,通过理解并掌握内部拦截和外部拦截,开发者能够创建更加流畅和直观的用户界面。本示例代码提供了一个很好的起点,帮助开发者深入理解这两种方法,并在自己的...

    Android滑动冲突解决

    总的来说,解决Android滑动冲突需要理解触摸事件的处理机制,以及各个滚动组件的工作原理。通过合理的布局设计和事件分发策略,可以有效地避免和解决滑动冲突,提升用户体验。在实际开发中,一定要充分测试各种滑动...

    View的滑动冲突处理方案

    在Android开发中,View的滑动冲突处理是一个常见的问题,特别是在多层嵌套布局中,如ScrollView、HorizontalScrollView、ViewPager等。滑动冲突通常发生在两个或更多可滑动的视图试图同时响应用户的滑动手势时。本...

    Android滑动冲突问题的解决方法

    在Android开发中,滑动冲突是一个常见的问题,特别是在涉及到多个可滑动组件(如ScrollView、ListView、ViewPager等)交互时。滑动冲突主要分为两种类型:同方向滑动冲突和不同方向滑动冲突。 1. 同方向滑动冲突: ...

    Android滑动冲突解决例子

    总的来说,解决Android滑动冲突需要开发者对触摸事件的处理有深入理解,并结合实际应用场景选择合适的解决方案。无论是手动处理事件分发,还是利用系统提供的特性或第三方库,关键在于确保用户体验的流畅性和一致性...

    android滑动事件冲突解决

    "android滑动事件冲突解决"这个主题正是针对这种问题展开的。当两个可以竖直方向滑动的View(例如ScrollView、ListView或者HorizontalScrollView)被嵌套在一起时,它们可能会互相干扰,导致滑动不顺畅或者无法正常...

    android完美解决listView与ScrollView滑动冲突

    总之,解决Android中的ListView与ScrollView滑动冲突问题,需要理解事件分发机制,以及掌握各种滚动控件的特性和API。通过合理的设计和编程,可以实现两者无缝协作,提供流畅的用户体验。在提供的压缩包文件...

    Android嵌套滑动冲突的解决方法

    使用 NestedScrollView 的好处是它可以自动处理滑动事件,并且可以避免滑动冲突的问题。例如,在布局中使用 NestedScrollView 作为父布局,然后嵌套 RecyclerView,就可以解决滑动冲突的问题。 四、使用 ...

    滑动冲突demo

    `NestedScrollView`是Android官方提供的支持嵌套滚动的视图,它可以自动处理大部分滑动冲突情况。而`CoordinatorLayout`则提供了更高级的布局协调机制,通过`Behavior`接口可以自定义视图间的交互行为,从而避免滑动...

    RecyclerView嵌套RecyclerView滑动冲突

    总的来说,RecyclerView的嵌套滑动冲突是Android开发中的常见问题,但通过理解和掌握滑动事件的处理机制,以及利用Android提供的接口和第三方库,我们可以有效地解决这个问题,实现流畅的用户体验。在Demo1中,你...

    全网唯一有用的解决方案!彻底解决VerticalViewPager嵌套RecyclerView引起的滑动冲突!

    4. **第三方库**:有一些第三方库,如`androidx.viewpager2.widget.ViewPager2`,它已经内置了解决滑动冲突的逻辑,可以替代VerticalViewPager,使得在RecyclerView内部的滑动更加平滑。 5. **使用...

    安卓的滑动冲突处理嵌套滑动一个demo搞定

    在安卓开发中,滑动冲突(Scrolling Collision)是一个常见且重要的问题,特别是在涉及嵌套滚动组件时。...对于复杂的滑动交互设计,掌握滑动冲突处理是提升用户体验的关键,也是安卓开发者必备的技能之一。

    View滑动冲突外部解决法Demo

    总结来说,这个Demo是一个实践性的教学案例,旨在帮助开发者理解并掌握Android中滑动冲突的解决策略,特别是处理ScrollView和ListView嵌套情况下的滑动交互。通过学习和分析这个Demo,开发者能够更好地控制界面的...

    Android上下左右滑动支持同时滑动斜向任意方向滑动大图片浏览大图表浏览

    此外,标签中的“Android滑动冲突”提示了这个问题的常见性和解决它的复杂性。解决滑动冲突通常需要对Android的触摸事件处理机制有深入理解,包括如何处理MotionEvent的ACTION_DOWN、ACTION_MOVE和ACTION_UP等,以及...

    RecyclerView 嵌套 RecyclerView 滑动冲突解决

    RecyclerView 嵌套 RecyclerView 滑动冲突解决----------------------- 本人亲测

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

    对于ViewPager的滑动冲突处理,通常情况ViewPager自身可以处理左右滑动,我们只需要防止ScrollView在水平方向上截断事件即可。可以设置一个OnTouchListener,当检测到水平滑动时,不让ScrollView拦截事件,允许...

    Android-一种处理嵌套和非嵌套滑动冲突的解决方案

    总的来说,解决Android中嵌套和非嵌套滑动冲突需要理解事件分发机制,并结合合适的工具和策略。无论是通过原生API还是利用第三方库,目标都是实现用户友好的交互,让每个视图都能正确响应用户的触摸操作。在实际项目...

Global site tag (gtag.js) - Google Analytics