事件分发:弹性滑动、滑动冲突

129 篇文章 0 订阅

什么是事件分发,分开解释即可,MotionEvent事件,当点击了,怎样处理,就是分发。

下面3个方法来共同完成

  • dispatchTouchEvent:用来进行事件的分发,如果事件能够传递给当前View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent和View的dispatchTouchEvent方法的影响,表示是否当消耗当前事件
public boolean dispatchTouchEvent(MotionEvent ev){
    boolean consume = false;
    if(onInterceptTouchEvent(ev)){
        consume = onTouchEvent(ev);
    }else{
        consume = child.dispatchTouchEvent(ev);
    }

    return consume;

}
  • onInterceptTouchEvent:用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件;
  • onTouchEvent:在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件。


1。弹性滑动的实现

使用Scroller

  • 典型代码如下

    Scroller scroller = new Scroller(mContext);
    
    //缓慢滚动到指定位置
    private void smoothScrollTo(int destX,int destY){
        int scrollX = getScrollX();
        int delta = destX-scrollX;
        //1000ms内滑向destX,效果就是慢慢滑动
        mScroller.startScroll(scrollX,0,delta,0,1000);
        invalidate();
    }
    
    @Override
    public void computeScroll(){
        if(mScroller.computeScrollOffset()){
            scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
            postInvalidate();
        }
    }
computeScrollOffset方法判主要是断滚动是否还在继续,Scroller类中的mFinished属性与之对应。如果滚动完成了,那么computeScrollOffset方法返回false,mFinished为true;如果滚动没有完成那么computeScrollOffset方法返回true,mFinished为false。
startScroll主要是用来开启滚动,startX和startY是滑动的起点,dx和dy是滑动的距离,duration是滑动时间。并且必须注意的是,仅仅调用startScroll方法是无法让View开始滑动的,因为它内部并没有做相关的滑动,仅仅是保存了我们传递的几个参数。那么View到底要怎么滑动呢?其实就是在调用该方法时,应该再调用invalidate方法,即重绘。重绘的时候则会导致View中的draw方法执行,draw方法中又会去调用computeScroll方法。遗憾的是computeScroll方法是一个空实现,因此需要我们自己去实现该方法。但是应该注意的是,在computeScroll方法中,我们应该调用View的postInvalidate方法来进行第二次重绘,和第一次重绘一样,也是为了导致computeScroll方法被调用,如此反复,知道滑动结束。
computeScroll方法也比较容易理解,它的代码格式一般都是固定的,主要是判断滑动是否完成,如果没有完成就去向Scroller获取当前的scrollX和scrollY,然后通过scrollTo实现滑动。最后再调用postInvalidate来让滑动持续进行。

首先startScroll开启滑动,并调用invalidate方法进行重绘。当View重绘后会在draw方法中调用computeScroll,而computeScroll又去向Scroller获取当前的scrollX和scrollY,然后通过scrollTo实现滑动。接着又调用postInvalidate进行第二次重绘,和第一次重绘一样,也是为了导致computeScroll方法被调用。然后继续向Scroller获取当前的scrollX和scrollY,并通过scrollTo滑动到最新的位置,如此反复,知道整个滑动结束。

弹性滑动的另外一种实现,
动画本身就是一种弹性滑动,所以对于动画实现的滑动不去专门讨论瞬间滑动和弹性滑动。通过动画让View进行滑动,其实质就是让View进行平移,其主要是操作translationX和translationY属性
R.anim.translate :
<?xml version="1.0" encoding="utf-8"?><set xmlns:android="http://schemas.android.com/apk/res/android" android:fillAfter="true" android:zAdjustment="normal"> <translate android:duration="2000" android:fromXDelta="0" android:fromYDelta="0" android:interpolator="@android:anim/linear_interpolator" android:toXDelta="200" android:toYDelta="200" /></set>

//加载动画 animation = AnimationUtils.loadAnimation(this, R.anim.translate);
mImageView.startAnimation(animation);

2。View的滑动冲突

产生:界面中存在内外两层同时可以滑动的View

外部拦截法和内部拦截法

  • 外部拦截法:事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要就不拦截。外部拦截法需要重写父容器的onInterceptTouchEvent方法

    //分别记录上次滑动的坐标(onInterceptTouchEvent)
    private int mLastXIntercept = 0;
    private int mLastYIntercept = 0;
    
    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:
    
                int deltaX = x - mLastXIntercept;
                int deltaY = y - mLastYIntercept;
    
                if(父容器需要当前点击事件){
                    intercepted = true;
                }else{
                    intercepted = false;
                }
                break;
            case MotionEvent.ACTION_UP:
                intercepted = false;
                break;
            default:
                break;
        }
    
        mLastXIntercept = x;
        mLastYIntercept = y;
    
        return intercepted;
    
    }
    

    上述代码是外部拦截法的典型逻辑,针对不同的滑动冲突只需修改“父容器需要当前点击事件”这个条件即可

  • 内部拦截法:指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就消费,否则就交由父容器进行处理。这种方法需要配合requestDisallowInterceptTouchEvent方法才能正常工作

    //分别记录上次滑动的坐标
    private int mLastX = 0;
    private int mLastY = 0;
    
    public boolean dispatchTouchEvent(MotionEvent event){
    
        int x = (int)event.getX();
        int y = (int)event.getY();
    
        switch(event.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(event);
    
    }
    

    上述代码是内部拦截法的典型代码,面对不同的滑动策略时只需要更改“父容器需要此类点击事件”这个条件即可

    父元素所做的修改如下

    public boolean onInterceptTouchEvent(MotionEvent event){
        int action = event.getAction();
        if(action == MotionEvent.ACTION_DOWN){
            return false;
        }else{
            return true;
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值