View事件分发源码解析

一、View的位置

1、Left、Right、Top、Bottom

left = view.getLeft(); // 表示view的左边距离父控件左边的距离

right= view.getRight(); // 表示view的右边距离父控件左边的距离

top= view.getTop(); // 表示view的上边距离父控件顶部的距离

bottom= view.getBottom(); // 表示view的下边距离父控件顶部的距离

1111.png

2、X、Y、TranslationX、TranslationY

translationX = view.set/getTranslationX(); // 表示属性动画平移的X方向距离

translationY = view.set/getTranslationY(); // 表示属性动画平移的Y方向距离

x = view.getX();

y = view.getY();

其中X和Y表示view左上角的坐标,这个坐标会随着translation而发生变化。

3、结论如下

(1)上面的8个参数值都是相对于父控件的值

(2)Left、Right、Top、Bottom都是基于原始位置的值,哪怕View发生过平移,这个值依然不会发生改变。

(3)X = getLeft() + getTranslationX();

Y = getTop() + getTranslationY();

Width = getRight() - getLeft();

Height = getBottom() - getTop();

二、滑动参数

1、MotionEvent

MotionEvent.ACTION_DOWN ---- 手指按下屏幕

MotionEvent.ACTION_MOVE ---- 手指在屏幕移动

MotionEvent.ACTION_UP ---- 手指从屏幕松开

MotionEvent.ACTION_CANCEL ---- 非人为意外的中断行为

MotionEvent.getX/getY,获取到触摸点的坐标,这个坐标是相对于当前View左上角的坐标值。

MotionEvent.getRawX/getRawY,获取到触摸点的坐标,这个坐标是相对于手机屏幕左上角的坐标值。

2、TouchSlop

TouchSlop是系统所能识别出的最小滑动距离,跟设备有关系,不同设备不一样。获取方式是,ViewConfiguration.get(context).getScaledTouchSlop();

3、VeloctiyTracker

用于追踪手指的滑动速度,包括水平和垂直方向的滑动速度。

VelocityTracker vt = VelocityTracker.obtain();

vt.addMovement(event);

当我们想计算当前的滑动速度时,可采用下面的方式获取:

vt.computeCurrentVelocity(1000); // 必须先调用计算,1000表示时间,单位毫秒

int xVelocity = (int) vt.getXVeloctiy(); // 获取水平滑动速度

int yVelocity = (int) vt.getYVeloctiy(); // 获取垂直滑动速度

不需要的时候,需要释放和回收内存:

if (vt != null) {

vt.clear();

vt.recycler();

}

关于时间参数,这里再说明一下:

假设1000ms内X方向滑动了100个像素,那么xVelocity = 100像素/1S = 100

假设我们设置的是100ms,同样也是滑动了100个像素,那么xVelocity =100像素/0.1S = 1000

此外,速度是可以负数的,因为滑动距离从左往右是正数,从右往左滑就是负数了。

4、GestureDetector

手势检测,用于辅助检测用户的单击、双击、长按、滑动等行为。使用方法如下:

GestureDetector gd = new GestureDetector(this, new SimpleOnGestureListener() {
  public boolean onFling() {
  }
});

public boolean onTouchEvent(MotionEvent event) {
  return gd.onTouchEvent(event);
}

实际开发中,很多时候都是在View的OnTouchEvent里实现相应的监听或者事件处理,如果单纯只是监听类似于双击这种行为的话,可以选择GestureDetoctor

5、ScrollTo、ScrollBy

(1)ScrollBy最终调用的是ScrollTo方法,只不是By里传的参数是偏移量,而To里传的参数里坐标位置

(2)这两个方法都可以让View实现滑动,但是要注意的是,并不是View本身滑动,而是让其里面的内容进行滑动

(3)下图中,正方形框表示View,黄色表示View的内容。第2个图,scrollX=100,相当于该View的内容向左滑动100像素,即黄色区域向左了100像素。第3个图,scrollY=100,表示View的内容向上滑动了100像素。

2222.png

三、弹性滑动

1、Scroller方式
/**
 * 开启滚动
 */
public void startScroll(Context context, int startX, int dx) {
    if (mScroller == null) {
        mScroller = new Scroller(context);
    }
    // scroller源码,startScroll并没有做什么事情,仅仅只是保存了滚动的起始位置
    // 滚动的距离以及时间等变量
    mScroller.startScroll(startX, 0, dx, 0, 1000);
    // 这里调用invalidate去刷新的目的是,让其重新执行View onDraw方法
    // 而onDraw方法刷新会触发其父类的computeScroll方法
    // 父类的computeScroll则是一个空实现,所以最终会调用我们重写的computeScroll方法
    invalidate();
}

