Android中滑屏实现----手把手教你如何实现触摸滑屏以及Scroller类详解

Scroller的fling方法详解 Scroller概述 scroller的作用: 作为一个developer,要有看官方文档的习惯,文档链接。 关于scroller的作用我就不叙述了,google一下有太多的介绍 案例分析自定义一个viewgroup,在viewgroup里面有一个能滑动的view,且手指必须按在view上才能生效,松手后能缓慢的停下来; 分析:滑动view首先想到的是使用Scrooler(ViewDragHel 阅读详情

    前言:  虽然本文标题的有点标题党的感觉,但无论如何,通过这篇文章的学习以及你自己的实践认知,写个简单的滑屏小

   Demo还是just so so的。

 

        友情提示:

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

 

             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轴方向的偏移坐标

 

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

 

[java]  view plain copy print ?
  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类的相关方法.

 

     其源代码(部分)如下: 路径位于 \frameworks\base\core\java\android\widget\Scroller.java

 

[java]  view plain copy print ?
  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类中      

 

[java]  view plain copy print ?
  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()过程中,如下:

 

[java]  view plain copy print ?
  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()方法去缓慢移动至该坐标处。

 

  截图如下:

 

 

                                                         

 

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

 

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

 

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

 

[java]  view plain copy print ?
  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类),

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

 

[java]  view plain copy print ?
  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.youkuaiyun.com/detail/qinjuning/4194324 (二合一的Demo)

JAVA+Appium 自动化实现手机屏幕滑动点击操作 背景:App某些页面元素无法通过Appium或其他方式抓取到,无法通过传统方式定位到元素。 原理:获取到当前屏幕的尺寸,在要操作的元素的屏幕位置构造像素点位,进而执行点击或滑动操作。 注:坐标定位时采用相对定位方式,可适配其他分辨率。 1、坐标点击屏幕元素 <!-- https://mvnrepository.com/artifact/io.appium/java-client --> <dependency> <groupId... 阅读详情

相关推荐

autojs左右滑动脚本代码_基于AutoJs过滑块快手极速版脚本

快手极速版邀请码: 2rvxaem脚本不是我原创,说实话我也没看明白,是一个好心的网友给我的。让我参考,他既然这么无私我也无私点。把源码给大家公开一下,大家能学多少算多少吧!一起学习进步。不废话直接贴代码://坐标机型:华为P10//检查无障碍权限申请auto.waitFor();//设置屏幕宽度高度setScreenMetrics(1080, 1920);//常量设置var AppName = ...

weixin_39593469的博客 3323

关于触摸按键圆环滑动的算法分析

关于触摸按键圆环滑动的算法分析 实现圆环触摸手指定位,滑动距离测算,精度试验。 使用如图所示圆环,可以增加数量,现分辨率为一个pad,2/3pad,1/2pad,这样可实现3个pad 12个点的分辨率,如图所示:实现思想: 如图取得各个按键充电计数,有手指时计数会减少,从而通过比较未按键时计数与按键时计数得出那个按键触发,计算按键与未按键时差值得出手指引起的变化量,取变化量可得出手指触发的pad 的面积,手指影响pad面积越大,则变化量越大,可从图中看出pad面积变化,从而得出手指变化的大致曲线,现取变

qq_42415953的博客 2933

Word三线表制作全攻略:跨页续表与样式自动化

在专业文档排版中,表格设计是提升数据呈现清晰度与专业性的关键环节。其核心原理在于通过精简的视觉元素(如线条、间距)引导读者聚焦信息本质,减少干扰。从技术实现角度看,这涉及到文档处理软件的样式定义、边框控制及页面布局逻辑。掌握规范的表格制作技术,能显著提升技术报告、学术论文等文档的沟通效率与视觉专业性。本文聚焦于Word中三线表这一高频应用场景,深入解析如何通过定义可复用样式、设置标题行重复功能,解决跨页时表头丢失、样式错乱等工程实践难题,并自然融入题注联动与样式集管理,实现从手动调整到半自动化的效率跃升。

weixin_34013044的博客 382

Java实现HTML转Word:方案对比与docx4j实战指南

在文档处理与报告生成场景中,格式转换是常见的工程需求。其核心原理在于理解不同文档模型(如HTML的流式布局与Word的OOXML结构化模型)间的差异,并实现精准映射。这项技术的价值在于能自动化、高质量地将网页内容或富文本数据转换为可编辑、可打印的办公文档,从而提升内容管理、合同报告导出等后台系统的效率。实践中,开发者常面临样式丢失、图片错位等兼容性问题。针对HTML转Word这一具体场景,主流技术方案包括基于Apache POI的底层生成、模板引擎替换、专用转换库以及无头浏览器渲染。其中,使用如docx4j

