View的事件体系一(触摸、手势、滑动)

一. View基础触摸、手势、滑动事件

View是所有界面层的空间的一种抽象。
Andorid屏幕坐标系原点在左上角, 而View的left,right,top,bottom属性,都是相对于其父View的坐标。


1. MotionEvent 和 TouchSlop

  1. MotionEvent

    手指触摸屏幕后,典型的事件类型:

    • ACTION_DOWN:手指刚接触屏幕;
    • ACTION_MOVE:手指在屏幕上移动;
    • Aciton_UP:手指在屏幕上松开的瞬间

    一般来说,正常触摸屏幕的行为,会触发一系列事件,比如

    • 点击屏幕后立即松开,事件序列: DOWN -> UP
    • 点击屏幕,滑动一会再松开,事件序列:DOWN -> MOVE -> …. -> MOVE -> UP

    MotionEvent提供两组方法,分别用于获取相对于当前View的坐标,和相对于屏幕原点的坐标:

    • getX() / getY() :获取相对于当前View左上角的 x 和 y 坐标。
    • getRawX() / getRawY:获取相对于屏幕原点的 x 和 y 坐标。
  2. TouchSlop

    • TouchSlop是系统所能识别出的被认为是滑动的最小距离,通过如下代码获取:
      ViewConfiguration.get(getApplicationContext()).getScaledTouchSlop();

    • 在处理滑动时,可以以此来过滤掉距离太小的滑动,提升用户体验。

2. VelocityTracker、GestureDetector 和 Scroller

  1. VelocityTracker 速度追踪器,可以追踪当前事件的速度:

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (velocityTracker1 == null) {
            velocityTracker1 = VelocityTracker.obtain();
        }
        velocityTracker1.addMovement(event);
        //参数单位:毫秒
        velocityTracker1.computeCurrentVelocity(1000);
        int xVelocity = (int) velocityTracker1.getXVelocity();
        int yVelocity = (int) velocityTracker1.getYVelocity();
        Log.d(TAG, "velocity:" + xVelocity + ", " + yVelocity);
        return true;
    }

    在合适的时候,需要回收它:

    protected void onDestroy() {
        velocityTracker.clear();
        velocityTracker.recycle();
        super.onDestroy();
    }
  2. GestureDetector

    • 创建GestureDetector对象,并实现接口.

      GestureDetector gestureDetector = new GestureDetector(new GestureDetector.OnGestureListener() {
          @Override
          public boolean onDown(MotionEvent e) {
              Log.d(TAG, "由ACTION_DOWN 触发 "+e.getAction());
              return false;
          }
      
          @Override
          public void onShowPress(MotionEvent e) {
              Log.d(TAG, "由ACTION_DOWN 触发, 触摸屏幕后,尚未松开或者拖动 "+ e.getAction());
          }
      
          @Override
          public boolean onSingleTapUp(MotionEvent e) {
              Log.d(TAG, "触摸屏幕后松开,单击行为 "+ e.getAction());
              return false;
          }
      
          @Override
          public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
              Log.d(TAG, "触摸屏幕并拖动 "+ e1.getAction()+"  ,  "+e2.getAction());
              return false;
          }
      
          @Override
          public void onLongPress(MotionEvent e) {
              Log.d(TAG, "长按行为 "+ e.getAction());
          }
      
          @Override
          public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
              Log.d(TAG, "快速滑动行为 "+e1.getAction()+"  ,  "+e2.getAction());
              return false;
          }
      });

      然后接管onTouchListener的返回:

      View.OnTouchListener touchListener = new View.OnTouchListener() {
          @Override
          public boolean onTouch(View v, MotionEvent event) {
              // ...
              gestureDetector.setIsLongpressEnabled(false);
              // return true;
              return gestureDetector.onTouchEvent(event);
          }
      };

      这样就可以监控手势行为了。

    • 双击回调接口:OnDoubleTapListener

      GestureDetector.OnDoubleTapListener doubleTapListener = new GestureDetector.OnDoubleTapListener() {
          @Override
          public boolean onSingleTapConfirmed(MotionEvent e) {
              //严格单击行为。不可能是双击中的一次单击。
              return false;
          }
      
          @Override
          public boolean onDoubleTap(MotionEvent e) {
              //双击,由2次连续的单击组成。它不可能和onSingleTapConfirmed共存。
              return false;
          }
      
          @Override
          public boolean onDoubleTapEvent(MotionEvent e) {
              //表示发生了双击行为。
              // 在双击的期间,ACTION_DOWN, ACTION_MOVE 和 ACTION_UP 都会触发此回调
              return false;
              }
          };

      你可以为GestureDetector对象注入OnDoubleTapListener:

      gestureDetector.setOnDoubleTapListener(doubleTapListener);
  3. Scroller

    • 弹性滑动对象,使View的滑动有过渡效果。如果我们使用View的 scrollTo / scrollBy ,滑动过程是瞬间完成的,用户体验较差。
    • 使用方法:

      在自定义View或ViewGroup类中:

      Scroller scroller ;
      public void smoothScrollBy(int dx, int dy) {
          Log.d(TAG, "startX: " + scroller.getFinalX() + " ,startY: " + scroller.getFinalY());
          scroller.startScroll(scroller.getFinalX(), scroller.getFinalY(), dx, dy);
          invalidate();
      }
      
      @Override
      public void computeScroll() {
          if (scroller.computeScrollOffset()) {
              scrollTo(scroller.getCurrX(), scroller.getCurrY());
              postInvalidate();
          }
      }

      当需要滑动时,调用smoothScrollBy 即可。

    • scroller.startScroll 方法的参数说明:

       /**
       * Start scrolling by providing a starting point and the distance to travel.
       * The scroll will use the default value of 250 milliseconds for the
       * duration.
       * 
       * @param startX Starting horizontal scroll offset in pixels. Positive
       *        numbers will scroll the content to the left.
       * @param startY Starting vertical scroll offset in pixels. Positive numbers
       *        will scroll the content up.
       * @param dx Horizontal distance to travel. Positive numbers will scroll the
       *        content to the left.
       * @param dy Vertical distance to travel. Positive numbers will scroll the
       *        content up.
       */
      public void startScroll(int startX, int startY, int dx, int dy) {
          startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
      }
      • 当 startX > 0,起始坐标相对于View向左偏移
      • 当 startY > 0,起始坐标相对于View向上偏移
      • 当 dx > 0,View的内容移动方向为左
      • 当 dy > 0,View的内容移动方向为上