@Override
public void computeScroll() {
    // 核心就是mScroller.computeScrollOffset()方法了
    if (mScroller != null && mScroller.computeScrollOffset()) {
        scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
        postInvalidate();
    }
}

/**
* mScroller的这个方法根据时间的流逝的比例来判断应该滚动多少像素
* 比如我们startScroll的时候记录了当前的时间A
* 经过onDraw,执行computeScroll后,computeScrollOffset被调用的时候记录当前时间B
* (B - A) / total,就能知道整体流逝的比例,从而计算出应该滚动的距离
*/
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;
       .....
        }
    }
    else {
        mCurrX = mFinalX;
        mCurrY = mFinalY;
        mFinished = true;
    }
    return true;
}
2、属性动画
// 属性动画,方式1
        ObjectAnimator.ofFloat(view, "translationX", 0, 100)
                .setDuration(500)
                .start();
        // 属性动画,方式2
        ValueAnimator va = ValueAnimator.ofFloat(0, 100);
        va.setDuration(500);
        va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value = animation.getAnimatedFraction();
                view.scrollTo((int) value, 0);
//                又或者直接setLayoutParams的形式
//                FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) view.getLayoutParams();
//                params.leftMargin = (int) value;
//                view.setLayoutParams(params);
            }
        });
        va.start();

3、Thread+Handler的形式也可以

四、触摸事件分发机制

1、事件分发顺序

Activity -> ViewGroup -> View,即1个点击事件发生后,事件先传到Activity、再传到ViewGroup、最终再传到View。即Activity(dispatchTouchEvent) -> Window -> ViewGroup(dispatchTouchEvent)

2、事件分发顺序的源码
/**
* Activity的dispatchTouchEvent方法
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
        // 这个调用可以忽略,我们看主干
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        // 调用window的superDispatchTouchEven方法
        // 我们知道在Android里,PhoneWindow是Window类的唯一实现类,
        // 所以实际上调用的就是PhoneWindow的superDispatchTouchEvent
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
}
/**
* PhoneWindow的superDispatchTouchEvent方法
*/
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
        // 直接调用DecorView的superDispatchTouchEvent方法,
        // DecorView也就是我们屏幕显示的最根布局的FrameLayout(ViewGroup)
        return mDecor.superDispatchTouchEvent(event);
}

基于上述的源码,我们可以知道,事件经由Activity传递Window再到ViewGroup的dispatchTouchEvent进行处理。
当整个dispatchTouchEvent的返回值是true时,Activity的事件分发也就完成了。当整个dispatchTouchEvent的返回值是false时(相当于其里的ViewGroup或者View的dispatchTouchEvent或者onTouchEvent返回false,没有消费这个事件),会调用Activity的onTouchEvent方法。这个方法的源码如下:

