View的事件体系

来源:互联网 发布:淘宝店铺旺旺在哪里 编辑:程序博客网 时间:2024/05/20 19:47

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();}@Overridepublic 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,这是个空方法,要自己实现

@Overridepublic 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 0
原创粉丝点击