转载请注明出处:http://blog.csdn.net/zhaokaiqiang1992 

    话接上文,在前一篇文章里面,咱们一起分析了“知乎”的回答详情页的需求,然后顺便用代码实现了下,忘了的可以再去看看【凯子哥带你夯实应用层】都说“知乎”逼格高,我们来实现“知乎”回答详情页动画效果 。其实在很多的界面效果中,这种“滚动”的效果能带来很多的惊喜,各种效果也很有搞头,说不定什么时候,Boss看着哪个界面好看,就让你去仿个过来,你要是说不会,那你下个月的工资还想发不!所以呢,今天这篇文章,就结合着一些案例,来稍微系统的总结一下Android系统中,如果要实现界面滚动,所涉及到的几个常用类。

    Scroller和OverScroller,这两个是AndroidUI框架下实现滚动效果的最关键的类,ScrollView内部的实现也是使用的OverScroller,所以熟练的使用这两个类的相关API,可以让我们满足大部分的开发需求。

    在View类里面,有两个和滚动相关的类,scrollTo()和scrollBy。这两个方法可以实现View内容的移动,注意,是内容,不是位置!是移动,不是滚动!什么叫做内容呢?比如说一个TextView,如果使用scrollTo(),那么移动的是里面的文字,而不是位置,scrollBy()也是一样的。那么为什么是移动,不是滚动呢?这是因为这两个方法完成的都是瞬间完成,即瞬移,而不是带有滚动过程的滚动,所以说,如果要实现效果比较好的滚动,光靠View自带的方法还是不行滴,还是要Scrollers出马~

    但是!Scrollers并不是控制View进行滚动,包括内容或者是位置,实际上,Scrollers只是一个控件移动轨迹的辅助计算类,如果你想滚,他能帮你计算什么时间应该滚到什么位置,但是滚不滚,全靠你自觉~所以说,滚动位置由Scrollers计算出来了,我们在什么时候滚呢?滚多少呢?这时候,就要View的一个回调函数computeScroll()出马了。

    我们看看View里面的computeScroll()做了些什么

[java]  view plain copy
  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.     }  

     duang!空的!不过没事,看看注释,就是说,如果我们用Scroller实现一个滚动动画的时候,这个方法就会被调用,被谁调用呢?parent,谁改变呢?child。所以一般来说,这个方法可以用来更新mScrollX和mScrollY,但是其实不光可以改变这些,我们还能做其他事情。

    如果我们调用Scroller.startScroll(int startX, int startY, int dx, int dy),那么我们就可以在computeScroll()里面执行实际的操作了,就像下面这样

[java]  view plain copy
  1. @Override  
  2.     public void computeScroll() {  
  3.   
  4.         // 先判断mScroller滚动是否完成  
  5.         if (mScroller.computeScrollOffset()) {  
  6.             // 这里调用View的scrollTo()完成实际的滚动  
  7.             scrollTo( mScroller.getCurrX(), mScroller .getCurrY());  
  8.             // 必须调用该方法,否则不一定能看到滚动效果  
  9.             invalidate();  
  10.         }  
  11.         super.computeScroll();  
  12.     }  

     Scroller.computeScrollOffset方法是来判断滚动过程是否完成的,如果没有完成,就需要不停的scrollTo下去,所以在最后需要加一个invalidate(),这样可以再次触发computScroll,直到滚动已经结束。


    其实说到这里,有的同学可能比较迷惑,OverScroller和Scroller有什么区别呢?事实上,这两个类都属于Scrollers,Scroller出现的比较早,在API1就有了,OverScroller是在API9才添加上的,出现的比较晚,所以功能比较完善,Over的意思就是超出,即OverScroller提供了对超出滑动边界的情况的处理,这两个类80%的API是一致的,OverScroller比Scroller添加了一下几个方法

    ☞ isOverScrolled()
    ☞ 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)
    ☞ notifyVerticalEdgeReached(int startY, int finalY, int overY)

    从名字也能看出来,都是对Over功能的支持,其他的API都一样,所以介绍通用API的时候,并不区分OverScroller和Scroller。


    下面简单介绍一下常用的API。

    ☞ computeScrollOffset() 这个就是来判断当前的滑动动作是否完成的,用法很单一,就是在computeScroll()里面来做判断滚动是否完成

    ☞ getCurrX() 这个就是获取当前滑动的坐标值,因为Scrollers只是一个辅助计算类,所以如果我们想获取滑动时的时时坐标,就可以通过这个方法获得,然后在computeScroll()里面调用

    ☞ getFinalX() 这个是用来获取最终滑动停止时的坐标

    ☞ isFinished() 用来判断当前滚动是否结束

    ☞ startScroll(int startX, int startY, int dx, int dy) 用来开始滚动,这个是很重要的一个触发computeScroll()的方法,调用这个方法之后,我们就可以在computeScroll里面获取滚动的信息,然后完成我们的需要。这个还有一个带有滚动持续时间的重载函数,可以根据需求自由使用。特别要注意这四个参数,startX和startY是开始的坐标位置,正数左上,负数右下,dx、dy同理,当在computeScroll()获取getCurrX()的时候,变化范围就与这里地设置有关。