/**
* Activity的onTouchEvent方法
*/
public boolean onTouchEvent(MotionEvent event) {
        // 判断这个触摸事件是否超出边界。如果超出,Activity直接finish掉。
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }
        return false;
}
3、ViewGroup的dispatchTouchEvent源码
/**
* ViewGroup的dispatchTouchEvent方法
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
            // 按下时,重置一些标记位及状态
            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();
            }
            // 这里是重点,如果是按下事件或者mFirstTouchTarget != null就可以进去。
            // 如果事件能够传递到子View去处理(消费),那么mFirstTouchTarget就指向该子View
            // 换句话,如果上一个事件被拦截了,又或者子View没有消费这个事件,最终当前这个ViewGroup消费掉时
            // 那么mFirstTouchTarget就等于null,那么intercepted=true
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                // 禁止拦截功能是否开启,默认是false,即关闭的。可通过requestDisallowIntercept方法来控制这个开关。
                if (!disallowIntercept) {
                    // 调用onInterceptTouchEvent方法
                    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;
            }

            // Check for cancelation.
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

            // Update list of touch targets for pointer down, if needed.
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
            // 如果intercepted=true,表示拦截,那么就不会执行其内部的分发给子View的代码
            // 如果intercepted=false,表示不拦截,就会执行分发给子View的代码
            if (!canceled && !intercepted) {
                // 分发给子View的代码,这里的主要逻辑是遍历找出其子View
                // 然后child.dispatchTouchEvent()把事件传递给子View
            }
            if (mFirstTouchTarget == null) {
                // 看下面的方法
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
            }
}
private boolean dispatchTransformedTouchEvent(MotionEvent event) {
    // 核心代码
    if (child == null) {
           handled = super.dispatchTouchEvent(event);
    } else {
           handled = child.dispatchTouchEvent(event);
    }
}

通过上述源码得到以下结论:
1、down事件时,必然会走onInterceptTouchEvent方法的。但是,move和up事件就不一定了。
2、如果onInterceptTouchEvent返回true,表示事件被拦截。那么就不会进入分发给子View的代码。而是调用dispatchTransformedTouchEvent方法,最终由于child==null,而调用其父类View的dispatchTouchEvent,View的dispatchTouchEvent方法下面会讲到,最核心的代码就几行,其中一行是会调用onTouchEvent方法。而子类ViewGroup里重写了onTouchEvent。所以这就解释了为什么我们拦截了事件之后,会执行其自己的onTouchEvent方法了。
3、如果onInterceptTouchEvent返回false,表示事件往下分发。从上面的分析可以知道,就会进入分发给子View的代码,调用child.dispatchTouchEvent方法进行事件传递。
4、可通过requestDisallowIntercept方法来控制onInterceptTouchEvent的调用情况

4、View的dispatchTouchEvent源码
/**
* View的dispatchTouchEvent方法,我们只看重点代码
*/
public boolean dispatchTouchEvent(MotionEvent event) {
        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            // 这里会调用当前View设置的OnTouchListener的onTouch方法,其返回值相当重要
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
            // 如果没有设置OnTouchListener或者OnTouchListener的onTouch方法返回是false,那么自己的onTouchEvent就会被调用
            // 否则,onTouchEvent将不执行。
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
        return result;
}

通过上述源码得到以下结论:
1、OnTouchListener的onTouch方法会比View自己的onTouchEvent方法优先执行。
2、OnTouchListener的onTouch方法返回值如果是true,表示事件在此消费,那么onTouchEvent将不被调用。
3、OnTouchListener的onTouch方法返回值如果是false,或者没有设置OnTouchListener,表示事件在此没有被消费,那么就会传递到onTouchEvent里。

5、View的onTouchEvent源码
/**
* View的onTouchEvent方法
*/
public boolean onTouchEvent(MotionEvent event) {
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();

                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                // 核心代码就是这里,up的时候,会执行点击事件的回调
                                if (!post(mPerformClick)) {
                                    performClickInternal();
                                }
                            }
                        }
                    }
                    break;
            }
            return true;
        }
        return false;
}

通过上述源码得到以下结论:
1、只有调View.onTouchEvent方法被调用了,在UP的时候才有可能会触发OnClickListener的onClick事件
2、onTouchListener > onTouchEvent > onClickListener

6、Demo验证结论

准备工作:
(1)自定义了一个MyLinearLayout,重写了dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent。
(2)同时也自定义了一个MyTextView,重写了dispatchTouchEvent和onTouchEvent(返回true)

