Android View滑动相关的基础知识点

简介: *本文涉及到的知识点:MotionEvent、ViewConfiguration、VelocityTracker 、GestureDetector、scrollTo、scrollBy、Scroller、OverScroller*

本文涉及到的知识点:MotionEvent、ViewConfiguration、VelocityTracker 、GestureDetector、scrollTo、scrollBy、Scroller、OverScroller

MotionEvent

ACTION_DOWN :手指刚接触到屏幕
ACTION_MOVE :手指在屏幕上移动
ACTION_UP :手指在屏幕上松开的一刹那
ACTION_CANCEL:当前滑动手势被打断

 1、如果点击屏幕立即松开,事件顺序  ACTION_DOWN->ACTION_UP 
 2、如果点击屏幕滑动然后松开,事件顺序 ACTION_DOWN->ACTION_MOVE->ACTION_UP 
3、ACTION_CANCEL是当前滑动手势被打断时调用,比如在某个控件保持按下操作,然后手势从控件内部转移到外部,此时控件手势事件被打断,会触发ACTION_CANCEL

MotionEvent类中有两组方法 getX()/getY() 以及getRawX()/getRawY(),由此我们可以得到点击事件的x坐标和y坐标,他们之间不同的是getX()/getY() 返回的是相当于当前View左上角的x和y坐标,getRawX()/getRawY()返回的是相当于手机屏幕左上角的x和y坐标。

ViewConfiguration

主要用到下面三个:
1、getScaledTouchSlop():
ViewConfiguration.get(getContext()).getScaledTouchSlop()返回一个int类型的值,表示被系统认为的滑动的最小距离,小于这个值系统不认为是一次滑动 。
2、getScaledMaximumFlingVelocity()
ViewConfiguration.get(getContext()).getScaledMaximumFlingVelocity()获得一个fling手势动作的最小速度值。
3、getScaledMinimumFlingVelocity()
ViewConfiguration.get(getContext()).getScaledMinimumFlingVelocity()获得一个fling手势动作的最大速度值。

VelocityTracker

VelocityTracker 是一个跟踪触摸事件滑动速度的帮助类,用于实现flinging 及其他类似的手势,它的使用流程:
*1、通过VelocityTracker.obtain()来获得VelocityTracker 实例,
2、通过velocityTracker.addMovement(event)将用户的滑动事件传给velocityTracker
3、通过velocityTracker.computeCurrentVelocity(int units, float maxVelocity)来开始计算速度,然后调用getXVelocity(int)和getYVelocity(int)得到每个指针id检索速度。*

方法 备注
obtain() 获得VelocityTracker 实例
addMovement(MotionEvent event) 将滑动事件传给VelocityTracker
computeCurrentVelocity(int units, float maxVelocity) 计算速度 参数units是时间,单位是毫秒 maxVelocity是最大滑动速度
getXVelocity(int id) 获得X轴的速度,在使用此方法之前必须先调用computeCurrentVelocity()计算速度
getYVelocity(int id) 获得Y轴的速度,在使用此方法之前必须先调用computeCurrentVelocity()计算速度

官网给的一个例子 Tracking Movement

public class MainActivity extends Activity {
    private static final String DEBUG_TAG = "Velocity";
        ...
    private VelocityTracker mVelocityTracker = null;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int index = event.getActionIndex();
        int action = event.getActionMasked();
        int pointerId = event.getPointerId(index);