[java]  view plain copy
  1. /** 
  2.     * Start scrolling by providing a starting point and the distance to travel. 
  3.     * The scroll will use the default value of 250 milliseconds for the 
  4.     * duration. 
  5.     *  
  6.     * @param startX Starting horizontal scroll offset in pixels. Positive 
  7.     *        numbers will scroll the content to the left. 
  8.     * @param startY Starting vertical scroll offset in pixels. Positive numbers 
  9.     *        will scroll the content up. 
  10.     * @param dx Horizontal distance to travel. Positive numbers will scroll the 
  11.     *        content to the left. 
  12.     * @param dy Vertical distance to travel. Positive numbers will scroll the 
  13.     *        content up. 
  14.     */  
  15.    public void startScroll(int startX, int startY, int dx, int dy) {  
  16.        startScroll(startX, startY, dx, dy, DEFAULT_DURATION);  
  17.    }  
    ☞ fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY) 这个方法也很重要,如果你想实现滑动之后,布局能够根据移动速度,慢慢减速的话,就需要用这个来实现,这里需要加速度的参数,我们可以通过VelocityTracker这个类来获取,然后使用,具体参数函数,在下面的实例中进行说明。


    说了这么多东西,都是最基础的,也是最没意思的,下面通过几个小例子,我们来简单地使用以下这些API,加深理解。

    因为gif帧率太低,不能很好地展示效果,所以我录取了一个视频,请大家戳这里(演示视频)查看演示视频,选择720P高清播放。

    顺便贴一下代码,在后面对代码进行解读。

[java]  view plain copy
  1. public void click(View view) {  
  2.   
  3.         switch (view.getId()) {  
  4.             case R.id.btn_scroll_to:  
  5.                 textView.scrollTo(distance, 0);  
  6.                 distance += 10;  
  7.                 break;  
  8.             case R.id.btn_scroll_by:  
  9.                 textView.scrollBy(300);  
  10.                 break;  
  11.             case R.id.btn_sping_back:  
  12.                 //不知道为什么第一次调用会贴墙,即到达x=0的位置  
  13.                 textView.spingBack();  
  14.                 break;  
  15.         }  
  16.   
  17.     }  

     首先点击了scrollBy()三次,这个函数是相对坐标移动,与当前坐标无关,而scrollTo()则是绝对坐标移动,如果distance相同的话,第二次就不会移动了,其实scrollBy()在源码上也是scrollTo()

[java]  view plain copy
  1. /** 
  2.  * Move the scrolled position of your view. This will cause a call to 
  3.  * {@link #onScrollChanged(int, int, int, int)} and the view will be 
  4.  * invalidated. 
  5.  * @param x the amount of pixels to scroll by horizontally 
  6.  * @param y the amount of pixels to scroll by vertically 
  7.  */  
  8. public void scrollBy(int x, int y) {  
  9.     scrollTo(mScrollX + x, mScrollY + y);  
  10. }  

     这样就明白为什么3次scrollBy()之后,调用scrollTo()之后,内容会移动回来了,以为前面的移动是30*3=90,而scrollTo()第一次调用distance是30,所以坐标就回来了,视觉上就是后退回来。

    第三个拖拽回弹效果用的是一个自定义控件,下面我们会详细的分析实现。

    第四个效果是spingBack(),即OverScroller的回弹效果,我们顺便也介绍了。

    OK,咱们开始介绍这个可以回弹的自定义TextView是如何实现这种效果的。

    下面是实现的代码

