zoukankan      html  css  js  c++  java
  • Android的Scroller介绍 滑动

    Android的Scroller介绍

     

    正文

      一、结构

        public class Scroller extends Object

     

        java.lang.Object

          android.widget.Scroller

     

      二、概述

        这个类封装了滚动操作。滚动的持续时间可以通过构造函数传递,并且可以指定滚动动作的持续的最长时间。经过这段时间,滚动会自动定位到最终位置,并且通过computeScrollOffset()会得到的返回值为false,表明滚动动作已经结束。
     

      三、构造函数

      public Scroller (Context context)

      使用缺省的持续持续时间和动画插入器创建一个Scroller。(译者注:interpolator这里翻译为动画插入器,见这里。)

     

      public Scroller (Context context, Interpolator interpolator)

      根据指定的动画插入器创建一个Scroller,如果指定的动画插入器为空,则会使用缺省的动画插入器(粘滞viscous)创建。

     

      四、公共方法

      public void abortAnimation ()

      停止动画。与forceFinished(boolean)相反,Scroller滚动到最终xy位置时中止动画。

      参见

            forceFinished(boolean)

     

      public boolean computeScrollOffset ()

      当想要知道新的位置时,调用此函数。如果返回true,表示动画还没有结束。位置改变以提供一个新的位置。

     

      public void extendDuration (int extend)

      延长滚动动画时间。此函数允许当使用setFinalX(int) or setFinalY(int) 时,卷动动作持续更长时间并且卷动更长距离。

              参数

                  extend 卷动事件延长的时间,以毫秒为单位

              参见

                  setFinalX(int)

                  setFinalY(int)

     

      public void fling (int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY)

      在fling(译者注:快滑,用户按下触摸屏、快速移动后松开)手势基础上开始滚动。滚动的距离取决于fling的初速度。

          参数

              startX 滚动起始点X坐标

      startY 滚动起始点Y坐标

      velocityX   当滑动屏幕时X方向初速度,以每秒像素数计算

      velocityY   当滑动屏幕时Y方向初速度,以每秒像素数计算

      minX    X方向的最小值,scroller不会滚过此点。

      maxX    X方向的最大值,scroller不会滚过此点。

      minY    Y方向的最小值,scroller不会滚过此点。

      maxY    Y方向的最大值,scroller不会滚过此点。

     

      public final void forceFinished (boolean finished)

      强制终止的字段到特定值。(译者注:立即停止滚动?)

          参数

              finished    新的结束值

     

      public final int getCurrX ()

      返回当前滚动X方向的偏移

          返回值

              距离原点X方向的绝对值

     

      public final int getCurrY ()

      返回当前滚动Y方向的偏移

          返回值

              距离原点Y方向的绝对值

     

      public final int getDuration ()

      返回滚动事件的持续时间,以毫秒计算。

          返回值

              滚动持续的毫秒数

     

      public final int getFinalX ()

      返回滚动结束位置。仅针对“fling”手势有效

          返回值

              最终位置X方向距离原点的绝对距离

     

      public final int getFinalY ()

      返回滚动结束位置。仅针对“fling”操作有效

          返回值

              最终位置Y方向距离原点的绝对距离

     

      public final int getStartX ()

      返回滚动起始点的X方向的偏移

          返回值

              起始点在X方向距离原点的绝对距离

     

      public final int getStartY ()

      返回滚动起始点的Y方向的偏移

          返回值

              起始点在Y方向距离原点的绝对距离

     

      public final boolean isFinished ()

      返回scroller是否已完成滚动。

          返回值

              停止滚动返回true,否则返回false

     

      public void setFinalX (int newX)

      设置scroller的X方向终止位置

          参数

              newX    新位置在X方向距离原点的绝对偏移。

          参见

              extendDuration(int)

              setFinalY(int)

     

      public void setFinalY (int newY)

      设置scroller的Y方向终止位置

          参数

              newY    新位置在Y方向距离原点的绝对偏移。

          参见

              extendDuration(int)

              setFinalY(int)

     

      public void startScroll (int startX, int startY, int dx, int dy)

      以提供的起始点和将要滑动的距离开始滚动。滚动会使用缺省值250ms作为持续时间。

          参数

              startX 水平方向滚动的偏移值,以像素为单位。负值表明滚动将向左滚动

      startY 垂直方向滚动的偏移值,以像素为单位。负值表明滚动将向上滚动

      dx 水平方向滑动的距离,负值会使滚动向左滚动

      dy 垂直方向滑动的距离,负值会使滚动向上滚动

     

      public void startScroll (int startX, int startY, int dx, int dy, int duration)

      以提供的起始点和将要滑动的距离开始滚动。

          参数

              startX 水平方向滚动的偏移值,以像素为单位。负值表明滚动将向左滚动

      startY 垂直方向滚动的偏移值,以像素为单位。负值表明滚动将向上滚动

      dx 水平方向滑动的距离,负值会使滚动向左滚动

      dy 垂直方向滑动的距离,负值会使滚动向上滚动

            duration    滚动持续时间,以毫秒计。
     

      public int timePassed ()

      返回自滚动开始经过的时间

          返回值

                经过时间以毫秒为单位
     

      五、补充

        文章精选

          Scroller 粗浅理解

          ScrollTextView - scrolling TextView for Android

     

      Android里Scroller类是为了实现View平滑滚动的一个Helper类。通常在自定义的View时使用,在View中定义一个私有成员mScroller = new Scroller(context)。设置mScroller滚动的位置时,并不会导致View的滚动,通常是用mScroller记录/计算View滚动的位置,再重写View的computeScroll(),完成实际的滚动。 

          相关API介绍如下
     
    Java代码  收藏代码
    1. mScroller.getCurrX() //获取mScroller当前水平滚动的位置  
    2. mScroller.getCurrY() //获取mScroller当前竖直滚动的位置  
    3. mScroller.getFinalX() //获取mScroller最终停止的水平位置  
    4. mScroller.getFinalY() //获取mScroller最终停止的竖直位置  
    5. mScroller.setFinalX(int newX) //设置mScroller最终停留的水平位置,没有动画效果,直接跳到目标位置  
    6. mScroller.setFinalY(int newY) //设置mScroller最终停留的竖直位置,没有动画效果,直接跳到目标位置  
    7.   
    8. //滚动,startX, startY为开始滚动的位置,dx,dy为滚动的偏移量, duration为完成滚动的时间  
    9. mScroller.startScroll(int startX, int startY, int dx, int dy) //使用默认完成时间250ms  
    10. mScroller.startScroll(int startX, int startY, int dx, int dy, int duration)  
    11.   
    12. mScroller.computeScrollOffset() //返回值为boolean,true说明滚动尚未完成,false说明滚动已经完成。这是一个很重要的方法,通常放在View.computeScroll()中,用来判断是否滚动是否结束。  



          举例说明,自定义一个CustomView,使用Scroller实现滚动: 

    Java代码  收藏代码
    1. import android.content.Context;  
    2. import android.util.AttributeSet;  
    3. import android.util.Log;  
    4. import android.view.View;  
    5. import android.widget.LinearLayout;  
    6. import android.widget.Scroller;  
    7.   
    8. public class CustomView extends LinearLayout {  
    9.   
    10.     private static final String TAG = "Scroller";  
    11.   
    12.     private Scroller mScroller;  
    13.   
    14.     public CustomView(Context context, AttributeSet attrs) {  
    15.         super(context, attrs);  
    16.         mScroller = new Scroller(context);  
    17.     }  
    18.   
    19.     //调用此方法滚动到目标位置  
    20.     public void smoothScrollTo(int fx, int fy) {  
    21.         int dx = fx - mScroller.getFinalX();  
    22.         int dy = fy - mScroller.getFinalY();  
    23.         smoothScrollBy(dx, dy);  
    24.     }  
    25.   
    26.     //调用此方法设置滚动的相对偏移  
    27.     public void smoothScrollBy(int dx, int dy) {  
    28.   
    29.         //设置mScroller的滚动偏移量  
    30.         mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy);  
    31.         invalidate();//这里必须调用invalidate()才能保证computeScroll()会被调用,否则不一定会刷新界面,看不到滚动效果  
    32.     }  
    33.       
    34.     @Override  
    35.     public void computeScroll() {  
    36.       
    37.         //先判断mScroller滚动是否完成  
    38.         if (mScroller.computeScrollOffset()) {  
    39.           
    40.             //这里调用View的scrollTo()完成实际的滚动  
    41.             scrollTo(mScroller.getCurrX(), mScroller.getCurrY());  
    42.               
    43.             //必须调用该方法,否则不一定能看到滚动效果  
    44.             postInvalidate();  
    45.         }  
    46.         super.computeScroll();  
    47.     }  
    48. }  

     

    下面更深一点介绍Scrooler:

     

       友情提示:

                在继续往下面读之前,希望您对以下知识点有一定程度掌握,否则,继续看下去对您意义也不大。

     

                 1、掌握View(视图)的"视图坐标"以及"布局坐标",以及scrollTo()和scrollBy()方法的作用 ----- 必须理解

                          如果对这方面知识不太清楚的话,建议先看看我的这篇博客

                             <Android中滑屏初探 ---- scrollTo 以及 scrollBy方法使用说明>, 

                       不夸张地说,这篇博客理论上来说是我们这篇博文的基础。

     

                 2、知道onInterceptTouchEvent()以及onTouchEvent()对触摸事件的分发流程         ---- 不是必须

                 3、知道怎么绘制自定义ViewGroup即可                                        ---- 不是必须

     

     

         OK。 继续往下看,请一定有所准备 。大家跟着我一步一步来咯。

     

     知识点一:  关于scrollTo()和scrollBy()以及偏移坐标的设置/取值问题

     

            在前面一篇博文中《Android中滑屏初探 ---- scrollTo 以及 scrollBy方法使用说明》,我们掌握了scrollTo()和

    scrollBy()方法的作用,这两个方法的主要作用是将View/ViewGroup移至指定的坐标中,并且将偏移量保存起来。另外:

                      mScrollX 代表X轴方向的偏移坐标

                      mScrollY 代表Y轴方向的偏移坐标

     

              关于偏移量的设置我们可以参看下源码:

     

    1. package com.qin.customviewgroup;  
    2.   
    3. public class View {  
    4.     ....  
    5.     protected int mScrollX;   //该视图内容相当于视图起始坐标的偏移量   , X轴 方向      
    6.     protected int mScrollY;   //该视图内容相当于视图起始坐标的偏移量   , Y轴方向  
    7.     //返回值  
    8.     public final int getScrollX() {  
    9.         return mScrollX;  
    10.     }  
    11.     public final int getScrollY() {  
    12.         return mScrollY;  
    13.     }  
    14.     public void scrollTo(int x, int y) {  
    15.         //偏移位置发生了改变  
    16.         if (mScrollX != x || mScrollY != y) {  
    17.             int oldX = mScrollX;  
    18.             int oldY = mScrollY;  
    19.             mScrollX = x;  //赋新值,保存当前便宜量  
    20.             mScrollY = y;  
    21.             //回调onScrollChanged方法  
    22.             onScrollChanged(mScrollX, mScrollY, oldX, oldY);  
    23.             if (!awakenScrollBars()) {  
    24.                 invalidate();  //一般都引起重绘  
    25.             }  
    26.         }  
    27.     }  
    28.     // 看出原因了吧 。。 mScrollX 与 mScrollY 代表我们当前偏移的位置 , 在当前位置继续偏移(x ,y)个单位  
    29.     public void scrollBy(int x, int y) {  
    30.         scrollTo(mScrollX + x, mScrollY + y);  
    31.     }  
    32.     //...  
    33. }  

     

     

         于是,在任何时刻我们都可以获取该View/ViewGroup的偏移位置了,即调用getScrollX()方法和getScrollY()方法

     

     知识点二: Scroller类的介绍

     

             在初次看Launcher滑屏的时候,我就对Scroller类的学习感到非常蛋疼,完全失去了继续研究的欲望。如今,没得办法,

      得重新看Launcher模块,基本上将Launcher大部分类以及功能给掌握了。当然,也花了一天时间来学习Launcher里的滑屏实现

     ,基本上业是拨开云雾见真知了。

         

           我们知道想把一个View偏移至指定坐标(x,y)处,利用scrollTo()方法直接调用就OK了,但我们不能忽视的是,该方法本身

       来的的副作用:非常迅速的将View/ViewGroup偏移至目标点,而没有对这个偏移过程有任何控制,对用户而言可能是不太

       友好的。于是,基于这种偏移控制,Scroller类被设计出来了,该类的主要作用是为偏移过程制定一定的控制流程(后面我们会

       知道的更多),从而使偏移更流畅,更完美。

       

         可能上面说的比较悬乎,道理也没有讲透。下面我就根据特定情景帮助大家分析下:

     

            情景: 从上海如何到武汉?

                普通的人可能会想,so easy : 飞机、轮船、11路公交车...

                文艺的人可能会想,  小 case : 时空忍术(火影的招数)、翻个筋斗(孙大圣的招数)...

     

         不管怎么样,我们想出来的套路可能有两种:

                   1、有个时间控制过程才能抵达(缓慢的前进)                              -----     对应于Scroller的作用

                          假设做火车,这个过程可能包括: 火车速率,花费周期等;

                   2、瞬间抵达(超神太快了,都眩晕了,用户体验不太好)                     ------   对应于scrollTo()的作用

     

        模拟Scroller类的实现功能:

     

            假设从上海做动车到武汉需要10个小时,行进距离为1000km ,火车速率200/h 。采用第一种时间控制方法到达武汉的

       整个配合过程可能如下:

            我们每隔一段时间(例如1小时),计算火车应该行进的距离,然后调用scrollTo()方法,行进至该处。10小时过完后,

        我们也就达到了目的地了。

     

        相信大家心里应该有个感觉了。我们就分析下源码里去看看Scroller类的相关方法.

     

         其源代码(部分)如下: 路径位于 frameworksasecorejavaandroidwidgetScroller.java

     

    1. public class Scroller  {  
    2.   
    3.     private int mStartX;    //起始坐标点 ,  X轴方向  
    4.     private int mStartY;    //起始坐标点 ,  Y轴方向  
    5.     private int mCurrX;     //当前坐标点  X轴, 即调用startScroll函数后,经过一定时间所达到的值  
    6.     private int mCurrY;     //当前坐标点  Y轴, 即调用startScroll函数后,经过一定时间所达到的值  
    7.      
    8.     private float mDeltaX;  //应该继续滑动的距离, X轴方向  
    9.     private float mDeltaY;  //应该继续滑动的距离, Y轴方向  
    10.     private boolean mFinished;  //是否已经完成本次滑动操作, 如果完成则为 true  
    11.   
    12.     //构造函数  
    13.     public Scroller(Context context) {  
    14.         this(context, null);  
    15.     }  
    16.     public final boolean isFinished() {  
    17.         return mFinished;  
    18.     }  
    19.     //强制结束本次滑屏操作  
    20.     public final void forceFinished(boolean finished) {  
    21.         mFinished = finished;  
    22.     }  
    23.     public final int getCurrX() {  
    24.         return mCurrX;  
    25.     }  
    26.      /* Call this when you want to know the new location.  If it returns true, 
    27.      * the animation is not yet finished.  loc will be altered to provide the 
    28.      * new location. */    
    29.     //根据当前已经消逝的时间计算当前的坐标点,保存在mCurrX和mCurrY值中  
    30.     public boolean computeScrollOffset() {  
    31.         if (mFinished) {  //已经完成了本次动画控制,直接返回为false  
    32.             return false;  
    33.         }  
    34.         int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);  
    35.         if (timePassed < mDuration) {  
    36.             switch (mMode) {  
    37.             case SCROLL_MODE:  
    38.                 float x = (float)timePassed * mDurationReciprocal;  
    39.                 ...  
    40.                 mCurrX = mStartX + Math.round(x * mDeltaX);  
    41.                 mCurrY = mStartY + Math.round(x * mDeltaY);  
    42.                 break;  
    43.             ...  
    44.         }  
    45.         else {  
    46.             mCurrX = mFinalX;  
    47.             mCurrY = mFinalY;  
    48.             mFinished = true;  
    49.         }  
    50.         return true;  
    51.     }  
    52.     //开始一个动画控制,由(startX , startY)在duration时间内前进(dx,dy)个单位,即到达坐标为(startX+dx , startY+dy)出  
    53.     public void startScroll(int startX, int startY, int dx, int dy, int duration) {  
    54.         mFinished = false;  
    55.         mDuration = duration;  
    56.         mStartTime = AnimationUtils.currentAnimationTimeMillis();  
    57.         mStartX = startX;       mStartY = startY;  
    58.         mFinalX = startX + dx;  mFinalY = startY + dy;  
    59.         mDeltaX = dx;            mDeltaY = dy;  
    60.         ...  
    61.     }  
    62. }  

     

     

         其中比较重要的两个方法为:

     

                public void startScroll(int startX, int startY, int dx, int dy, int duration)

                       函数功能说明:根据当前已经消逝的时间计算当前的坐标点,保存在mCurrX和mCurrY值中

                public void startScroll(int startX, int startY, int dx, int dy, int duration)

                      函数功能说明:开始一个动画控制,由(startX , startY)在duration时间内前进(dx,dy)个单位,到达坐标为

                          (startX+dx , startY+dy)处。

     

            PS : 强烈建议大家看看该类的源码,便于后续理解。


    知识点二: computeScroll()方法介绍

     

           为了易于控制滑屏控制,Android框架提供了 computeScroll()方法去控制这个流程。在绘制View时,会在draw()过程调用该

      方法。因此, 再配合使用Scroller实例,我们就可以获得当前应该的偏移坐标,手动使View/ViewGroup偏移至该处。

         computeScroll()方法原型如下,该方法位于ViewGroup.java类中      

     

    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.      */由父视图调用用来请求子视图根据偏移值 mScrollX,mScrollY重新绘制  
    7.     public void computeScroll() { //空方法 ,自定义ViewGroup必须实现方法体  
    8.           
    9.     }  

     

     

              为了实现偏移控制,一般自定义View/ViewGroup都需要重载该方法 。

     

         其调用过程位于View绘制流程draw()过程中,如下:

     

    1. @Override  
    2. protected void dispatchDraw(Canvas canvas){  
    3.     ...  
    4.       
    5.     for (int i = 0; i < count; i++) {  
    6.         final View child = children[getChildDrawingOrder(count, i)];  
    7.         if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {  
    8.             more |= drawChild(canvas, child, drawingTime);  
    9.         }  
    10.     }  
    11. }  
    12. protected boolean drawChild(Canvas canvas, View child, long drawingTime) {  
    13.     ...  
    14.     child.computeScroll();  
    15.     ...  
    16. }  

     

     

       Demo说明:

               我们简单的复用了之前写的一个自定义ViewGroup,与以前一次有区别的是,我们没有调用scrollTo()方法去进行瞬间

           偏移。 本次做法如下:

                       第一、调用Scroller实例去产生一个偏移控制(对应于startScroll()方法)

                       第二、手动调用invalid()方法去重新绘制,剩下的就是在 computeScroll()里根据当前已经逝去的时间,获取当前

                           应该偏移的坐标(由Scroller实例对应的computeScrollOffset()计算而得),

                       第三、当前应该偏移的坐标,调用scrollBy()方法去缓慢移动至该坐标处。

     

      截图如下:

     

     

                                                             

     

                                             原始界面                                     点击按钮或者触摸屏之后的显示界面

     

            附:由于滑动截屏很难,只是简单的截取了两个个静态图片,触摸的话可以实现左右滑动切屏了。

     

               更多知识点,请看代码注释。。

     

    1. //自定义ViewGroup , 包含了三个LinearLayout控件,存放在不同的布局位置,通过scrollBy或者scrollTo方法切换  
    2. public class MultiViewGroup extends ViewGroup {  
    3.     ...  
    4.     //startScroll开始移至下一屏  
    5.     public void startMove(){  
    6.         curScreen ++ ;  
    7.         Log.i(TAG, "----startMove---- curScreen " + curScreen);  
    8.           
    9.         //使用动画控制偏移过程 , 3s内到位  
    10.         mScroller.startScroll((curScreen-1) * getWidth(), 0, getWidth(), 0,3000);  
    11.         //其实点击按钮的时候,系统会自动重新绘制View,我们还是手动加上吧。  
    12.         invalidate();  
    13.         //使用scrollTo一步到位  
    14.         //scrollTo(curScreen * MultiScreenActivity.screenWidth, 0);  
    15.     }  
    16.     // 由父视图调用用来请求子视图根据偏移值 mScrollX,mScrollY重新绘制  
    17.     @Override  
    18.     public void computeScroll() {     
    19.         // TODO Auto-generated method stub  
    20.         Log.e(TAG, "computeScroll");  
    21.         // 如果返回true,表示动画还没有结束  
    22.         // 因为前面startScroll,所以只有在startScroll完成时 才会为false  
    23.         if (mScroller.computeScrollOffset()) {  
    24.             Log.e(TAG, mScroller.getCurrX() + "======" + mScroller.getCurrY());  
    25.             // 产生了动画效果,根据当前值 每次滚动一点  
    26.             scrollTo(mScroller.getCurrX(), mScroller.getCurrY());  
    27.               
    28.             Log.e(TAG, "### getleft is " + getLeft() + " ### getRight is " + getRight());  
    29.             //此时同样也需要刷新View ,否则效果可能有误差  
    30.             postInvalidate();  
    31.         }  
    32.         else  
    33.             Log.i(TAG, "have done the scoller -----");  
    34.     }  
    35.     //马上停止移动,如果已经超过了下一屏的一半,我们强制滑到下一个屏幕  
    36.     public void stopMove(){  
    37.           
    38.         Log.v(TAG, "----stopMove ----");  
    39.           
    40.         if(mScroller != null){  
    41.             //如果动画还没结束,我们就按下了结束的按钮,那我们就结束该动画,即马上滑动指定位置  
    42.             if(!mScroller.isFinished()){  
    43.                   
    44.                 int scrollCurX= mScroller.getCurrX() ;  
    45.                 //判断是否超过下一屏的中间位置,如果达到就抵达下一屏,否则保持在原屏幕  
    46.                 // 这样的一个简单公式意思是:假设当前滑屏偏移值即 scrollCurX 加上每个屏幕一半的宽度,除以每个屏幕的宽度就是  
    47.                 //  我们目标屏所在位置了。 假如每个屏幕宽度为320dip, 我们滑到了500dip处,很显然我们应该到达第二屏  
    48.                 //即(500 + 320/2)/320 = 2  
    49.                 int descScreen = ( scrollCurX + getWidth() / 2) / getWidth() ;  
    50.                   
    51.                 Log.i(TAG, "-mScroller.is not finished scrollCurX +" + scrollCurX);  
    52.                 Log.i(TAG, "-mScroller.is not finished descScreen +" + descScreen);  
    53.                 mScroller.abortAnimation();  
    54.   
    55.                 //停止了动画,我们马上滑倒目标位置  
    56.                 scrollTo(descScreen *getWidth() , 0);  
    57.                 curScreen = descScreen ; //纠正目标屏位置  
    58.             }  
    59.             else  
    60.                 Log.i(TAG, "----OK mScroller.is  finished ---- ");  
    61.         }     
    62.     }  
    63.     ...  
    64. }  

     

     

     如何实现触摸滑屏? 

     

          其实网上有很多关于Launcher实现滑屏的博文,基本上也把道理阐释的比较明白了 。我这儿也是基于自己的理解,将一些

     重要方面的知识点给补充下,希望能帮助大家理解。

     

          想要实现滑屏操作,值得考虑的事情包括如下几个方面:

     

            其中:onInterceptTouchEvent()主要功能是控制触摸事件的分发,例如是子视图的点击事件还是滑动事件。

            其他所有处理过程均在onTouchEvent()方法里实现了。

                1、屏幕的滑动要根据手指的移动而移动  ---- 主要实现在onTouchEvent()方法中

                2、当手指松开时,可能我们并没有完全滑动至某个屏幕上,这是我们需要手动判断当前偏移至去计算目标屏(当前屏或者

                   前后屏),并且优雅的偏移到目标屏(当然是用Scroller实例咯)。

               3、调用computeScroll ()去实现缓慢移动过程。

     

      知识点介绍:              

        VelocityTracker类

               功能:  根据触摸位置计算每像素的移动速率。

               常用方法有:     

                         public void addMovement (MotionEvent ev)

                       功能:添加触摸对象MotionEvent , 用于计算触摸速率。   

                public void computeCurrentVelocity (int units)

                       功能:以每像素units单位考核移动速率。额,其实我也不太懂,赋予值1000即可。
                       参照源码 该units的意思如下:
                               参数 units : The units you would like the velocity in.  A value of 1
                                 provides pixels per millisecond, 1000 provides pixels per second, etc.

                public float getXVelocity ()

                               功能:获得X轴方向的移动速率。

     

        ViewConfiguration类

               功能: 获得一些关于timeouts(时间)、sizes(大小)、distances(距离)的标准常量值 。

               常用方法:

                      public int getScaledEdgeSlop()

                          说明:获得一个触摸移动的最小像素值。也就是说,只有超过了这个值,才代表我们该滑屏处理了。

                     public static int getLongPressTimeout()

                         说明:获得一个执行长按事件监听(onLongClickListener)的值。也就是说,对某个View按下触摸时,只有超过了

             这个时间值在,才表示我们该对该View回调长按事件了;否则,小于这个时间点松开手指,只执行onClick监听

     

     

            我能写下来的也就这么多了,更多的东西参考代码注释吧。 在掌握了上面我罗列的知识后(重点scrollTo、Scroller类),

        其他方面的知识都是关于点与点之间的计算了以及触摸事件的分发了。这方面感觉也没啥可写的。

     

    1. //自定义ViewGroup , 包含了三个LinearLayout控件,存放在不同的布局位置,通过scrollBy或者scrollTo方法切换  
    2. public class MultiViewGroup extends ViewGroup {  
    3.   
    4.     private static String TAG = "MultiViewGroup";  
    5.       
    6.     private int curScreen = 0 ;  //当前屏幕  
    7.     private Scroller mScroller = null ; //Scroller对象实例  
    8.       
    9.     public MultiViewGroup(Context context) {  
    10.         super(context);  
    11.         mContext = context;  
    12.         init();  
    13.     }  
    14.     public MultiViewGroup(Context context, AttributeSet attrs) {  
    15.         super(context, attrs);  
    16.         mContext = context;  
    17.         init();  
    18.     }  
    19.     //初始化  
    20.     private void init() {     
    21.         ...  
    22.         //初始化Scroller实例  
    23.         mScroller = new Scroller(mContext);  
    24.         // 初始化3个 LinearLayout控件  
    25.         ...  
    26.         //初始化一个最小滑动距离  
    27.         mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();  
    28.     }  
    29.     // 由父视图调用用来请求子视图根据偏移值 mScrollX,mScrollY重新绘制  
    30.     @Override  
    31.     public void computeScroll() {     
    32.         // TODO Auto-generated method stub  
    33.         Log.e(TAG, "computeScroll");  
    34.         // 如果返回true,表示动画还没有结束  
    35.         // 因为前面startScroll,所以只有在startScroll完成时 才会为false  
    36.         if (mScroller.computeScrollOffset()) {  
    37.             Log.e(TAG, mScroller.getCurrX() + "======" + mScroller.getCurrY());  
    38.             // 产生了动画效果,根据当前值 每次滚动一点  
    39.             scrollTo(mScroller.getCurrX(), mScroller.getCurrY());  
    40.               
    41.             Log.e(TAG, "### getleft is " + getLeft() + " ### getRight is " + getRight());  
    42.             //此时同样也需要刷新View ,否则效果可能有误差  
    43.             postInvalidate();  
    44.         }  
    45.         else  
    46.             Log.i(TAG, "have done the scoller -----");  
    47.     }  
    48.     //两种状态: 是否处于滑屏状态  
    49.     private static final int TOUCH_STATE_REST = 0;  //什么都没做的状态  
    50.     private static final int TOUCH_STATE_SCROLLING = 1;  //开始滑屏的状态  
    51.     private int mTouchState = TOUCH_STATE_REST; //默认是什么都没做的状态  
    52.     //--------------------------   
    53.     //处理触摸事件 ~  
    54.     public static int  SNAP_VELOCITY = 600 ;  //最小的滑动速率  
    55.     private int mTouchSlop = 0 ;              //最小滑动距离,超过了,才认为开始滑动  
    56.     private float mLastionMotionX = 0 ;       //记住上次触摸屏的位置  
    57.     //处理触摸的速率  
    58.     private VelocityTracker mVelocityTracker = null ;  
    59.       
    60.     // 这个感觉没什么作用 不管true还是false 都是会执行onTouchEvent的 因为子view里面onTouchEvent返回false了  
    61.     @Override  
    62.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
    63.         // TODO Auto-generated method stub  
    64.         Log.e(TAG, "onInterceptTouchEvent-slop:" + mTouchSlop);  
    65.   
    66.         final int action = ev.getAction();  
    67.         //表示已经开始滑动了,不需要走该Action_MOVE方法了(第一次时可能调用)。  
    68.         //该方法主要用于用户快速松开手指,又快速按下的行为。此时认为是出于滑屏状态的。  
    69.         if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {  
    70.             return true;  
    71.         }  
    72.           
    73.         final float x = ev.getX();  
    74.         final float y = ev.getY();  
    75.   
    76.         switch (action) {  
    77.         case MotionEvent.ACTION_MOVE:  
    78.             Log.e(TAG, "onInterceptTouchEvent move");  
    79.             final int xDiff = (int) Math.abs(mLastionMotionX - x);  
    80.             //超过了最小滑动距离,就可以认为开始滑动了  
    81.             if (xDiff > mTouchSlop) {  
    82.                 mTouchState = TOUCH_STATE_SCROLLING;  
    83.             }  
    84.             break;  
    85.   
    86.         case MotionEvent.ACTION_DOWN:  
    87.             Log.e(TAG, "onInterceptTouchEvent down");  
    88.             mLastionMotionX = x;  
    89.             mLastMotionY = y;  
    90.             Log.e(TAG, mScroller.isFinished() + "");  
    91.             mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;  
    92.   
    93.             break;  
    94.   
    95.         case MotionEvent.ACTION_CANCEL:  
    96.         case MotionEvent.ACTION_UP:  
    97.             Log.e(TAG, "onInterceptTouchEvent up or cancel");  
    98.             mTouchState = TOUCH_STATE_REST;  
    99.             break;  
    100.         }  
    101.         Log.e(TAG, mTouchState + "====" + TOUCH_STATE_REST);  
    102.         return mTouchState != TOUCH_STATE_REST;  
    103.     }  
    104.     public boolean onTouchEvent(MotionEvent event){  
    105.   
    106.         super.onTouchEvent(event);  
    107.           
    108.         Log.i(TAG, "--- onTouchEvent--> " );  
    109.   
    110.         // TODO Auto-generated method stub  
    111.         Log.e(TAG, "onTouchEvent start");  
    112.         //获得VelocityTracker对象,并且添加滑动对象  
    113.         if (mVelocityTracker == null) {  
    114.             mVelocityTracker = VelocityTracker.obtain();  
    115.         }  
    116.         mVelocityTracker.addMovement(event);  
    117.           
    118.         //触摸点  
    119.         float x = event.getX();  
    120.         float y = event.getY();  
    121.         switch(event.getAction()){  
    122.         case MotionEvent.ACTION_DOWN:  
    123.             //如果屏幕的动画还没结束,你就按下了,我们就结束上一次动画,即开始这次新ACTION_DOWN的动画  
    124.             if(mScroller != null){  
    125.                 if(!mScroller.isFinished()){  
    126.                     mScroller.abortAnimation();   
    127.                 }  
    128.             }  
    129.             mLastionMotionX = x ; //记住开始落下的屏幕点  
    130.             break ;  
    131.         case MotionEvent.ACTION_MOVE:  
    132.             int detaX = (int)(mLastionMotionX - x ); //每次滑动屏幕,屏幕应该移动的距离  
    133.             scrollBy(detaX, 0);//开始缓慢滑屏咯。 detaX > 0 向右滑动 , detaX < 0 向左滑动 ,  
    134.               
    135.             Log.e(TAG, "--- MotionEvent.ACTION_MOVE--> detaX is " + detaX );  
    136.             mLastionMotionX = x ;  
    137.             break ;  
    138.         case MotionEvent.ACTION_UP:  
    139.               
    140.             final VelocityTracker velocityTracker = mVelocityTracker  ;  
    141.             velocityTracker.computeCurrentVelocity(1000);  
    142.             //计算速率  
    143.             int velocityX = (int) velocityTracker.getXVelocity() ;    
    144.             Log.e(TAG , "---velocityX---" + velocityX);  
    145.               
    146.             //滑动速率达到了一个标准(快速向右滑屏,返回上一个屏幕) 马上进行切屏处理  
    147.             if (velocityX > SNAP_VELOCITY && curScreen > 0) {  
    148.                 // Fling enough to move left  
    149.                 Log.e(TAG, "snap left");  
    150.                 snapToScreen(curScreen - 1);  
    151.             }  
    152.             //快速向左滑屏,返回下一个屏幕)  
    153.             else if(velocityX < -SNAP_VELOCITY && curScreen < (getChildCount()-1)){  
    154.                 Log.e(TAG, "snap right");  
    155.                 snapToScreen(curScreen + 1);  
    156.             }  
    157.             //以上为快速移动的 ,强制切换屏幕  
    158.             else{  
    159.                 //我们是缓慢移动的,因此先判断是保留在本屏幕还是到下一屏幕  
    160.                 snapToDestination();  
    161.             }  
    162.             //回收VelocityTracker对象  
    163.             if (mVelocityTracker != null) {  
    164.                 mVelocityTracker.recycle();  
    165.                 mVelocityTracker = null;  
    166.             }  
    167.             //修正mTouchState值  
    168.             mTouchState = TOUCH_STATE_REST ;  
    169.               
    170.             break;  
    171.         case MotionEvent.ACTION_CANCEL:  
    172.             mTouchState = TOUCH_STATE_REST ;  
    173.             break;  
    174.         }  
    175.           
    176.         return true ;  
    177.     }  
    178.     ////我们是缓慢移动的,因此需要根据偏移值判断目标屏是哪个?  
    179.     private void snapToDestination(){  
    180.         //当前的偏移位置  
    181.         int scrollX = getScrollX() ;  
    182.         int scrollY = getScrollY() ;  
    183.           
    184.         Log.e(TAG, "### onTouchEvent snapToDestination ### scrollX is " + scrollX);  
    185.         //判断是否超过下一屏的中间位置,如果达到就抵达下一屏,否则保持在原屏幕      
    186.         //直接使用这个公式判断是哪一个屏幕 前后或者自己  
    187.         //判断是否超过下一屏的中间位置,如果达到就抵达下一屏,否则保持在原屏幕  
    188.         // 这样的一个简单公式意思是:假设当前滑屏偏移值即 scrollCurX 加上每个屏幕一半的宽度,除以每个屏幕的宽度就是  
    189.         //  我们目标屏所在位置了。 假如每个屏幕宽度为320dip, 我们滑到了500dip处,很显然我们应该到达第二屏  
    190.         int destScreen = (getScrollX() + MultiScreenActivity.screenWidth / 2 ) / MultiScreenActivity.screenWidth ;  
    191.           
    192.         Log.e(TAG, "### onTouchEvent  ACTION_UP### dx destScreen " + destScreen);  
    193.           
    194.         snapToScreen(destScreen);  
    195.     }  
    196.     //真正的实现跳转屏幕的方法  
    197.     private void snapToScreen(int whichScreen){   
    198.         //简单的移到目标屏幕,可能是当前屏或者下一屏幕  
    199.         //直接跳转过去,不太友好  
    200.         //scrollTo(mLastScreen * MultiScreenActivity.screenWidth, 0);  
    201.         //为了友好性,我们在增加一个动画效果  
    202.         //需要再次滑动的距离 屏或者下一屏幕的继续滑动距离  
    203.           
    204.         curScreen = whichScreen ;  
    205.         //防止屏幕越界,即超过屏幕数  
    206.         if(curScreen > getChildCount() - 1)  
    207.             curScreen = getChildCount() - 1 ;  
    208.         //为了达到下一屏幕或者当前屏幕,我们需要继续滑动的距离.根据dx值,可能想左滑动,也可能像又滑动  
    209.         int dx = curScreen * getWidth() - getScrollX() ;  
    210.           
    211.         Log.e(TAG, "### onTouchEvent  ACTION_UP### dx is " + dx);  
    212.           
    213.         mScroller.startScroll(getScrollX(), 0, dx, 0,Math.abs(dx) * 2);  
    214.           
    215.         //由于触摸事件不会重新绘制View,所以此时需要手动刷新View 否则没效果  
    216.         invalidate();  
    217.     }  
    218.     //开始滑动至下一屏  
    219.     public void startMove(){  
    220.         ...       
    221.     }  
    222.     //理解停止滑动  
    223.     public void stopMove(){  
    224.         ...  
    225.     }  
    226.     // measure过程  
    227.     @Override  
    228.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    229.        ...  
    230.     }  
    231.     // layout过程  
    232.     @Override  
    233.     protected void onLayout(boolean changed, int l, int t, int r, int b) {  
    234.         ...  
    235.     }  
    236. }  

     demo地址:http://download.csdn.net/detail/linghu_java/5573479

       最后,希望大家能多多实践,最好能写个小Demo出来。那样才正在的掌握了所学所得。

    转自http://m.blog.csdn.net/blog/linghu_java/9087841

  • 相关阅读:
    mysql优化---订单查询优化(1):视图优化+索引创建
    docker系列(一):docker基础与安装笔记
    Linux进程管理
    scrapy-redis源码解读之发送POST请求
    anaconda虚拟环境管理,从此Python版本不用愁
    Ubuntu18.04安装mongodb
    Python开发之日志记录模块:logging
    Git学习笔记:基础篇
    python开发之虚拟环境管理:virtualenv、virtualenvwrapper、pycharm
    Python开发之序列化与反序列化:pickle、json模块使用详解
  • 原文地址:https://www.cnblogs.com/wzachenjian/p/3667554.html
Copyright © 2011-2022 走看看