View的事件体系

一. View基础知识

  • view的位置参数
  • MotionEvent和TouchSlop
  • Velocity,GentureDetecor和Scroller

二. 什么是View

View是Android中所有的组件的基类,包括系统提供的控件,如:Button,TextView,Relativelayout和Listview还是自定义控件他们的共同基类都是view,所以说.View是一种界面层的控件的一种抽象,它代表了一个控件.除了View,还有ViewGroup,从名字来看,它可以翻译为控件组,ViewGroup里面可以包括多个控件,即一组View,在Android的设计中,ViewGroup也继承View,View本身就可以是单个控件也可以是多个控件组成的一组控件,这种关系就View树的结构.

例如:Button显然是一个View,但LinearLayout不但是一个View而且还是一个ViewGroup,而ViewGroup内部可以有子View的,这个子View可能还是View
复制代码

三. View的位置参数

  • top 左上角纵坐标
  • left 左上角横坐标
  • right 右下角横坐标
  • bottom 右下角纵坐标 这些坐标都是以父容器为参考系的,因此它是一种相对坐标
width = right - left
height = bottom - top
复制代码

那么如何得到这四个参数呢?

Left = getLeft()
Right =getRight()
Top = getTop()
Bottom = getBottom()
复制代码

从 Android 3.0 开始增加了这几个额外的参数 x,y.translationX 和 teanslationY , 其中 x, y 是 View 左上角的坐标.而 translationX 和 teanslationY 是 View 左上角相对于的偏移量. 这几个参数也是相对于父容器的偏移量. translationX 和 teanslationY 默认值是 0 , View 也为 他们提供默认的set/get 方法

x = left + translationX
y = top + teanslationY
复制代码

四. MotionEvent和TouchSlop

①.MotionEvent

  • 手指触摸屏幕后产生一系列事件
  • ACTION_DOWN -- 手指刚接触屏幕
  • ACTION_MOVE -- 手指在屏幕上移动
  • ACTION_UP -- 手指从屏幕松开的一瞬间
  • 正常情况,一次手指触摸屏幕的行为会触发一系列的点击事件
  • 点击屏幕后离开松开 事件序列 DOWN --> UP
  • 点击屏幕滑动一会再松开 DOWN --> MOVE --> .. --> MOVE --> UP
  • 通过 MotionEvent 对象我们可以获取 点击事件发生的 x 和 y 坐标,为此系统提供了 两组方法:
  • getX / getY (当前View 左上角 x 坐标 和 y坐标)
  • getRawX / getRawY (屏幕左上角 x 坐标 和 y 坐标)

②.TouchSlop

TouchSlop 是系统所能识别出被认为是滑动的最小距离 ,当手指在屏幕上滑动时,如果两次滑动之间的距离小于这个常量,那么系统不认为他是滑动的,这个常量值和设备有关,不同的设备这个值可能有所差异 如何回去这个常量呢? ViewConfiguration.get(getContext()).getScaledTouchTop()

五. Velocity,GentureDetecor和Scroller

①.Velocity

速度追踪,用来追踪手指滑动过程中的速度,包括水平速度和垂直速度,他的使用过程很简单

  • 在View 的 onTouchEvent方法中追踪当前点击事件的速度
final VelocityTracker obtain = VelocityTracker.obtain();
obtain.addMovement(event);
复制代码
  • 获取当前滑动速度
  obtain.computeCurrentVelocity(1000);
  final int xVelocity = (int) obtain.getXVelocity();
  final int yVelocity = (int) obtain.getYVelocity();
复制代码

当不需要的时候,注意重置回收

   obtain.recycle();
   obtain.clear();
复制代码

②.GentureDetecor

手势检测,用户辅助检测用户单击,滑动,长按,双击行为

  • 创建一个GentureDetecor对象并实现OnGestureListener接口,根据需要我们还可以实现 OnDoubleTapListener 从而能够监听双击行为
  final GestureDetector detector = new GestureDetector(this);
  // 解决长按屏幕无法拖动的现象
  detector.setIsLongpressEnabled(false);
复制代码
  • 接管目标 View 的 onTouchEvent 方法,在待监听 View 的 onTouchEvent 方法中添加如下实现
   final boolean consume = detector.onTouchEvent(event);
   return consume;