[java]  view plain copy
  1. /** 
  2.  * Created by zhaokaiqiang on 15/2/28. 
  3.  */  
  4. public class JellyTextView extends TextView {  
  5.   
  6.     private OverScroller mScroller;  
  7.   
  8.     private float lastX;  
  9.     private float lastY;  
  10.   
  11.     private float startX;  
  12.     private float startY;  
  13.   
  14.     public JellyTextView(Context context, AttributeSet attrs) {  
  15.         super(context, attrs);  
  16.         mScroller = new OverScroller(context, new BounceInterpolator());  
  17.     }  
  18.   
  19.   
  20.     @Override  
  21.     public boolean onTouchEvent(MotionEvent event) {  
  22.   
  23.         switch (event.getAction()) {  
  24.             case MotionEvent.ACTION_DOWN:  
  25.                 lastX = event.getRawX();  
  26.                 lastY = event.getRawY();  
  27.                 break;  
  28.             case MotionEvent.ACTION_MOVE:  
  29.                 float disX = event.getRawX() - lastX;  
  30.                 float disY = event.getRawY() - lastY;  
  31.   
  32.                 offsetLeftAndRight((int) disX);  
  33.                 offsetTopAndBottom((int) disY);  
  34.                 lastX = event.getRawX();  
  35.                 lastY = event.getRawY();  
  36.                 break;  
  37.             case MotionEvent.ACTION_UP:  
  38.                 mScroller.startScroll((int) getX(), (int) getY(), -(int) (getX() - startX),  
  39.                         -(int) (getY() - startY));  
  40.                 invalidate();  
  41.                 break;  
  42.         }  
  43.   
  44.         return super.onTouchEvent(event);  
  45.     }  
  46.   
  47.   
  48.     @Override  
  49.     public void computeScroll() {  
  50.   
  51.         if (mScroller.computeScrollOffset()) {  
  52.             setX(mScroller.getCurrX());  
  53.             setY(mScroller.getCurrY());  
  54.             invalidate();  
  55.         }  
  56.   
  57.     }  
  58.   
  59.     @Override  
  60.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
  61.         super.onSizeChanged(w, h, oldw, oldh);  
  62.         startX = getX();  
  63.         startY = getY();  
  64.     }  
  65.   
  66.     public void spingBack() {  
  67.   
  68.         if (mScroller.springBack((int) getX(), (int) getY(), 0, (int) getX(), 0,  
  69.                 (int) getY() - 100)) {  
  70.             Log.d("TAG""getX()=" + getX() + "__getY()=" + getY());  
  71.             invalidate();  
  72.         }  
  73.   
  74.     }  
  75.   
  76.   
  77. }  

     代码不到一百行,是不是很简单呀,实现的效果是类似果冻的颤动效果,来来来,凯子哥带你分析下代码实现。

    首先我们用的是OverScroller,因为和Scroller非常类似,而且增加了回弹支持,所以大部分情况下我们都可以使用OverScroller。我们在构造函数完成初始化,然后因为我们需要记录最开始的位置,在回弹的时候需要用,所以在onSizeChange()完成了起始坐标的初始化。为了完成拖拽功能,我们需要重写onTouch,然后在MOVE事件中,完成控件的位置移动,用offsetLeftAndRight和offsetTopAndBottom即可,参数是一个相对位移的距离,所以很简单就完成了控件跟随手指移动的效果。

    最后的效果当然是控件回弹,但是这里的回弹并不是用spingBack()完成,而是通过startScroll()完成,只要设置好当前的位置和我们需要位移的距离,然后记住invalidate一下,我们就可以去computeScroll()里面实际的改变控件的位置了,通过getCurrX()就可以获取到如果当前滚动应该的位置,所以setX()就OK啦,很简单是不是?不过要记住invalidate(),这样才能继续往下触发未完成的滚动操作。

    另外发现没,这个控件叫JellyTextView,就是果冻TextView,因为实现的是有来回颤动的效果,这个怎么实现呢?也很简单,设置一个BounceInterpolation就可以了,so easy~

    OK,其实现在大部分的Scroller的用法我们都用过了,还剩下一个OverScroll特有的spingBack()和fling(),我们先介绍一个spingBack的用法。

    springBack(int startX, int startY, int minX, int maxX, int minY, int maxY) 

    看上面的参数,前两个是开始位置,是绝对坐标,minX和maxX是用来设定滚动范围的,也是绝对坐标范围,如果startX不在这个范围里面,比如大于maxX,就会触发computeScroll(),我们可以移动距离,最终回弹到maxX所在的位置,并返回true,从而完成后续的滚动效果,比minX小的话,就会回弹到minX,一样的道理。所以我们可以像上面代码里面一样,判断是否在范围内,在的话,就invalidate()一下,触发滚动动画,所以名字叫spingBack(),即回弹,在上面的视频里有演示效果。参照效果和代码,你应该能看明白用法。

更多相关文章

  1. Android进阶之光读书笔记:View体系(一) View与 ViewGroup、View坐标
  2. Android 位置服务——BaiduLocation的使用
  3. Android 动画分析之翻转效果
  4. 如何在Android中实现全屏,去掉标题栏效果
  5. Android实现自定义滑动式抽屉效果菜单
  6. 自定义RadioButton样式并去除默认样式位置【Android】
  7. Android UI控件之ListView实现圆角效果
  8. 《android 的四中动画效果》
  9. Android 自定义view的简单应用(2) 毛玻璃效果

随机推荐

  1. Android中Notification详解【android进化
  2. Android 项目开发基础再回顾(一)
  3. android studio设置debug.keystore
  4. Android安装配置注意
  5. Android监控外接USB设备和获取USB等设备
  6. Android Asynchronous HTTPClient tutori
  7. 开源项目之Android RibbonMenu(导航菜单)
  8. android studio 中使用fastjson
  9. 收藏的Android非常好用的组件或者框架
  10. android设备上视频只有声音没有图像