        switch(action) {
            case MotionEvent.ACTION_DOWN:
                if(mVelocityTracker == null) {
                    // Retrieve a new VelocityTracker object to watch the velocity of a motion.
                    mVelocityTracker = VelocityTracker.obtain();
                }
                else {
                    // Reset the velocity tracker back to its initial state.
                    mVelocityTracker.clear();
                }
                // Add a user's movement to the tracker.
                mVelocityTracker.addMovement(event);
                break;
            case MotionEvent.ACTION_MOVE:
                mVelocityTracker.addMovement(event);
                // When you want to determine the velocity, call
                // computeCurrentVelocity(). Then call getXVelocity()
                // and getYVelocity() to retrieve the velocity for each pointer ID.
                mVelocityTracker.computeCurrentVelocity(1000);
                // Log velocity of pixels per second
                // Best practice to use VelocityTrackerCompat where possible.
                Log.d("", "X velocity: " +
                        VelocityTrackerCompat.getXVelocity(mVelocityTracker,
                        pointerId));
                Log.d("", "Y velocity: " +
                        VelocityTrackerCompat.getYVelocity(mVelocityTracker,
                        pointerId));
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                // Return a VelocityTracker object back to be re-used by others.
                mVelocityTracker.recycle();
                break;
        }
        return true;
    }
}

GestureDetector

GestureDetector相比于OnTouchListener提供了更多的手势操作,GestureDetector类对外提供了两个接口:OnGestureListener,OnDoubleTapListener,还有一个内部类SimpleOnGestureListener

OnGestureListener有下面的几个动作:
按下(onDown): 刚刚手指接触到触摸屏的那一刹那,就是触的那一下。
抛掷(onFling): 手指在触摸屏上迅速移动,并松开的动作。
长按(onLongPress): 手指按在持续一段时间,并且没有松开。
滚动(onScroll): 手指在触摸屏上滑动。
按住(onShowPress): 手指按在触摸屏上,它的时间范围在按下起效,在长按之前。
抬起(onSingleTapUp):手指离开触摸屏的那一刹那。

官网给的例子:Detecting Common Gestures

public class MainActivity extends Activity implements
        GestureDetector.OnGestureListener,
        GestureDetector.OnDoubleTapListener{

    private static final String DEBUG_TAG = "Gestures";
    private GestureDetectorCompat mDetector;

    // Called when the activity is first created.
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Instantiate the gesture detector with the
        // application context and an implementation of
        // GestureDetector.OnGestureListener
        mDetector = new GestureDetectorCompat(this,this);
        // Set the gesture detector as the double tap
        // listener.
        mDetector.setOnDoubleTapListener(this);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event){
        this.mDetector.onTouchEvent(event);
        // Be sure to call the superclass implementation
        return super.onTouchEvent(event);
    }

    @Override
    public boolean onDown(MotionEvent event) {
        Log.d(DEBUG_TAG,"onDown: " + event.toString());
        return true;
    }

    @Override
    public boolean onFling(MotionEvent event1, MotionEvent event2,
            float velocityX, float velocityY) {
        Log.d(DEBUG_TAG, "onFling: " + event1.toString()+event2.toString());
        return true;
    }

    @Override
    public void onLongPress(MotionEvent event) {
        Log.d(DEBUG_TAG, "onLongPress: " + event.toString());
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
            float distanceY) {
        Log.d(DEBUG_TAG, "onScroll: " + e1.toString()+e2.toString());
        return true;
    }

    @Override
    public void onShowPress(MotionEvent event) {
        Log.d(DEBUG_TAG, "onShowPress: " + event.toString());
    }

    @Override
    public boolean onSingleTapUp(MotionEvent event) {
        Log.d(DEBUG_TAG, "onSingleTapUp: " + event.toString());
        return true;
    }

    @Override
    public boolean onDoubleTap(MotionEvent event) {
        Log.d(DEBUG_TAG, "onDoubleTap: " + event.toString());
        return true;
    }

    @Override
    public boolean onDoubleTapEvent(MotionEvent event) {
        Log.d(DEBUG_TAG, "onDoubleTapEvent: " + event.toString());
        return true;
    }

    @Override
    public boolean onSingleTapConfirmed(MotionEvent event) {
        Log.d(DEBUG_TAG, "onSingleTapConfirmed: " + event.toString());
        return true;
    }
}

