android进阶(三)-----View事件分发机制及冲突解决

一、View基础知识

 

1、view的位置参数

view的位置由四个顶点决定的,分别是对应view的四个属性:

top:左上角纵坐标

left:左上角横坐标

right:右下角横坐标

bottom:右下角纵坐标

android中的X轴和Y轴的正方向分别是右边和下边

android进阶(三)-----View事件分发机制及冲突解决_第1张图片

从android3.0开始,View增加了几个额外的参数:x、y、translationX、translationY

x和y是View左上角的坐标

translationX、translationY是View左上角相对于父容器的偏移量

 

2、MotionEvent和TouchSlop

(1)MotionEvent在手指接触屏幕后产生的一系列事件中,有如下几种:

ACTION_DOWN:手指刚接触屏幕

ACTION_MOVE:手指在屏幕上移动

ACTION_UP:手指从屏幕上抬起的一瞬间

通过getX和getY我们可以获取x和y轴的坐标

通过getRowX和getRowY我们可以获取相对于手机屏幕左上角的x和y坐标

(2)TouchSlop是系统所能辨别的滑动的最小距离

通过ViewConfiguration.get(context).getScaledTouchSlop()方法获取这个常量

 

3、GestureDetector和Scroller

(1)GestureDetector:手势检测,用于检测用户的单击、滑动、长按、双击的行为

使用方式

创建GestureDetector对象并实现OnGesturelistener接口

OnGesturelistener接口中的方法

onDown:手指触摸屏幕的一瞬间,由1个ACTION_DOWN触发

onShowPress:手指触摸屏幕尚未松开或拖动,由1个ACTION_DOWN触发

onSingleTapUp:手指触摸屏幕松开,由1个ACTION_UP触发,单击事件

onScroll:手指按下屏幕并拖动,由1个ACTION_DOWN,多个ACTION_MOVE触发,拖动事件

onLongPress:用于长按屏幕

onFling:用户按下屏幕、快速滑动后松开,由1个ACTION_DOWN,多个ACTION_MOVE和1个ACTION_UP触发,快速滑动事件

(2)Scroller:弹性滑动对象,用于实现View的弹性滑动

使用方式:

Scroller scroller = new Scroller(context)

//缓慢滑动到指定位置

private void smoothScrollTo(int destX,int destY){

    int scrollX = getScrollX()

    int delta = destX - scrollX

    scroller.startScroll(scrollX,0,delta,0,1000)

    invalidate()

}

@Override

public void computeScroll(){

    if(mScroller.computeScrollOffset()){

        scrollTo(mScroller.getCurrX(),mScroller.getCurrY())

        postInvalidate()

    }

}

二:View的滑动

通过三种方式可以实现View的滑动:

第一种通过View本身提供的scrollTo/scrollBy方法实现滑动

第二种通过动画给View添加平移效果实现滑动

第三种通过改变View的LayoutParams使得View重新布局实现滑动

 

1、使用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); 
}

从源码可以看出,scrollBy也是调用了scrollTo方法,他实现了基于当前位置的相对滑动,scrollTo实现了基于所传递参数的绝对滑动

mScrollX和mScrollY的单位为像素,从左向右滑动,mScrollX为负值,反之为正值,如果从上往下滑动,那么mScrollY为负值,反之为正值

 

2、使用动画滑动

通过动画让一个View进行平移,主要操作View的translationX和translationY属性,既可以采用传统View动画,也可以使用属性动画

使用方式:




    

使用属性动画将View在100ms内向右移动100像素

ObjectAnimator.ofFloat(targetView,"translationX",0,100).setDuration(100).start();

3、改变布局参数

这种方式很简单,直接上代码了

MarginLayoutParams params = (MarginLayoutParams)textview.getLayoutParams();

params.width += 100;

params.leftMargin += 100;

textview.setLayoutParams(params)

4、各种滑动方式的对比

scrollTo/scrollBy

特点:操作简单,适合对View内容进行滑动。只能滑动View内容,不能滑动View本身

动画方式

特点:操作简单,适用于没有交互的View和实现复杂的动画效果

布局方式

特点:操作略微复杂,适用于有交互的View

 

三:View的时间分发机制

1、点击事件的传递规则

点击事件分发过程:

dispatchTouchEvent:进行事件分发,如果事件能够传递跟当前View,就会被调用,返回结果受当前View的onTouchEvent和下级的dispatchTouchEvent方法的影响,表示是否消费当前事件

onInterceptTouchEvent:用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列中,此方法不会被再次调用,返回结果表示是否拦截当前事件

onTouchEvent:在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消费当前事件,不消费则在同一个事件序列中,当前View无法再次受到事件

2、点击事件流程图

 

android进阶(三)-----View事件分发机制及冲突解决_第2张图片

点击事件到达顶级View后,会调用ViewGroup的dispatchTouchEvent方法,如果顶级ViewGroup dispatchTouchEvent方法返回true,则事件由ViewGroup处理,这时如果ViewGroup的OnTouchListener被设置,则onTouch会被调用,否则onTouchEvent会被调用,同时设置,onTouch会屏蔽掉onTouchEvent。在onTouchEvent中,如果设置了OnClickListener,则onClick会被调用。如果顶级的ViewGroup不拦截事件,则事件会传递给他所在的点击事件链上的子View,这时子View的dispatchTouchEvent会被调用。

 

四:View的滑动冲突

1、常见的滑动冲突

外部滑动方向和内部滑动方向不一致

外部滑动方向和内部滑动方向一致

上面两种情况的嵌套

2、滑动冲突的处理规则

(1)外部和内部滑动不一致:当用户左右滑动时,需要让外部的View拦截事件,当用户上下滑动时,让内部View拦截事件(根据滑动是水平滑动还是竖直滑动来判断是谁来拦截)

(2)外部和内部滑动方向一致:根据业务需要,当处于某种状态时外部View滑动,而处于另一种状态时需要内部View滑动

3、滑动冲突的解决方式

(1)外部拦截:事件通过父容器拦截处理,如果负容器需要此事件就拦截,不需要就不拦截。需要重写onInterceptTouchEvent方法

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:

            if(父容器需要当前点击事件){

                intercepted = true

            }else{

                intercepted = false

            }

            break;

        case MotionEvent.ACTION_UP:

            intercepted = fasle;

            break;

    }

    mLastXIntercept = x;

    mLastYIntercept = y;

    return intercepted;

}

(2)内部拦截法:指父容器不拦截事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消费,否则就交给父容器处理。这种方式和android中的事件分发机制不一致,需要配合requestDisallowInterceptTouchEvent方法,使用起来比外部拦截稍复杂,需要重写子元素的dispatchTouchEvent方法。伪代码如下:

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;

    }

    mLastX = x

    mLastY = y

    return super.dispatchTouchEvent(event)

}

 

 

 

 

 

你可能感兴趣的:(android进阶)