复制代码
方法名描述所属接口
onDown(触摸放开)手指轻轻触摸屏幕一瞬间,由 1 个 ACTION_DOWN 触发OnGestureListener
onShowPress(触摸未松动)手指轻轻触摸屏幕,尚未松动或拖动,由1个 ACTION_DOWN 触发 * 注意 和 onDown() 区别是,强调的是没有松开或拖动的状态OnGestureListener
onSingleTapUp(单击)手指松开,伴随着1个 MotionEvent ACTION_UP 而触发,这是单击行为OnGestureListener
onLongPress (长按)用户长久地按着屏幕不放OnGestureListener
onFling(快速滑动)用户按下触摸屏,快速滑动松开,由1个 ACTION_DOWN ,多个 ACTION_MOVE 和 ACTION_UP触发,这就是快速滑动行为OnGestureListener
OnDoubleTab(双击)双击,由两次单击组成,它不可能和 OnSingleTabConfirm 共存OnDoubleTabListener
OnSingleTabConfirm(严格单击行为)严格单击行为,只响应一次OnDoubleTabListener
OnDoubleTabEvent (双击)双击行为OnDoubleTabListener
onScroll(拖动)手指按下屏幕并拖动,由1个ACTION_DOWN,多个ACTION_MOVE触发,这就是拖动行为OnGestureListener

③.Scroller

弹性滑动对象

   final Scroller scroller = new Scroller(this);
复制代码

六. View的滑动

如何实现View的滑动

①. scrollTo / scrollBy

    public void scrollTo(int x, int y) {
        if (mScrollX != x || mScrollY != y) {
            int oldX = mScrollX;
            int oldY = mScrollY;
            mScrollX = x;
            mScrollY = y;
            invalidateParentCaches();
            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            if (!awakenScrollBars()) {
                postInvalidateOnAnimation();
            }
        }
    }

 
    public void scrollBy(int x, int y) {
        scrollTo(mScrollX + x, mScrollY + y);
    }
复制代码
  • View 施加平滑效果实现View的滑动

  • 改变View的LayoutParam 使得 View重新布局, 从而实现滑动

②.使用动画

<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha
        android:fromAlpha="1.0"
        android:toAlpha="1.0"
        android:duration="550"/>
</set>
复制代码

为了兼容3.0以下版本我们需要引入 nineoldAndroid,但是注意一点的是View动画只能改变View的影像,并不能改变View的布局参数

③.改变布局参数

	ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mMore.getLayoutParams();
		params.width += 100;
		params.leftMargin += 100;
		mMore.requestLayout();
		//或者 mMore.setLayoutParams(params);
复制代码

③.各种滑动方式对比

  • scrollTo / scrollBy

适合对View内容的滑动

  • 动画

适用于没有交互的 View 和 实现复杂的动画效果

  • 改变布局

操作复杂,适用于交互的View

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        final int x = (int) event.getRawX();
        final int y = (int) event.getRawY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX =	x - mLastX;
                int deltaY =	y - mLastY;
                final int translationX = (int)ViewHelper.getTranslationX(this) + deltaX;
                final int translationY = (int)ViewHelper.getTranslationX(this) + deltaY;
                ViewHelper.setTranslationX(this,translationX);
                ViewHelper.setTranslationY(this,translationY);
                break;
            case MotionEvent.ACTION_UP:
                break;
            default:
                break;
           

        }
        mLastX = x;
        mLastY = y;
        return true;
    }
复制代码

④.弹性滑动

如何实现弹性滑动?

将一次大的滑动分成若干的小滑动,并在一定的时间内完成

⑤.使用Scroller

    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;
    }
复制代码
  • Scroller 如何让 View 滑动的?
    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;
            case FLING_MODE:
                final float t = (float) timePassed / mDuration;
                final int index = (int) (NB_SAMPLES * t);
                float distanceCoef = 1.f;
                float velocityCoef = 0.f;
                if (index < NB_SAMPLES) {
                    final float t_inf = (float) index / NB_SAMPLES;
                    final float t_sup = (float) (index + 1) / NB_SAMPLES;
                    final float d_inf = SPLINE_POSITION[index];
                    final float d_sup = SPLINE_POSITION[index + 1];
                    velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
                    distanceCoef = d_inf + (t - t_inf) * velocityCoef;
                }

                mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;
                
                mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
                // Pin to mMinX <= mCurrX <= mMaxX
                mCurrX = Math.min(mCurrX, mMaxX);
                mCurrX = Math.max(mCurrX, mMinX);
                
                mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
                // Pin to mMinY <= mCurrY <= mMaxY
                mCurrY = Math.min(mCurrY, mMaxY);
                mCurrY = Math.max(mCurrY, mMinY);

                if (mCurrX == mFinalX && mCurrY == mFinalY) {
                    mFinished = true;
                }

                break;
            }
        }
        else {
            mCurrX = mFinalX;
            mCurrY = mFinalY;
            mFinished = true;
        }
        return true;
    }
复制代码

⑥.通过动画