wangqiaowqo的专栏 347

三线表跨页排版全攻略:Word与LaTeX实现专业文档表格处理

在学术论文与技术文档撰写中,表格排版是影响数据呈现质量的关键环节。三线表作为一种标准化的表格格式,通过顶线、栏目线和底线三条横线实现视觉清晰与结构规范。当表格内容超出一页范围时,跨页处理机制需要确保表头自动重复与格式连续性,这是文档专业性的重要体现。从排版原理层面,跨页表格涉及分页算法、标题行标记与格式继承等核心技术,其技术价值在于提升长表格的可读性、维护数据关联性并满足出版规范要求。在工程实践中,Microsoft Word通过“标题行重复”功能实现跨页表头,而LaTeX则借助longtable或xlta

weixin_28826961的博客 315

Word文档排版技巧:奇偶页眉与页码的个性化设置指南

本文详细介绍了Word文档中奇偶页眉与页码的个性化设置技巧,帮助用户解决学术论文、商业报告等正式文档的排版难题。通过启用奇偶页差异功能、设置交替页码、使用StyleRef域自动同步章节标题等步骤,提升文档的专业性和可读性。同时提供了常见问题排雷指南和专业级分节技巧,确保排版效果完美无缺。

weixin_33734785的博客 437

Origin复制页面到Word出现上划线?根源排查与彻底解决方法

在科学绘图与文档排版中,图形对象边框是控制视觉边界的基础属性。Origin的Page和Layer均支持独立边框设置,当复制页面对象到Word时,边框信息会随剪贴板传递,导致图形顶部出现多余横线。理解这一机制后,可通过关闭Page或Layer边框、选用Copy Graph或Copy as Image复制方式,以及定制无边框主题来彻底解决。该问题常见于论文投稿、报告撰写等场景,掌握源头设置能显著提升Origin转Word的工作流效率。

weixin_30745553的博客 534

踩坑总结:poi-tl处理Word表格循环时,样式丢失和格式错乱怎么破?

本文深入探讨了poi-tl在处理Word表格循环渲染时常见的样式丢失和格式错乱问题,提供了从模板设计到代码实现的全面解决方案。通过理解样式继承机制、优化模板设计、自定义RenderPolicy以及处理复杂场景,开发者可以确保生成的Word文档保持完美的格式和样式。

weixin_26765287的博客 394

避坑指南:Python处理Word转PDF时格式错乱怎么办?(附完整代码示例)

本文深入剖析了Python处理Word转PDF时格式错乱的根源,如字体缺失、表格扭曲和图片漂移,并提供了完整的解决方案。通过结合python-docx、reportlab等库,详细讲解了字体映射、表格精确绘制和图片位置校准等关键技术,附有完整代码示例,帮助开发者构建高保真的批量转换脚本,有效避免商业文档转换中的专业性问题。

weixin_29056145的博客 106

豆包回答导出Word/PDF的2步结构化方案

AI生成内容的文档交付,本质是语义结构到排版格式的可靠映射。传统复制粘贴因HTML语义丢失、CSS渲染隔离导致标题降级、公式失真、表格错位等问题频发;而基于结构化Markdown提取与Pandoc专业转换的技术路径,可完整保留标题层级、数学公式LaTeX源码、代码块语法标识等关键语义,实现开箱即用的格式一致性。该方案不依赖API或插件,纯前端本地执行,兼顾安全性、合规性与工程复用性,广泛适用于职场报告、教学讲义、技术白皮书等需正式交付的办公场景,是当前豆包用户实现‘导出即可用’的最优解。

weixin_34194964的博客 299

避坑指南:SpringBoot用poi-tl合并Word表格时常见的5个错误

本文详细解析了SpringBoot项目中利用poi-tl合并Word表格时常见的5大问题,包括表格样式丢失、动态数据绑定失败、合并条件判断失效等,并提供了经过生产验证的解决方案。特别针对poi-tl合并操作中的样式继承、数据预处理和性能优化等核心难点给出专业指导,帮助开发者高效处理Word文档合并需求。

weixin_30576827的博客 80

PDF复制粘贴到Word格式混乱?5种方法彻底解决WPS/Word格式转换难题

