Android

View 事件处理

2017-04-28  本文已影响25人  GordenNee

View

1.View 事件体系

1.1 基础知识

1.2 View的滑动

View的滑动主要有三种方式

1.3 弹性滑动

目前上面的平移方式都很粗暴,视觉上看会很粗暴,需要一个平缓的滑动,而不是瞬间完成。弹性滑动的基本原理是将一次大的华东分成若干个小的滑动。

实现方法也有三种

Scroller mScroller = new Scroller(MainApplication.getContext());

private void smoothScroll(int destX, int destY) {
    int scrollX = getScrollX();
    int deltaX = destX - scrollX;
    mScroller.startScroll(scrollX, 0, deltaX, 0, 500);
    invalidate();
}

@Override
public void computeScroll() {
    super.computeScroll();
    if (mScroller.computeScrollOffset()) {
        scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
        invalidate();
    }
}

首先看下startScroll() 

​```java
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
      mMode = SCROLL_MODE;
      mFinished = false;
      mDuration = duration;
      mStartTime = AnimationUtils.currentAnimationTimeMillis();
      mStartX = startX;
      mStartY = startY;
      mFinalX = startX + dx;
      mFinalY = startY + dy;
      mDeltaX = dx;
      mDeltaY = dy;
      mDurationReciprocal = 1.0f / (float) mDuration;
  }

​ startScroll中只是初始化相关参数,并没有实质功能.实际的实现实在invalidate().invalidate()方法会引起View的重绘,也就是会调用onDraw()方法,onDraw()又会调用ViewGroup中的computScroll()方法,但是该方法是个空的方法,需要自己去重写实现。看下我们实现的方法内容。很简单,首先获取Scroller的scrollX和scrollY,然后调用scrollTo移动到指定位置。接着再去调用invalidate()发起第二次重绘.....循环下去。

​ 那么这个scrollX是怎么变化的,可以看到在scrollTo前调用了computeScrollOffset()方法:

    /**
     * Call this when you want to know the new location.  If it returns true,
     * the animation is not yet finished.
     */ 
    public boolean computeScrollOffset() {
        if (mFinished) {
            return false;
        }

        int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
    
        if (timePassed < mDuration) {
            switch (mMode) {
            case SCROLL_MODE:
                final float x = mInterpolator.getInterpolation(timePassed *                                            mDurationReciprocal);
                mCurrX = mStartX + Math.round(x * mDeltaX);
                mCurrY = mStartY + Math.round(x * mDeltaY);
                break;
            }
        }
    }

首先获取得到已经滑动的时间,接着需要注意一个变量mDurationReciprocal ,它是什么呢,我们在startScroll时有初始化它mDurationReciprocal = 1.0f / (float) mDuration 。

那么timePassed * mDurationReciprocal 就是已经滑动的时间占据总滑动时间的百分比,接着大家就可以理解了,计算得到当前要移动到的位置,并返回true,如果已经滑动结束,那么就会返回false,不在进行下面的滑动。

1.4 View的事件分发机制

   public boolean dispatchTouchEvent(MotionEvent ev) {      
     
                 // Handle an initial down.
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                // Throw away all previous state when starting a new touch gesture.
                // The framework may have dropped the up or cancel event for the previous gesture
                // due to an app switch, ANR, or some other state change.
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }
     
     
        // Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }
     }

理解下这段源码:两种情况下会去判断是否要拦截。第一种就是按下时MotionEvent.ACTION_DOWN,第二种是mFirstTouchTarget!=null ,这个mFirstTouchTarget 可以这样理解,它会在当前view不处理,交给子view处理时将mFirstTouchTarget 置为false。在交给过子view处理过后,后面每一次都要进行判断是否拦截。也就是只要当前的ViewGroup拦截一次事件,那么后面不需要进行onInterceptEvent判断是否需要拦截,直接进行拦截。 再换个说法,只要当前ViewGroup处理过一次事件(除开按下),那么后面的事件都由他处理。

这里还有个标志位的判断:FLAG_DISALLOW_INTERCEPT;这个标志位一旦被设置,那么它将无法在拦截除ACTION_DOWN之外的事件,因为ACTION_DOWN会重置该标志位。因此在ACTION_DOWN时,必然会调用onInterceptEvent。可以看到 // Handle an initial down 这部分代码对标志位进行了重置.

接下来会去循环 判断子元素是否能够接收到事件,能否接收到有了两个条件:1,在上一级view的区域内2,没有在播放动画。 子元素会去调用它的dispatchTouchEvent。如果当前的子元素的dispatchTouchEvent返回false说明没有处理,那么就会接着for循环,调用同一级的下一个子元素的dispatchTouchEvent;如果的当前的dispatchTouchEvent 返回true;说明子元素处理了改时间,那么就会将mFirstTouchTarget 赋值。也就是我们最开始说的逻辑。

如果循环结束事件都没有被处理,有两种情况:1.ViewGroup没有子元素 2,子元素处理了点击事件,但是在dispatchTouchEvent 返回了false,因为这个方法可以重写。 这两种情况下,viewGroup 交给他的父类即View的dispatchTouchEvent来处理,最终会调用到onTouchEvent来处理。

1.5 滑动冲突处理

滑动冲突主要有三种情况:

解决方案:

​ 基本思想,根据需求如果需要外部滑动时,就在外部进行拦截,否则不拦截。

具体的实践也有两种实现方式:

1.6 总结点

上一篇下一篇

猜你喜欢

热点阅读