美文网首页
Android艺术探索学习笔记:第3章View的事件体系

Android艺术探索学习笔记:第3章View的事件体系

作者: Android绝世小菜鸟 | 来源:发表于2017-06-06 17:55 被阅读0次

    一.View的基础知识

    1.View的位置参数

    View的位置主要由它的四个定点来决定,分别对应View的四个属性:top、left、right、bottom,   
    这下坐标都是相对父容器而言的。从3.0开始View增加了x、y、translationX、translationY;  
    x和y是View左上角的坐标,translationX和translationY是View左上方相对父容器的偏移量。
    x = left + translationX;  
    y = top + translationY;
    View平移的过程中,top和left表示的是原始左上角的位置信息;其值并不会改变,  
    此时发生改变的是x、y、translationX和translationY。
    

    2.MotionEvent

    点击屏幕后松开,事件序列            DOWN->UP  
    点击屏幕滑动一会再松开,事件序列为   DOWN->MOVE->...->MOVE->UP。
    
    getX/getY  获取相对当前View左上角的x和y坐标  
    getRawX/getRawY  获取相对手机屏幕左上角的x和y坐标。
    

    3.TouchSlop

    是系统能识别滑动的最小距离
    ViewConfiguration.get(getContext()).getScaledTouchSlop()
    

    4.VelocityTracker

    用于追踪手指在滑动过程中的速度。使用前在View的onTouchEvent方法中追踪当前单击事件的速度。
    
    VelocityTracker velocityTracker = VelocityTracker.obtain(); 
    velocityTracker.addMovement(event); 
    //想知道滑动速度时
    //获取速度前需计算速度  参数 时间间隔 单位ms
    velocityTracker.computeCurrentVelocity(1000);  
    //获取速度
    int xVelocity = (int)velocityTracker.getXVelocity(); 
    int yVelocity = (int)velocityTracker.getYVelocity();
    

    5.GestureDetector

    用于辅助检测用户的单击、滑动、长按、双击等行为;建议:如果只是监听滑动相关的推荐  
    在onTouchEvent中实现,如果需要监听双击,使用GeststureDetector。
    
    eg: GestureDetector mGes = new GestureDetecotr(this);
        在View的OnTouchEvent方法中传递给mGes对象:
        return mGes.onTouchEvent(event);
    

    6.Scroller

    用来实现View的弹性滑动,View的scrollTo/scrollBy是瞬间完成的,  
    使用Scroller配合View的computeScroll方法配合使用达到弹性滑动的效果
    

    二.View的滑动

    3种滑动方式:
    1.scrollTo和scrollBy
    2.动画来移动View
    3.改变控件位置

    1.ScrollTo 和 ScrollBy

    scrollTo和scrollBy只能改变View内容而不能改变View本身的位置。scrollBy内部 
    也是调用了scrollTo,它是基于当前位置的相对滑动,scrollTo是基于所传递参数 
    的绝对滑动。在滑动过程中mScrollX/mScrollY总是等于View边缘与View内容边缘  
    的距离,这两个属性用getScrollX/getScrollY方法获取
    

    2.使用动画

    帧动画 View动画 属性动画

    使用动画来移动View,主要是操作View的translationX和translationY属性。需要  
    注意的是,View动画只是对View的影像做操作,它并不能真正改变View的位置参  
    数,如果这个View设置了点击事件,点击动画后的新位置无法触发点击事件的,  
    使用属性动画没有此问题,但3.0之前系统无属性动画。
    
    eg: xml设置
      <scale  
            android:fromXScale="float"  
            android:toXScale="float"  
            android:fromYScale="float"  
            android:toYScale="float"  
            android:pivotX="float"  
            android:pivotY="float" />  
        代码中设置
           ScaleAnimation sAnima = new ScaleAnimation(0, 5, 0, 5);
           scale.startAnimation(sAnima); 
           
    属性动画:
            ObjectAnimator//  
             .ofFloat(view, "rotationX", 0.0F, 360.0F)//  
             .setDuration(500)//  
             .start();  
    

    3.改变布局 参数

    改变布局参数实现滑动,即改变LayoutParams,如想将一个View右平移100px,  
    只需要将该View的LayoutParams里的marginLeft增加100px即可
    
    ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mBtn.getLayoutParams();
    params.leftMargin += 100;
    mBtn.requestLayout();
    //或者mBtn.setLayoutParams(params);
    

    ==总结:==
    scrollTo/scrollBy: 操作简单,适合对View内容的滑动
    动画: 操作简单,适合没有交互的View和实现负责的动画效果
    改变布局参数:操作稍微复杂,适合有交互的View

    三.弹性滑动

    1.使用Scroller原理: startScroll()保存了我们传递的几个参数 ——> invalidate()会导致View重绘 ——> draw() ——> computeScroll()该方法为空实现,我们内部调用scrollTo(x,y)实现滑动 和 postInvalidate() 继续重绘,反复下去完成弹性滑动。

    Scroller scroller=new Scroller(context);
    //缓慢滚动到指定位置
    private void smoothScrollTo(int destX,int destY){
        int scrollx=getScrollX();
        int deltaX=destX-scrollX;
        //1000ms内滑向destX,效果就是慢慢滑动
        scroller.startScroll(scrollX,0,deltaX,0,1000);
        invalidate();
    }
    @Override
    public void computeScroll(){
        if(scroller.computeScrollOffset()){
        scrollTo(scroller.getCurrX(),scroller.getCurrY());
        postInvalidate();
        }
    }
    

    2.通过动画可以直接实现弹性滑动

    3.使用延时策略完成滑动,核心思想 就是通过发送一系列延时消息从而达到一种渐进式的效果。用Handler或View的postDelayed方法,postDelayed发送延时消息,然后消息中进行View滑动,接连不断的发送这种延时消息,达到弹性滑动的效果。也可以使用线程的sleep方法来实现。

    四.View的事件分发机制

    1. 点击事件的传递规则

    dispatchTouchEvent(MotionEvent event) 用来处理事件的分发,返回结果受当前View的onTouchEvent和下级View的  
    dispatchTouchEvent方法影响,表示是否消耗该事件。  
    
    onInterceptTouchEvent(MotionEvent event)
    在dispatchTouchEvent方法内部调用,用来判断是否拦截某个事件,如果  
    当前View拦截了某个事件,那在同一个事件序列中,此方法不会再次调用,  
    返回结果表示是否拦截当前事件  
    
    onTouchEvent(MotionEvent event)
    在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗  
    ,在同一事件序列里,当前View无法再次接收到事件三者关系可以用如下伪代码表示
    

    ==原理==:dispatchTouchEvent方法中判断是否需要拦截,需要的话,就不再调用onInterceptTouchEvent,并调用onTouchEvent方法,如果不拦截,就会调用子VIew的dispatchTouchEvent方法。

    public boolean dispatchTouchEvent(MotionEvent ev){    
        boolean consume = false;
        if(onInterceptTouchEvent(ev)){
            consume = onTouchEvent(ev);
        }else{
            consume = child.dispatchTouchEvent(ev);   
        }
        return consume;
    }
    

    事件顺序: OnTouListener的OnTouch > onTouchEvent > onClickListener
    如果OnTouch中返回true,就不会调用onTouchEvent方法,返回false就会调用,如果onTouchEvent中存在onClickListener,onClick一定会被调用。除非设置该VIew的onClickible 和 onLongClickible都为false。

    1.对于一个根ViewGroup,点击事件产生后,首先会传递给它,这时他的dispatchTouchEvent会调用,
    如果它的onInterceptTouchEvent返回true表示要拦截当前事件,接下来事件会交给这个ViewGroup  
    处理,它的onTouchEvent就会被调用,如果这个ViewGroup的onInterceptTouchEvent返回false,  
    则事件会继续传递给子元素,子元素的dispatchTouchEvent会调用,如此反复直到事件被处理。  
    

    view事件传递


    image
    2.当一个View需要处理事件时,如果设置了OnTouchListener,那么OnTouchListener的onTouch方法  
    会回调,如果onTouch返回false,则当前View的onTouchEvent方法会被调用;如果返回true,那么  
    onTouchEvent方法将不会调用。由此可见,OnTouchListener优先级高于onTouchEvent。  
    OnClickListener优先级处在事件传递的尾端。
    
    3.一个点击事件产生后,传递顺序:Activity->Window->View;如果一个View的onTouchEvent返回false,  
    那么它的父容器的onTouchEvent会被调用,以此类推,所有元素都不处理该事件,最终将传递给Activity  
    处理,即Activity的onTouchEvent会被调用。
    
    4.同一个事件序列是指从手指触摸屏幕那一刻开始,到手指离开屏幕那一刻(down->move...move->up)
    
    5.一个事件序列只能被一个View拦截且消耗,同一个事件序列所有事件都会直接交给它处理,  
    并且它的onInterceptTouchEvent不会再被调用。
    
    
    6.某个View一旦开始处理事件,如果它不消耗ACTION_DOWN(onTouchEvent返回了false),那么同一事件  
    序列中其他事件都不会再交给它来处理,事件将重新交给他的父元素处理,即父元素的onTouchEvent会被调用。  
    
    7.如果某个View不消耗除ACTION_DOWN以外的其他事件,那么这个点击事件会消失,此时父元素的onTouchEvent  
    并不会被调用,并且当前View可以收到后续事件,最终这些消失的点击事件会传递给Activity处理
    
    8.ViewGroup默认不拦截任何事件,ViewGroup的onInterceptTouchEvent方法默认返回false。  
    
    9.View没有onInterceptTouchEvent方法,一旦有事件传递给它,那么它的onTouchEvent方法就会被调用。  
    
    10.View的onTouchEvent方法默认消耗事件(返回true),除非他是不可点击的(clickable和longClickable同时为false)。  
    View的longClickable属性默认都为false,clickable属性分情况,Button默认为true,TextView默认为false。
    onClick发生的前提是View可点击,并且它收到了down和up事件。
    
    11.事件传递过程是由内而外,事件总是先传递给父元素,然后在由父元素分发给子View,通过  
    requestDisallowInterceptTouchEvent方法可以在子元素干预父元素的事件分发过程,但ACTION_DOWN事件除外。
    

    2.顶级View对点击事件的分发过程

    ①.ViewGroup对事件拦截的处理。

    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);
        }else{
            intercepted = false;
        }
        
    }else{
        intercepted = true;
    }
    

    原理:如果事件类型是MotionEvent.ACTION_DOWN 或者 已经由子元素成功处理时,既mFirstTouchTarget指向子元素,不为null,当ViewGroup拦截事件时,当ACTION_MOVE 和 ACTION_UP来时, 将不会再去调用onInterceptTouchEvent。并且其他的事件都会交给它处理。

    1.ViewGroup会在down事件到来时重置状态,会将FLAG_DISALLOW_INTERCEPT重置,所以这个标志不能拦截down事件。
    2.FLAG_DISALLOW_INTERCEPT可以请求不要拦截事件,(move 和  up),前提是ViewGroup不拦截down事件。
    

    ②.ViewGroup不拦截事件,分发给子View的处理
    子View接受点击事件的标准:
    1子View在播放动画 2.点击事件坐标落在子View内

    子View如果设置了onClick事件,则会默认设置onClickable为true 长按事件一样,

    3.View的滑动冲突

    1.常见滑动冲突场景

    • 场景1 —— 外部滑动方向与内部滑动方向不一致,比如ViewPager中包含ListView;
    • 场景2 —— 外部滑动方向与内部滑动方向一致,比如ScrollView中包含ListView;
    • 场景3 —— 上面两种情况的嵌套
    image

    2.拦截方法: 外部拦截 内部拦截

    外部拦截:

    q.png

    在这里,首先down事件父容器必须返回false ,因为若是返回true,也就是拦截了down事件,那么后续的move和up事件就都会传递给父容器,子元素就没有机会处理事件了。

    其次是up事件也返回了false,一是因为up事件对父容器没什么意义,其次是因为若事件是子元素处理的,却没有收到up事件会让子元素的onClick事件无法触发。

    内部拦截:

    w.png

    修改父类的拦截方法:

    e.png

    总结:外部优先于内部拦截

    相关文章

      网友评论

          本文标题:Android艺术探索学习笔记:第3章View的事件体系

          本文链接:https://www.haomeiwen.com/subject/bmknfxtx.html