TODO: 博主暂时也没有弄明白

        ObjectAnimator.ofFloat(view, "translationX", 0, 100).setDuration(100).start();
		final int startX = 0;
		final int startY = 100;
        final ValueAnimator animator = ValueAnimator.ofInt(0, 1).setDuration(1000);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                final float fraction = animator.getAnimatedFraction();
                mHome.scrollTo(startX + (deltaX * fraction),0);
            }
        });
        animator.start();
复制代码

⑦.使用延时策略

通过发送一系列的延时消息从而达到一种渐进式的效果

    private static final int MESSAGE_SCROLL_TO = 1;
    private static final int FRAME_COUNT = 30;
    private static final int DELAYED_TIME = 33;

    private int mCount = 0;


    protected Handler mHandler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            int what = msg.what;
            switch (what) {

                case MESSAGE_SCROLL_TO:
                    mCount++;
                    if (mCount <= FRAME_COUNT) {
                        final float fraction = mCount / (float) FRAME_COUNT;
                        final int scrollX = (int) (fraction * 100);
                        mAutoLogin.scrollTo(scrollX,0);
                        mHandler.sendMessageDelayed(MESSAGE_SCROLL_TO,DELAYED_TIME);
                    }
                    break;
}
}
复制代码

七. View的事件分发机制

①. 点击事件的传递规则

点击事件传递过程中涉及一个很重要的API,就是MotionEvent.所谓的点击事件的事件分发就是对MotionEvent事件的分发过程,当一个MotionEvent产生后,系统需要将这个事件传递给具体的View,而这个传递的过程就是分发的过程. 中间风阀过程涉及三个重点的方法:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        return super.dispatchTouchEvent(ev);
    }

复制代码

进行事件分发,如果事件能够传递给当前View,那么此方法一定会被调用,返回结果受当前View的TouchEvent和下级View的dispatchTouchEvent影响,表示正在消耗当前事件

public boolean onIntercrptTouchEvent(MotionEvent ev) {}
复制代码

用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一事件序列中,此方法不会被调用

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean consume = false;
        if (onInterceptTouchEvent(event)){
            consume = onTouchEvent(event);
        }else {
            consume = child.dispatchTouchEvent(event);
        }
        return consume;
    }
复制代码

对于一个根View而言,当点击事件发生以后,它的dispatchevent就会被调用,如果onInterceptTouchEvent方法返回true,就表示拦截此事件,接着事件就会交给ViewGroup处理,即他的TouchEvent会被调用,如果这个ViewGrop返回false,那么表示不拦截这个事件,这时,当前事件就会传递给他的子元素,接着子元素的dispatchEvent方法就会被调用,如此反复直至事件被消耗完毕 当一个View处理事件时,它设置了onTouchListener,那么onTouchListener中的onTouch方法就会被调用,这时事件如何处理还要看onTouch的返回值,如果返回false,则当前view的ontouchEvent方法会被调用,如果返回为true,那么onTouchEvent方法将不会被调用.由此可见onTouchListener的优先级比 onTouchEvent还高,在 onTouchEvent 方法中 , 如果当前设置的有onClickListener,那么 onClick 方法会被调用,平时我们常用的 onClickListerner 优先级最低 点击事件的传递顺序在ui层的优先顺序表现为 Activity -> Window -> View

  1. ViewGroup 默认不拦截任何事件, Android源码中 ViewGrop 默认返回 false
      
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                && ev.getAction() == MotionEvent.ACTION_DOWN
                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                && isOnScrollbarThumb(ev.getX(), ev.getY())) {
            return true;
        }
        return false;
    }
复制代码
  1. View 没有 oninterceptEvent 方法,一旦点击事件传递给它,那么它的 onTouchEvent 就会被调用
 public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();

        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return clickable;
        }
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    if ((viewFlags & TOOLTIP) == TOOLTIP) {
                        handleTooltipUp();
                    }
                    if (!clickable) {
                        removeTapCallback();
                        removeLongPressCallback();
                        mInContextButtonPress = false;
                        mHasPerformedLongPress = false;
                        mIgnoreNextUpEvent = false;
                        break;
                    }
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        // take focus if we don't have it already and we should in
                        // touch mode.
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                        if (prepressed) {
                            // The button is being released before we actually
                            // showed it as pressed.  Make it show the pressed
                            // state now (before scheduling the click) to ensure
                            // the user sees it.
                            setPressed(true, x, y);
                        }

                        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();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }

                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }

                        if (prepressed) {
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }

                        removeTapCallback();
                    }
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_DOWN:
                    if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                        mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                    }
                    mHasPerformedLongPress = false;

                    if (!clickable) {
                        checkForLongClick(0, x, y);
                        break;
                    }

                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    // Walk up the hierarchy to determine if we're inside a scrolling container.
                    boolean isInScrollingContainer = isInScrollingContainer();

                    // For views inside a scrolling container, delay the pressed feedback for
                    // a short period in case this is a scroll.
                    if (isInScrollingContainer) {
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                        checkForLongClick(0, x, y);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    if (clickable) {
                        setPressed(false);
                    }
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    break;

                case MotionEvent.ACTION_MOVE:
                    if (clickable) {
                        drawableHotspotChanged(x, y);
                    }

                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        // Remove any future long press/tap checks
                        removeTapCallback();
                        removeLongPressCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            setPressed(false);
                        }
                        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    }
                    break;
            }

            return true;
        }

        return false;
    }