更详细可以看下这两篇博客:
http://blog.csdn.net/xyz_lmn/article/details/16826669
http://blog.csdn.net/hpk1994/article/details/51224228

ScrollTo 、scrollBy

1、ScrollTo(int x, int y)、ScrollBy(int x, int y)

ScrollBy默认也是调用的ScrollTo方法:

public void scrollBy(int x, int y) {
      scrollTo(mScrollX + x, mScrollY + y);
  }

mScrollX 记录的是View的左边缘和View内容的左边缘之间的距离,mScrollY 记录的是View的上边缘和View内容的上边缘之间的距离

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

看源码scrollTo(int x, int y)中将x值赋给了mScrollX ,y赋给了mScrollY ,所以scrollTo是相对于View左边缘的绝对滑动,scrollBy是相对滑动,另外要注意的是:scrollTo和scrollBy是对View内容的滑动而不是对View的滑动。

Scroller常用方法:

Scroller类并不会控制View进行滑动,它只是View滑动的辅助类,负责计算View滑动的一系列参数:

方法 备注
getCurrX()、getCurrY() 距离原位置在X、Y轴方向的距离,往左滑动为正值,反之为负值;往上滑为正值,反之为负
getStartX()、getStartY() 开始滑动时距离原位置在X、Y轴方向的距离,往左滑动为正值,反之为负值;往上滑为正值,反之为负
getFinalX()、getFinalY() 滑动停止时距离原位置在X、Y轴方向的距离,往左滑动为正值,反之为负值;往上滑为正值,反之为负
startScroll(int startX, int startY, int dx, int dy) 开始滑动,默认时间250毫秒,startX、startY为开始位置,左上为正值,右下为负值,dx、dy为要滑动的距离,方向同startX、startY
startScroll(int startX, int startY, int dx, int dy, int duration) 作用同上,多了一个滑动持续时间时间duration
computeScrollOffset() 当前滑动是否已经完成,true表示已经完成,false表示还未完成。
fling(int startX, int startY, int velocityX, int velocityY,int minX, int maxX, int minY, int maxY) 手指在触摸屏上迅速移动,并松开,靠惯性滑动
abortAnimation() 强制结束动画,并滑到最终位置
forceFinished(boolean finished) 是否强制结束动画,true便是强制结束

调用Scroller的startScroll()方法并不会有任何滑动行为,这里的滑动指的是View内容的滑动而不是View的滑动,来看startScroll方法的源码:

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;
 }

可以看到只是一些赋值操作,没有任何滑动操作,所以如果想让View内容滑动,还需要invalidate()的参与,如:

 private Scroller mScroller = new Scroller(context);
 ...
 public void zoomIn() {
     // Revert any animation currently in progress
     mScroller.forceFinished(true);
     // Start scrolling by providing a starting point and
     // the distance to travel
     mScroller.startScroll(0, 0, 100, 0,3000);
     // Invalidate to request a redraw
     invalidate();
 }

 @Override
 public void computeScroll() {
     if (mScroller.computeScrollOffset()) {
         // Get current x and y positions
         int currX = mScroller.getCurrX();
         int currY = mScroller.getCurrY();
         scrollTo(currX, currY);
         invalidate();
     }
 }

看下效果:
GIF.gif

流程是这样的:首先在startScroll中设置各种滑动信息,这时并没有进行滑动,当调用下面的invalidate时,View会进行重绘,重绘时又会调用View中的computeScroll()方法,View中的computeScroll()是个空实现,需要我们自己实现,在computeScroll()中,我们先通过computeScrollOffset判断是否滑动完成,如果没完成,通过mScroller.getCurrX()、mScroller.getCurrY()得到滑动的位置,然后通过scrollTo(currX, currY)来实现滑动,此时完成了一次滑动,然后调用invalidate()方法继续重绘,继续滑动到新位置,直到滑动完成。

OverScroller

