欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-05-05 10:05:54
...

本文涉及到的知识点: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();
     }
 }

看下效果:
Android View滑动相关的基础知识点

流程是这样的:首先在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侧滑菜单