在文档处理中,PDF与Word格式的差异是导致内容迁移时格式混乱的核心原因。PDF作为一种固定版式的便携文档格式,其设计初衷是确保跨平台显示的一致性,但这也牺牲了内容的可编辑性和结构化信息。而Word等文字处理软件则基于XML等结构化标记语言,通过样式、段落、表格等元素构建文档。这种底层原理的差异,使得从PDF直接复制内容到Word时,常出现字体错乱、行距异常、表格结构丢失等问题,严重影响文档复用效率。针对这一常见痛点,本文聚焦于PDF转Word、OCR文字识别等关键技术,系统梳理了从基础粘贴选项调整、利用

weixin_30315435的博客 369

双栏文档中图片表格跨栏排版全攻略:Word/LaTeX/Markdown实战指南

在文档排版领域,双栏布局因其高效利用空间、提升专业感而被广泛应用于学术论文、技术报告等场景。其核心原理是通过分栏控制文本流,实现内容的紧凑排列。然而,当需要插入宽幅图片、汇总表格等强调性内容时,常规的栏内嵌入会限制展示效果,这就需要跨栏排版技术。跨栏排版通过将浮动对象的定位层级从‘栏内’提升至‘页面级’,并调整文本环绕方式,使元素突破栏宽限制,占据整个页面宽度,从而形成视觉焦点,有效引导阅读节奏,突出关键信息。这一技术在提升文档可读性、组织复杂内容方面具有重要价值。无论是学术出版中的图表展示,还是商业报告的

weixin_34268169的博客 422

Word转HTML格式保留技术方案与优化实践

文档格式转换是内容管理系统中的常见需求,特别是在线教育平台需要处理大量Word教案的数字化迁移。从技术原理看,Word使用的RTF格式与HTML/CSS的语义化标记存在本质差异,导致直接转换时出现样式丢失、公式乱码等问题。通过mammoth.js等开源库实现基础转换,结合Apache POI深度解析OOXML结构,可构建完整的格式保留方案。在工程实践中,采用客户端+服务端混合架构,配合数学公式的MathML转换和移动端CSS适配,最终实现89%的格式完整率。该方案特别适用于K12教育平台等需要高保真文档展示

weixin_29758911的博客 201

Word文档插入代码总乱格式?手把手教你用Code2Word实现完美排版(含常见问题解决方案)

本文详细介绍了如何使用Code2Word工具解决Word文档中代码排版混乱的问题,包括缩进丢失、语法高亮消失等常见问题。通过安装教程、使用步骤和高级定制技巧,帮助用户实现专业级的代码展示效果,提升技术文档的可读性和工作效率。

weixin_29021291的博客 195

EasyPoi模板语法避坑指南:为什么你的Word动态行导出总是错位?

本文深入解析EasyPoi模板语法在Word动态行导出中的常见问题,揭示$fe标签的底层机制,并提供7个关键要点确保完美排版。通过实战案例和代码示例,帮助开发者避免样式继承异常、布局错位等典型问题,实现高效的动态表格导出。

rust6ferris的博客 350

DeepSeek表格高效导出Word的4种方法与避坑指南

数据表格的跨平台迁移是数据分析工作流中的关键环节。通过CSV/HTML等中间格式转换,可以保持数据结构完整性,解决专业工具与办公软件的兼容性问题。在数据可视化场景中,条件格式、合并单元格等复杂样式的保留尤为重要。本文以DeepSeek到Word的表格导出为例,详解直接复制、CSV转换、HTML中转和VBA自动化四种方案,特别针对金融分析报告等需要保留颜色预警、特殊符号的场景提供解决方案。实践表明,配合自动调整列宽、兼容性检查等技巧,可显著提升包含278+行数据的大型表格导出效率。

weixin_26805451的博客 281

Prometheus+Grafana部署及企业微信邮件/群消息告警通知配置

本文详细介绍了在Ubuntu 22.04系统上部署普罗米修斯监控系统的完整流程。首先说明了系统配置要求(2核CPU、4G内存、100G硬盘),然后分步骤指导下载、解压和安装普罗米修斯服务。文章包含配置prometheus.yml文件、启动服务、设置系统服务等关键操作,并提供了访问9090端口验证服务的方法。接着介绍了通过node_exporter监控Linux主机的配置过程,包括安装、启动服务和修改普罗米修斯配置文件添加监控项。最后还涉及了mysqld_exporter的部署流程,用于监控MySQL数据库服

m0_58761900的博客 1638
上一篇: 【Android基础】Activity的启动模式(android:launchMode)
下一篇: Android中layout过程详解
stonenotes123
博客等级 码龄15年 30粉丝 176原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值