// 默认情况下,点击MyTextView的日志如下: 
I: ===MyLinearLayout===dispatchTouchEvent===0 
I: ===MyLinearLayout===onInterceptTouchEvent===0 
I: ===MyTextView===dispatchTouchEvent===0 
I: ===MyTextView===onTouchEvent===0 
I: ===MyLinearLayout===dispatchTouchEvent===2 
I: ===MyLinearLayout===onInterceptTouchEvent===2 
I: ===MyTextView===dispatchTouchEvent===2
I: ===MyTextView===onTouchEvent===2 
I: ===MyLinearLayout===dispatchTouchEvent===2 
I: ===MyLinearLayout===onInterceptTouchEvent===2 
I: ===MyTextView===dispatchTouchEvent===2 
I: ===MyTextView===onTouchEvent===2 
I: ===MyLinearLayout===dispatchTouchEvent===1 
I: ===MyLinearLayout===onInterceptTouchEvent===1
I: ===MyTextView===dispatchTouchEvent===1 
I: ===MyTextView===onTouchEvent===1
// 把MyLinearLayout的onInterceptTouchEvent在down的时候返回true, 
// onTouchEvent返回true的日志如下: 
I: ===MyLinearLayout===dispatchTouchEvent===0 
I: ===MyLinearLayout===onInterceptTouchEvent===0 
I: ===MyLinearLayout===onTouchEvent===0 
I: ===MyLinearLayout===dispatchTouchEvent===2 
I: ===MyLinearLayout===onTouchEvent===2 
I: ===MyLinearLayout===dispatchTouchEvent===2 
I: ===MyLinearLayout===onTouchEvent===2 
I: ===MyLinearLayout===dispatchTouchEvent===1 
I: ===MyLinearLayout===onTouchEvent===1
// MyLinearLayout的onInterceptTouchEvent的返回false,onTouchEvent返回true, 
// MyTextView的onTouchEvent返回false的日志如下: 
I: ===MyLinearLayout===dispatchTouchEvent===0 
I: ===MyLinearLayout===onInterceptTouchEvent===0 
I: ===MyTextView===dispatchTouchEvent===0 
I: ===MyTextView===onTouchEvent===0 
I: ===MyLinearLayout===onTouchEvent===0
I: ===MyLinearLayout===dispatchTouchEvent===2 
I: ===MyLinearLayout===onTouchEvent===2
I: ===MyLinearLayout===dispatchTouchEvent===2 
I: ===MyLinearLayout===onTouchEvent===2 
I: ===MyLinearLayout===dispatchTouchEvent===1 
I: ===MyLinearLayout===onTouchEvent===1
// MyLinearLayout的onInterceptTouchEvent的返回false,onTouchEvent返回true, 
// MyTextView的onTouchEvent返回true,并且给MyTextView设置 
// onTouchListener(返回false)和onClickListener,日志如下: 
I: ===MyLinearLayout===dispatchTouchEvent===0 
I: ===MyLinearLayout===onInterceptTouchEvent===0 
I: ===MyTextView===dispatchTouchEvent===0 
I: ===MyTextView===OnTouchListener===0 
I: ===MyTextView===onTouchEvent===0 
I: ===MyLinearLayout===dispatchTouchEvent===2 
I: ===MyLinearLayout===onInterceptTouchEvent===2 
I: ===MyTextView===dispatchTouchEvent===2 
I: ===MyTextView===OnTouchListener===2 
I: ===MyTextView===onTouchEvent===2 
I: ===MyLinearLayout===dispatchTouchEvent===2 
I: ===MyLinearLayout===onInterceptTouchEvent===2
 I: ===MyTextView===dispatchTouchEvent===2 
 I: ===MyTextView===OnTouchListener===2 
 I: ===MyTextView===onTouchEvent===2 
 I: ===MyLinearLayout===dispatchTouchEvent===1 
 I: ===MyLinearLayout===onInterceptTouchEvent===1 
 I: ===MyTextView===dispatchTouchEvent===1 
 I: ===MyTextView===OnTouchListener===1 
 I: ===MyTextView===onTouchEvent===1 
 // 发现并没有执行onClickListener,是因为虽然onTouchListener返回false 
 // onTouchEvent有被调用,但是被调用的是重写的MyTextView里的onTouchEvent, 
 // 要想onClickListener被执行,父类的super.onTouchEvent必须被调用才行。
