专栏首页刘晓杰View的事件体系

View的事件体系

1.View的位置参数

2.MotionEvent 手指触摸屏幕后的一系列事件,包括ACTION_DOWN,ACTION_MOVE,ACTION_UP

3.TouchSlop 系统所能识别的被认为是滑动的最小距离,获取方式为:

ViewConfiguration.get(getContext()).getScaledTouchSlop()

4.VelocityTracker速度追踪 VelocityTracker的使用方式

       //初始化
       VelocityTracker mVelocityTracker = VelocityTracker.obtain();
       //在onTouchEvent方法中
       mVelocityTracker.addMovement(event);
       //获取速度
       mVelocityTracker.computeCurrentVelocity(1000);
       float xVelocity = mVelocityTracker.getXVelocity();
       //重置和回收
       mVelocityTracker.clear(); //一般在MotionEvent.ACTION_UP的时候调用
       mVelocityTracker.recycle(); //一般在onDetachedFromWindow中调用

5.GestureDetector GestureDetector用于辅助检测用户的单击、滑动、长按、双击等行为 使用方法:

先创建GestureDetector 并实现onGestureListener接口,需要时还可以实现OnDoubleTapListener实现双击行为
GestureDetector mGestureDetector = new GestureDetector(this);
//解决长按屏幕后无法拖动现象
mGestureDetector .setIsLongpressEnabled(false);
然后接管view的onTouchEvent方法,添加如下实现
return mGestureDetector.onTouchEvent(event);

做完以上步骤就可以有选择的实现onGestureListener和OnDoubleTapListener的方法(具体实现这里不详细说明) 建议:如果只是监听滑动相关的事件在onTouchEvent中实现;如果要监听双击这种行为的话,那么就使用GestureDetector。

6.Scroller 当使用View的scrollBy和scrollTo的时候,过程是瞬间完成的,用户体验差。因此,可以使用scroller来实现有过渡效果的滑动 使用方法

Scroller scroller = new Scroller(getContext());

//缓慢移动到指定位置
private void smoothScrollBy(int dx, int dy) {
        int delta = dx - getScrollX();
        //500ms内划向dx
        scroller.startScroll(getScrollX(), 0, delta, 0, 500);
        invalidate();
}

@Override
public void computeScroll() {
    if (scroller.computeScrollOffset()) {
        scrollTo(scroller.getCurrX(), scroller.getCurrY());
        postInvalidate();
    }
}

7.View的滑动 (1)使用scrollTo/scrollBy

/**
   * Set the scrolled position of your view. This will cause a call to
   * {@link #onScrollChanged(int, int, int, int)} and the view will be
   * invalidated.
   * @param x the x position to scroll to
   * @param y the y position to scroll to
   */
  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();
      }
    }
  }

/**
  * Move the scrolled position of your view. This will cause a call to
  * {@link #onScrollChanged(int, int, int, int)} and the view will be
  * invalidated.
  * @param x the amount of pixels to scroll by horizontally
  * @param y the amount of pixels to scroll by vertically
  */
    public void scrollBy(int x, int y) {
     scrollTo(mScrollX + x, mScrollY + y);
    }

可以看出,scrollBy其实调用了scrollTo函数。scrollTo滑动到绝对位置,scrollBy是相对位置。两者只能改变view内容的位置,不能改变在布局中的位置。 (2)使用动画 网上例子很多,不再说明 (3)改变布局参数 比如让一个button向右平移100px可以用如下代码

        MarginLayoutParams params = (MarginLayoutParams)button.getLayoutParams();
        params.width += 100;
        params.leftMargin += 100;
        button.requestLayout();
        //或者button。setLayoutParams(params)

(4)各种滑动方式对比 使用scrollTo/scrollBy:操作简单,适合对View内容的滑动 动画:操作简单,主要适合于没有交互的view和实现复杂的动画效果 改变布局参数:操作稍微复杂,适用于有交互的view

8.弹性滑动 (1)使用scroller 使用方法之前已经讲过,现在看一下如何实现的。主要是startScroll和computeScrollOffset两个函数

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;  
    // This controls the viscous fluid effect (how much of it)  
    mViscousFluidScale = 8.0f;  
    // must be set to 1.0 (used in viscousFluid())  
    mViscousFluidNormalize = 1.0f;  
    mViscousFluidNormalize = 1.0f / viscousFluid(1.0f);  
} 

在这个方法中我们只看到了对一些滚动的基本设置动作,比如设置滚动模式,开始时间,持续时间等等,并没有任何对View的滚动操作 其实整个过程是这样的:View重绘后会在draw方法中调用computeScroll,这是个空方法,要自己实现

@Override
public void computeScroll() {
    if (scroller.computeScrollOffset()) {
        scrollTo(scroller.getCurrX(), scroller.getCurrY());
        postInvalidate();
    }
}

在我们自己实现的方法中先调用scrollTo再调用postInvalidate进行第二次重绘,然后调用draw函数,这样如此反复知道滑动结束 再看一下computeScrollOffset方法