3. View的滑动

滑动方法有三种:
1. scrollToscrollBy:

```
/**
 * 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();
        }
    }
}
```

请注意,移动的是View中的内容,而View在布局中的位置不变。
参数说明:
- x > 0 : View中的内容向左移动
- y > 0 :View中的内容向上移动
多调式几次机会明白参数与移动的规律。

  1. 使用动画

    • 可以使用XML定义动画,也可以在代码直接设置:

      ObjectAnimator.ofFloat(tvTest1,"translationX",0,300).setDuration(1000).start();
    • 在”Android 开发艺术探索” 一书第132页中,作者提到移动动画结束之后的View,其位置并没有发生改变; 尽管View移动到了新位置,但只能点击其移动之前的区域,View才会响应点击事件。

      不过在测试中发现,这个问题已经被修复,android版本是比较新的API 27: Android 8.1 (Oreo)

  2. 改变布局参数:

    直接改变View的坐标和尺寸,达到移动View或改变大小的目的:

    ```
    private void moveView(View view) {
        ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
        params.leftMargin += 100;
        view.requestLayout();
        //或者重新注入布局参数:setLayoutParams
    //  view.setLayoutParams(params);
    }
    ```
    
  3. 弹性滑动:在上一小节已经介绍了Scroller实现弹性滑动的方案。此外,还可以使用动画ValueAnimator

    final int deltaX = -100;
    final ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 1).setDuration(1000);
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            float fraction = animation.getAnimatedFraction();
            llTest1.scrollTo((int) (deltaX * fraction),0);
        }
    });
    //....启动动画,放在需要的地方。
    valueAnimator.start();

    此外,还有一种方法:启动一个线程,然后不断地给Handler发送消息,在handleMessage方法中,对View调用scrollToscrollBy,就可以实现View的弹性滑动效果。不过这已经属于奇技淫巧了,用力过度,浪费不必要的开销。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值