OverScroller大部分API和Scroller是一样的,只是多了一些对滑动到边缘时的处理方法:

方法 备注
isOverScrolled() 通过fling()方法只会判断是否滑动过界
springBack(int startX, int startY, int minX, int maxX, int minY, int maxY) 回弹效果
fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY, int overX, int overY) 手指在触摸屏上迅速移动,并松开,靠惯性滑动
notifyHorizontalEdgeReached(int startX, int finalX, int overX) 通知scroller已经在X轴方向到达边界
notifyVerticalEdgeReached(int startY, int finalY, int overY) 通知scroller已经在Y轴方向到达边界

OverScroller因为和Scroller非常类似,而且增加了回弹支持,所以大部分情况下我们都可以使用OverScroller。

下一篇通过模仿一下QQ侧滑菜单例子来实践一下上述知识点:Android仿QQ侧滑菜单

相关文章
|
23天前
|
缓存 测试技术 Android开发
深入探究Android中的自定义View绘制优化策略
【4月更文挑战第8天】 在Android开发实践中,自定义View的绘制性能至关重要,尤其是当涉及到复杂图形和动画时。本文将探讨几种提高自定义View绘制效率的策略,包括合理使用硬件加速、减少不必要的绘制区域以及利用缓存机制等。这些方法不仅能改善用户体验,还能提升应用的整体性能表现。通过实例分析和性能测试结果,我们将展示如何有效地实现这些优化措施,并为开发者提供实用的技术指南。
|
29天前
|
前端开发 Android开发 开发者
深入探究Android中的自定义View组件开发
【4月更文挑战第3天】 在现代Android应用程序的开发过程中,创建具有独特功能和高度定制化的用户界面是一个常见需求。为此,理解并掌握自定义View组件的开发成为了开发者必备的技能之一。本文将深入探讨如何在Android中创建自定义View,从基础的绘制原理到事件处理机制,再到性能优化技巧,旨在为读者提供一个全面的技术视角,并通过实例代码演示如何实现一个功能丰富、响应迅速的自定义View组件。
|
4月前
|
JavaScript Android开发
使用贝叶斯曲线滑动安卓屏幕(autojsPro7)
使用贝叶斯曲线滑动安卓屏幕(autojsPro7)
67 0
|
4月前
|
Android开发 容器
Android UI设计: 什么是View和ViewGroup?
Android UI设计: 什么是View和ViewGroup?
36 0
|
20天前
|
XML 数据可视化 Android开发
深入探究Android中的自定义View组件开发
【4月更文挑战第12天】在安卓应用开发中,创建具有独特交互和视觉表现的自定义View组件是增强用户体验的重要手段。本文将详细阐述如何从头开始构建一个Android自定义View,包括理解View的工作原理、处理绘制流程、事件分发机制以及属性的自定义与管理。通过具体案例分析,我们将一步步实现一个可定制的动态进度条,不仅具备基础功能,还能根据业务需求进行扩展,体现高度的产品个性化。
|
24天前
|
XML Java Android开发
Android控件之基础控件——进度条类的view——TextView、Checkbox复选控件、RadioButton单选控件、ToggleButton开关、SeekBar拖动条、menu、弹窗
Android控件之基础控件——进度条类的view——TextView、Checkbox复选控件、RadioButton单选控件、ToggleButton开关、SeekBar拖动条、menu、弹窗
|
1月前
|
前端开发 Android开发 容器
Android View介绍
Android View介绍
20 0
|
4月前
|
Android开发 Kotlin 索引
Android Compose——ScrollableTabRow和LazyColumn同步滑动
Android Compose——ScrollableTabRow和LazyColumn同步滑动
|
5月前
|
XML API Android开发
Android 自定义View 之 Dialog弹窗
Android 自定义View 之 Dialog弹窗
|
5月前
|
XML API Android开发
Android 自定义View 之 饼状进度条
Android 自定义View 之 饼状进度条