public boolean computeScrollOffset() {  
    if (mFinished) {  
        return false;  
    }  

    int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);  

    if (timePassed < mDuration) {  
        switch (mMode) {  
        case SCROLL_MODE:  
            float x = (float)timePassed * mDurationReciprocal;  

            if (mInterpolator == null)  
                x = viscousFluid(x);   
            else  
                x = mInterpolator.getInterpolation(x);  

            mCurrX = mStartX + Math.round(x * mDeltaX);  
            mCurrY = mStartY + Math.round(x * mDeltaY);  
            break;  
        case FLING_MODE:  
            float timePassedSeconds = timePassed / 1000.0f;  
            float distance = (mVelocity * timePassedSeconds)  
                    - (mDeceleration * timePassedSeconds * timePassedSeconds / 2.0f);  

            mCurrX = mStartX + Math.round(distance * mCoeffX);  
            // Pin to mMinX <= mCurrX <= mMaxX  
            mCurrX = Math.min(mCurrX, mMaxX);  
            mCurrX = Math.max(mCurrX, mMinX);  

            mCurrY = mStartY + Math.round(distance * mCoeffY);  
            // Pin to mMinY <= mCurrY <= mMaxY  
            mCurrY = Math.min(mCurrY, mMaxY);  
            mCurrY = Math.max(mCurrY, mMinY);  

            break;  
        }  
    }  
    else {  
        mCurrX = mFinalX;  
        mCurrY = mFinalY;  
        mFinished = true;  
    }  
    return true;  
}  

它根据时间的流逝来计算当前的scrollX和scrollY的值 (2)动画 (3)延时策略 通过handler的sendEmptyMessageDelayed方法

8.事件分发机制 具体解释我之前已经讲过了,地址在这里 http://blog.csdn.net/lxj1137800599/article/details/51034566

9.滑动冲突 解决方法 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: {
         int deltaX = x - mLastXIntercept;
         int deltaY = y - mLastYIntercept;
         if (父容器需要拦截当前点击事件的条件) {
             intercepted = true;
         } else {
             intercepted = false;
         }
         break;
     }
     case MotionEvent.ACTION_UP: {
         intercepted = false;
         break;
     }
     default:
         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: {
           getParent().requestDisallowInterceptTouchEvent(true);
           break;
       }
       case MotionEvent.ACTION_MOVE: {
           int deltaX = x - mLastX;
           int deltaY = y - mLastY;
           if (父容器需要点击事件) {
               getParent().requestDisallowInterceptTouchEvent(false);
           }
           break;
       }
       case MotionEvent.ACTION_UP: {
           break;
       }
       default:
           break;
       }
       mLastX = x;
       mLastY = y;
       return super.dispatchTouchEvent(event);
   }

当然,不能拦截父容器的ACTION_DOWN事件

   public boolean  onInterceptTouchEvent(MotionEvent ev){
       if(ev.getAction() == MotionEvent.ACTION_DOWN){
           return false;
       }else{
           return true;
       }
   }

为什么不能拦截呢?因为拦截了ACTION_DOWN,会导致所有时间都无法传递到子元素

了解到了这里,我想来解决一下半年前的困惑 http://blog.csdn.net/lxj1137800599/article/details/51007062 看该文的最后,scrollview和listview的滑动冲突 我决定采用外部拦截法。因此,我需要自定义MyScrollView来继承ScrollView并重写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:
            Log.e("ACTION_DOWN", "ACTION_DOWN");
            intercepted = false;
            break;
        case MotionEvent.ACTION_MOVE:
            Log.e("ACTION_MOVE", "ACTION_MOVE");
            //getChildAt(0)得到的就是listview
            if (y > getChildAt(0).getBottom()) {
                intercepted = true;
            } else {
                intercepted = false;
            }
            break;
        case MotionEvent.ACTION_UP:
            Log.e("ACTION_UP", "ACTION_UP");
            intercepted = false;
            break;
        }

        Log.e("intercepted", "" + intercepted);

        return intercepted;
    }

这样,完美解决 代码地址:http://download.csdn.net/detail/lxj1137800599/9611947

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • node.js中exports 和 module.exports 的区别

    解释:a 是一个对象,b 是对 a 的引用,即 a 和 b 指向同一块内存,所以前两个输出一样。当对 b 作修改时,即 a 和 b 指向同一块内存地址的内容发生...

    acoolgiser
  • node.js中的包管理之npm使用介绍

    NPM 是Node.js的包管理工具。它的重要性就像 gem 之于 Ruby 一样。Node.js 与 NPM的关系是密不可分的。

    acoolgiser
  • “Debugger listening on [::]:25950” node.js调试时出现端口占用的问题,run没问题 debug时老提示端口占用 。Error: listen EADDRI

    在node.js多进程项目中,程序直接run没问题,但debug时老提示端口占用。“Debugger listening on [::]:25951”    n...

    acoolgiser
  • node.js express 配置模块config-lite的用法,为什么项目中出现"import config from 'config-lite' ”代码?

          不管是小项目还是大项目,将配置与代码分离是一个非常好的做法。我们通常将配置写到一个配置文件里,如 config.js 或 config.json,并...

    acoolgiser
  • node.js中“模块”Module的概念和介绍

    在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里代码就会越来越长,越来越不容易维护。

    acoolgiser
  • JS中函数的两种定义方法

    请注意,函数体内部的语句在执行时,一旦执行到return时,函数就执行完毕,并将结果返回。因此,函数内部通过条件判断和循环可以实现非常复杂的逻辑。

    acoolgiser
  • three.js中场景,相机,渲染器之间的关系

    摘自:http://www.hewebgl.com/article/getarticle/50

    acoolgiser
  • JavaScript中的Map与Set键值对象的用法

    JavaScript的默认对象表示方式{}可以视为其他语言中的Map或Dictionary的数据结构,即一组键值对。

    acoolgiser
  • JavaScript中的比较运算符

    第二种是===比较,它不会自动转换数据类型,如果数据类型不一致,返回false,如果一致,再比较。

    acoolgiser
  • JavaScript 实现前端table页面,vue.js实现前端表格

    对于table中的th,tr,td 可以设置rowspan,colspan属性,使得具有任何复杂包含、重叠、组合关系的表格都能做出来。

    acoolgiser

扫码关注云+社区

领取腾讯云代金券