复制代码
  1. 一个事件序列所有的事件都只能由一个 View 完成,也就是谁当一个View决定拦截一个事件后,那么系统会将所有的事件方法分配给它处理,因此不会再调用这个 View 的 onInterceptEvent 去询问他是否要拦截了
  2. 如果同序列事件传递给一个View处理,那么它就必须消耗掉
  3. View 的 onTouchEvent 默认都会消耗事件,除非他是不可点击的
  • View 的 longClickable 属性是 false; Button clickable 是 true , TextView 的 clickable 默认是 false
  1. 事件的传递都是由外向内传递的,即事件总是先传递给父元素,然后再由父元素分发给子View.通过 requestDisallowInterceptTouchEvent 方法 可以在 子元素中干预父元素的事件分发过程,但是 ACTION_DOWN 除外
    @Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
            // We're already in this state, assume our ancestors are too
            return;
        }

        if (disallowIntercept) {
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }

        // Pass it up to our parent
        if (mParent != null) {
            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
        }
    }
复制代码

②. 事件分发源码分析

点击事件 用 MotionEvent 表示,当一个点击事件发生以后,事件先传递给 Activity,由Activity 的 dispatchEvent 进行事件派发,具体的工作由Activity的 window 完成,window 将事件传递给 dector view,dector view 一般就是当前界面的底层容器(即 setContentView 所设置的 View的父容器),通过Activity.getWindow. getDectorView()可以获得

i.Activty对点击事件的分发过程
    public boolean dispatchTouchEvent(MotionEvent event) {
         if (event.isTargetAccessibilityFocus()) {
             if (!isAccessibilityFocusedViewOrHost()) {
                return false;
            }
             event.setTargetAccessibilityFocus(false);
        }

        boolean result = false;

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
             stopNestedScroll();
        }

        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
             
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

        if (!result && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
 
        if (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
            stopNestedScroll();
        }

        return result;
    }
复制代码
ii.window将事件传递给Activity的过程
 public abstract boolean superDispatchTouchEvent(MotionEvent event);
复制代码
iii.顶级View对点击事件的分发过程

TODO: 难度系数太高,参考<<Android艺术与探索>> page146 ~ 151

iv.view对点击事件的处理过程

TODO: 难度系数太高,参考<<Android艺术与探索>> page151 ~ 154

八.View的滑动冲突

  • 外部滑动方向和内部滑动方向的不一致 ViewPager 和 Fragment 配合使用所组成的页面滑动效果,主流应用几乎都会使用这个效果.在这种效果中,可以左右滑动来切换页面,而每个页面往往又是一个ListView.本来这种情况下是有滑动冲突的,ViewPager 内部处理了这种冲突,因此采用Viewpager时我们无须关注这个问题,如果我们采用不是ViewPager 而是 ScroolView 等,那就必须处理滑动冲突了,否则造成的后果就是内外两层只能有一层能够滑动,这是滑动因为,这是因为两者之间的滑动事件有冲突,除了这两种情况,还存在其他情况,比如外部上下滑动,内部左右滑动等,它们属于同类一类滑动冲突
  • 外部滑动方向和内部滑动方向的一致性 当手指滑动用户无法知道到底是想让哪一层滑动,所以当手指滑动就会出现问题,系统不知道用户到底是王哪一层滑动,所以当手指滑动就会出现一种问题,要么只有一层滑动,要么就是两层滑动就会很卡顿
  • 上述两种情况均存在 如:外部有一个SlideMenu效果,然后内部有一个ViewPage,ViewPage的每一个页面中又是一个ListView,但是他是几个单一的滑动事件的总合

九.常见的滑动冲突场景

TODO: 难度系数太高,参考<<Android艺术与探索>> page155 ~ 156

①滑动冲突的处理规则

TODO: 难度系数太高,参考<<Android艺术与探索>> page156 ~ 157

②滑动冲突的解决方式

TODO: 难度系数太高,参考<<Android艺术与探索>> page158 ~ 159

i.外部拦截法
ii.内部拦截法

TODO: 难度系数太高,参考<<Android艺术与探索>> page159 ~ 173

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值