zoukankan      html  css  js  c++  java
  • 代码讲解Android Scroller、VelocityTracker

    在编写自定义滑动控件时常常会用到Android触摸机制和Scroller及VelocityTracker。Android Touch系统简介(二):实例详解onInterceptTouchEvent与onTouchEvent的调用过程对Android触摸机制需要用到的函数进行了详细的解释,本文主要介绍两个重要的类:Scroller及VelocityTracker。利用上述知识,最后给出了一个自定义滑动控件的demo,该demo类似于ImageGallery。ImageGallery一般是用GridView来实现的,可以左右滑动。本例子实现的控件直接继承一个ViewGroup,对其回调函数如 onTouchEvent、onInterceptTouchEvent、computeScroll等进行重载。弄懂该代码,对Android touch的认识将会更深一层。

    VelocityTracker:用于对触摸点的速度跟踪,方便获取触摸点的速度。
    用法:一般在onTouchEvent事件中被调用,先在down事件中获取一个VecolityTracker对象,然后在move或up事件中获取速度,调用流程可如下列所示:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. VelocityTracker vTracker = null;  
    2. @Override    
    3. public boolean onTouchEvent(MotionEvent event){    
    4.     int action = event.getAction();    
    5.     switch(action){    
    6.     case MotionEvent.ACTION_DOWN:    
    7.         if(vTracker == null){    
    8.             vTracker = VelocityTracker.obtain();    
    9.         }else{    
    10.             vTracker.clear();    
    11.         }    
    12.         vTracker.addMovement(event);    
    13.         break;    
    14.     case MotionEvent.ACTION_MOVE:    
    15.         vTracker.addMovement(event);    
    16.         //设置单位,1000 表示每秒多少像素(pix/second),1代表每微秒多少像素(pix/millisecond)。   
    17.         vTracker.computeCurrentVelocity(1000);    
    18.         //从左向右划返回正数,从右向左划返回负数  
    19.         System.out.println("the x velocity is "+vTracker.getXVelocity());    
    20.         //从上往下划返回正数,从下往上划返回负数  
    21.         System.out.println("the y velocity is "+vTracker.getYVelocity());    
    22.         break;    
    23.     case MotionEvent.ACTION_UP:    
    24.     case MotionEvent.ACTION_CANCEL:    
    25.         vTracker.recycle();    
    26.         break;    
    27.     }    
    28.     return true;    
    29. }    



    Scroller:用于跟踪控件滑动的轨迹,此类不会移动控件,需要你在View的一个回调函数computerScroll()中使用Scroller对象还获取滑动的数据来控制某个View。

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1.   /** 
    2.  * Called by a parent to request that a child update its values for mScrollX 
    3.  * and mScrollY if necessary. This will typically be done if the child is 
    4.  * animating a scroll using a {@link android.widget.Scroller Scroller} 
    5.  * object. 
    6.  */  
    7. public void computeScroll()  
    8. {  
    9. }  

    parentView在绘制式,会调用dispatchDraw(Canvas canvas),该函数会调用ViewGroup中的每个子view的boolean draw(Canvas canvas, ViewGroup parent, long drawingTime),用户绘制View,此函数在绘制View的过程中会调用computeScroll()
    下面给出一段代码:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. @Override  
    2. public void computeScroll() {     
    3.     // TODO Auto-generated method stub  
    4.     Log.e(TAG, "computeScroll");  
    5.     if (mScroller.computeScrollOffset()) { //or !mScroller.isFinished()  
    6.         Log.e(TAG, mScroller.getCurrX() + "======" + mScroller.getCurrY());  
    7.         scrollTo(mScroller.getCurrX(), mScroller.getCurrY());  
    8.         Log.e(TAG, "### getleft is " + getLeft() + " ### getRight is " + getRight());  
    9.         postInvalidate();  
    10.     }  
    11.     else  
    12.         Log.i(TAG, "have done the scoller -----");  
    13. }  

    这段代码在滑动view之前先调用mScroller.computeScrollOffset()来判断滑动动画是否已结束。computerScrollerOffset()的源代码如下:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. /** 
    2.  * Call this when you want to know the new location.  If it returns true, 
    3.  * the animation is not yet finished. 
    4.  */   
    5. public boolean computeScrollOffset() {  
    6.     if (mFinished) {  
    7.         return false;  
    8.     }  
    9.       
    10.     //滑动已经持续的时间  
    11.     int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);  
    12.     //若在规定时间还未用完,则继续设置新的滑动位置mCurrX和mCurry  
    13.     if (timePassed < mDuration) {  
    14.         switch (mMode) {  
    15.         case SCROLL_MODE:  
    16.             float x = timePassed * mDurationReciprocal;  
    17.   
    18.             if (mInterpolator == null)  
    19.                 x = viscousFluid(x);   
    20.             else  
    21.                 x = mInterpolator.getInterpolation(x);  
    22.   
    23.             mCurrX = mStartX + Math.round(x * mDeltaX);  
    24.             mCurrY = mStartY + Math.round(x * mDeltaY);  
    25.             break;  
    26.         case FLING_MODE:  
    27.             final float t = (float) timePassed / mDuration;  
    28.             final int index = (int) (NB_SAMPLES * t);  
    29.             float distanceCoef = 1.f;  
    30.             float velocityCoef = 0.f;  
    31.             if (index < NB_SAMPLES) {  
    32.                 final float t_inf = (float) index / NB_SAMPLES;  
    33.                 final float t_sup = (float) (index + 1) / NB_SAMPLES;  
    34.                 final float d_inf = SPLINE_POSITION[index];  
    35.                 final float d_sup = SPLINE_POSITION[index + 1];  
    36.                 velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);  
    37.                 distanceCoef = d_inf + (t - t_inf) * velocityCoef;  
    38.             }  
    39.   
    40.             mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;  
    41.               
    42.             mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));  
    43.             // Pin to mMinX <= mCurrX <= mMaxX  
    44.             mCurrX = Math.min(mCurrX, mMaxX);  
    45.             mCurrX = Math.max(mCurrX, mMinX);  
    46.               
    47.             mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));  
    48.             // Pin to mMinY <= mCurrY <= mMaxY  
    49.             mCurrY = Math.min(mCurrY, mMaxY);  
    50.             mCurrY = Math.max(mCurrY, mMinY);  
    51.   
    52.             if (mCurrX == mFinalX && mCurrY == mFinalY) {  
    53.                 mFinished = true;  
    54.             }  
    55.   
    56.             break;  
    57.         }  
    58.     }  
    59.     else {  
    60.         mCurrX = mFinalX;  
    61.         mCurrY = mFinalY;  
    62.         mFinished = true;  
    63.     }  
    64.     return true;  
    65. }  

    ViewGroup.computeScroll()被调用时机:
    当我们执行ontouch或invalidate()或postInvalidate()都会导致这个方法的执行。

    我们在开发控件时,常会有这样的需求:当单机某个按钮时,某个图片会在规定的时间内滑出窗口,而不是一下子进入窗口。实现这个功能可以使用Scroller来实现。
    下面给出一段代码,该代码控制下一个界面在3秒时间内缓慢进入的效果。

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. public void moveToRightSide(){  
    2.     if (curScreen <= 0) {  
    3.         return;  
    4.     }  
    5.     curScreen-- ;  
    6.     Log.i(TAG, "----moveToRightSide---- curScreen " + curScreen);  
    7.     mScroller.startScroll((curScreen + 1) * getWidth(), 0, -getWidth(), 0, 3000);  
    8.     scrollTo(curScreen * getWidth(), 0);  
    9.     invalidate();  
    10. }  

    上述代码用到了一个函数:void android.widget.Scroller.startScroll(int startX, int startY, int dx, int dy, int duration)
    当startScroll执行过程中即在duration时间内,computeScrollOffset  方法会一直返回true,但当动画执行完成后会返回返加false.
    这个函数的源码如下所示,主要用于设置滑动参数

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. /** 
    2.  * Start scrolling by providing a starting point, the distance to travel, 
    3.  * and the duration of the scroll. 
    4.  *  
    5.  * @param startX Starting horizontal scroll offset in pixels. Positive 
    6.  *        numbers will scroll the content to the left. 
    7.  * @param startY Starting vertical scroll offset in pixels. Positive numbers 
    8.  *        will scroll the content up. 
    9.  * @param dx Horizontal distance to travel. Positive numbers will scroll the 
    10.  *        content to the left. 
    11.  * @param dy Vertical distance to travel. Positive numbers will scroll the 
    12.  *        content up. 
    13.  * @param duration Duration of the scroll in milliseconds. 
    14.  */  
    15. public void startScroll(int startX, int startY, int dx, int dy, int duration) {  
    16.     mMode = SCROLL_MODE;  
    17.     mFinished = false;  
    18.     mDuration = duration;  
    19.     mStartTime = AnimationUtils.currentAnimationTimeMillis();  
    20.     mStartX = startX;  
    21.     mStartY = startY;  
    22.     mFinalX = startX + dx;  
    23.     mFinalY = startY + dy;  
    24.     mDeltaX = dx;  
    25.     mDeltaY = dy;  
    26.     mDurationReciprocal = 1.0f / (float) mDuration;  
    27. }  

    invalidate()会使得视图重绘,导致parent调用了dispatchDraw(Canvas canvas),然后递归调用child View的draw()函数,该函数又会调用我们定义的computeScroll(), 而这个函数又会调用mScroller.computeScrollOffset()判断动画是否结束,若没结束则继续重绘直到直到startScroll中设置的时间耗尽mScroller.computeScrollOffset()返回false才停下来。

    附上完整的实例代码:

    自定义Android可滑动控件源码

    运行效果图如下,滑动屏幕会显示不同的图片。

  • 相关阅读:
    【消息队列MQ】各类MQ比较
    MySql查询功能梳理
    头条日常实习生面经 2018.11.28
    排序算法 JavaScript
    浅谈二分查找 JavaScript
    LeetCode17.电话号码的字母组合 JavaScript
    LeetCode16.最接近的三数之和 JavaScript
    LeetCode15.三数之和 JavaScript
    LeetCode14.最长公共前缀 JavaScript
    LeetCode13.罗马数字转整数 JavaScript
  • 原文地址:https://www.cnblogs.com/Free-Thinker/p/5501640.html
Copyright © 2011-2022 走看看