// MyLinearLayout的onInterceptTouchEvent的返回false,onTouchEvent返回true, 
// MyTextView的onTouchEvent返回super.onTouchEvent,并且给MyTextView设置 
// onTouchListener(返回false)和onClickListener,日志如下: 
I: ===MyLinearLayout===dispatchTouchEvent===0 
I: ===MyLinearLayout===onInterceptTouchEvent===0 
I: ===MyTextView===dispatchTouchEvent===0 
I: ===MyTextView===OnTouchListener===0 
I: ===MyTextView===onTouchEvent===0 
I: ===MyLinearLayout===dispatchTouchEvent===2 
I: ===MyLinearLayout===onInterceptTouchEvent===2 
I: ===MyTextView===dispatchTouchEvent===2 
I: ===MyTextView===OnTouchListener===2 
I: ===MyTextView===onTouchEvent===2 
I: ===MyLinearLayout===dispatchTouchEvent===2 
I: ===MyLinearLayout===onInterceptTouchEvent===2 
I: ===MyTextView===dispatchTouchEvent===2 
I: ===MyTextView===OnTouchListener===2 
I: ===MyTextView===onTouchEvent===2 
I: ===MyLinearLayout===dispatchTouchEvent===1 
I: ===MyLinearLayout===onInterceptTouchEvent===1 
I: ===MyTextView===dispatchTouchEvent===1 
I: ===MyTextView===OnTouchListener===1 
I: ===MyTextView===onTouchEvent===1 
I: ===MyTextView===OnClickListener=== // OnClickListener已经被执行
// MyLinearLayout的onInterceptTouchEvent的返回false,onTouchEvent返回true, 
// MyTextView的onTouchEvent返回true,并且给MyTextView设置 
// onTouchListener(返回true)和onClickListener,日志如下: 
I: ===MyLinearLayout===dispatchTouchEvent===0 
I: ===MyLinearLayout===onInterceptTouchEvent===0 
I: ===MyTextView===dispatchTouchEvent===0 
I: ===MyTextView===OnTouchListener===0 
I: ===MyLinearLayout===dispatchTouchEvent===2 
I: ===MyLinearLayout===onInterceptTouchEvent===2 
I: ===MyTextView===dispatchTouchEvent===2 
I: ===MyTextView===OnTouchListener===2 
I: ===MyLinearLayout===dispatchTouchEvent===2 
I: ===MyLinearLayout===onInterceptTouchEvent===2 
I: ===MyTextView===dispatchTouchEvent===2 
I: ===MyTextView===OnTouchListener===2 
I: ===MyLinearLayout===dispatchTouchEvent===1 
I: ===MyLinearLayout===onInterceptTouchEvent===1 
I: ===MyTextView===dispatchTouchEvent===1 
I: ===MyTextView===OnTouchListener===1 // 直接被onTouchListener消费掉,事件到达不了onTouchEvent

五、滑动冲突解决

1、常见的滑动冲突类型

同方向滑动冲突、不同方向滑动冲突、混合滑动冲突

2、外部拦截法解决滑动冲突
/**
* 重写外层控件的onInterceptTouchEvent
* 假设外层是垂直滚动的,内层有一个横向滚动的scrollview
* 那么当垂直方向滑动距离大于横向滑动距离时,我们就认为
* 用户的滑动意向是垂直方向,这个时候就拦截掉这个事件让自己(即外层的控件)去处理
*/
@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:
        case MotionEvent.ACTION_UP:
            intercepted = false;
            break;
        case MotionEvent.ACTION_MOVE:
            if(父布局需要滑动) // 根据冲突的不同情况自己判断
                intercepted = true;
            else
                intercepted = false;
            break;
        default:
            break;
    }
    mLastX = x; // 用于判断是否拦截的条件
    mLastY = y; // 用于判断是否拦截的条件
    return intercepted;
}
3、内部拦截法解决滑动冲突
/**
* 重写内层控件的dispatchTouchEvent
* 假设外层是垂直滚动的,内层有一个横向滚动的scrollview
* 
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
    int y = (int) ev.getY();
    int x = (int) ev.getX();
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            // 请求父容器下一个事件不要拦截
            getParent().requestDisallowInterceptTouchEvent(true);
            break;
        case MotionEvent.ACTION_MOVE:
            int deltaX = x - mLastX;
            int deltaY = y - mLastY;
            if (父容器需要这个事件) {
                // 请求父容器拦截掉此事件,不让要其传递到子View这
                getParent().requestDisallowInterceptTouchEvent(false);
            }
            break;
        case MotionEvent.ACTION_UP:
            break;
    }
    return super.dispatchTouchEvent(ev);
}
/**
* 重写外层即父容器的onInterceptTouchEvent
* 对比于ViewGroup分发的源码理解一下
* down的时候会调用ViewGroup的onInterceptTouchEvent,这个时候不拦截
* 事件就传递到内层View的dispatchTouchEvent里,然后requestDisallowInterceptTouchEvent(true);
* 设置状态位为true。当Move事件来到ViewGroup的dispatchTouchEvent时,不再调用onInterceptTouchEvent
* 方法,intercepted=false表示不拦截,那么事件继续传递到内层View的dispatchTouchEvent里,符合外层
* 的条件,这个时候,就requestDisallowInterceptTouchEvent(false)来要求父控件来拦截掉这个事件。接着当
* 下一个move事件到来时,会执行ViewGroup的onInterceptTouchEvent方法,此方法返回true,所以事件就此没
* 法往下传递了,相当于自己处理了。
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent event){
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        return false;
    }
    return true;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值