愿我所见之人,所历之事,哪怕因为我有一点点的好的改变,我就心满意足了
上一节介绍了View的一些基础知识和概念,本节开始介绍很重要的一个内容:View的滑动。在移动端设备上,不管是Android还是Ios,滑动几乎是应用的标配,不管是下拉刷新还是侧拉滑动(SlidingMenu),它们的基础都是滑动。从另外一方面来说,Android手机由于屏幕比较小,为了给用户呈现更多的内容,就需要使用滑动来隐藏和显示一些内容。
通过三种方式可以实现View的滑动:(常见的滑动方式)
第一种是通过View本身提供的scrollTo/scrollBy方法来实现滑动;
第二种是通过动画给View施加平移效果来实现滑动;
第三种是通过改变View的LayoutParams使得View重新布局从而实现滑动。
使用scrollTo/scrollBy
为了实现View的滑动,View提供了专门的方法来实现这个功能,那就是scrollTo和scrollBy,我们先来看看这两个方法的实现,如下所示:
|
|
从上面的源码可以看出,scrollBy实际上也是调用了scrollTo方法,它实现了基于当前位置的相对滑动,而scrollTo则实现了基于所传递参数的绝对滑动。利用scrollTo和scrollBy来实现View的滑动,这不是一件困难的事,但是我们要明白滑动过程中View内部的两个属性mScrollX和mScrollY的改变规则,这两个属性可以通过getScrollX和getScrollY方法分别得到。这里先简要概述一下:在滑动过程中,mScrollX的值总是等于View左边缘和View内容左边缘在水平方向的距离,而mScrollY的值总是等于View上边缘和View内容上边缘在竖直方向的距离。View边缘是指View的位置,由四个顶点组成,而View内容边缘是指View中的内容的边缘,scrollTo和scrollBy只能改变View内容的位置而不能改变View在布局中的位置。mScrollX和mScrollY的单位为像素,并且当View左边缘在View内容左边缘的右边时,mScrollX为正值,反之为负值;当View上边缘在View内容上边缘的下边时,mScrollY为正值,反之为负值。换句话说,如果从左向右滑动,那么mScrollX为负值,反之为正值;如果从上往下滑动,那么mScrollY为负值,反之为正值。
为了更好地理解上述内容,下面举个例子,在下图中假设水平和竖直方向的滑动距离都为100像素,针对图中各种滑动情况,都给出了对应的mScrollX和mScrollY的值。根据上面的分析,使用scrollTo和scrollBy来实现View的滑动,只能将View的内容进行移动,并不能将View本身进行移动,也就是说,不管怎么滑动,也不可能将当前View滑动到附件View所在的区域。请细细品味这句话的意思。
使用动画
上面介绍了采用scrollTo/scrollBy来实现View的滑动,下面将介绍另一种滑动方式,那就是使用动画,通过动画我们能够让一个View进行平移,而平移就是一种滑动。使用动画来移动View,主要是操作View的translationX和translationY属性,既可以采用传统的View动画,也可以采用属性动画,如果采用属性动画的话,为了能够兼容3.0一下的版本,需要采用开源动画库nineoldandroids。
采用View动画的代码,此动画可以在100ms内将一个View从原始位置向右下角移动100个像素,如下所示:
|
|
如果采用属性动画的话,就更简单了,以下代码可以将一个View在100ms内从原始位置向右平移100像素。
|
|
上面简单介绍了通过使用动画来移动View的方法,关于动画会在后续进行详细说明(TODO)。使用动画来做View的滑动需要注意一点,View动画是对View的影像做操作,它并不能真正改变View的位置参数,包括宽/高,并且如果希望动画后的状态得以保留还必须将fillAfter属性设置为true,否则动画完成后其动画结果会消失。比如我们要把View向右移动100像素,如果fillAfter为false,那么在动画完成的一刹那,View会瞬间恢复到动画前的状态;如果fillAfter为true,那么在动画完成后,View会停留在距原始位置100像素的右边。使用属性动画就不会存在上述问题,但是在Android3.0以下无法使用属性动画,这个时候我们可以使用动画兼容库nineoldandroids来实现属性动画,尽管如此,在Android3.0以下的手机上通过nineoldandroids来实现的属性动画本质上仍然是View动画。
刚刚提到View动画并不能真正改变View的位置,这会带来一个很严重的问题。比如我们通过View动画将一个Button向右移动100px,并且这个View设置有onClick事件,然后你会惊奇地发现,单击新位置无法触发onClick事件,而单击原始位置仍然可以触发onClick事件,尽管Button已经不在原始位置了。这个问题带来的影响是致命的,但是它却又是可以理解的,因为不管Button怎么做交换,但是它的位置信息(四个顶点和宽/高)并不会随着动画而改变,因此在系统的眼里,这个Button并没有发生任何改变,它的真身仍然在原始位置。在这种情况下,单击新位置当然不会触发onClick事件了,因为Button的真身并没有发生改变,在新位置上只是View的影像而已。基于这一点,我们不能简单地给一个View做平移动画并且还希望它在新位置继续触发一些单击事件。
那么这种问题难道就无法解决了吗?也不是的,针对上面View动画的问题,我们可以在新位置预先创建一个和目标Button一模一样的Button,它们不但外观一样连onClick事件也一样。当目标Button完成平移动画后,就把目标Button隐藏,同时把预先创建的Button显示出来,通过这种方式我们可以解决了上面的问题,这仅仅是个参考。
改变布局参数
接下来将继续介绍第三种实现View滑动的方法,那就是改变布局参数,即改变LayoutParams。这个比较好理解,比如我们想把一个Button向右平移100px,我们只需要将这个Button的LayoutParams里的marginLeft参数的值增加100px即可,是不是很简单呢?还有一种情形,为了达到移动Button的目的,我们可以在Button的左边放置一个空的View,这个空View的默认宽度为0,当我们需要向右移动Button时,只需要重新设置空View的宽度即可,当空View的宽度增大时(假设Button的父容器是水平方向的LinearLayout),Button就自动被挤向右边,即实现了向右平移的效果。如何重新设置一个View的LayoutParams呢?如下所示:
|
|
通过改变LayoutParams的方式去实现View的滑动同样是一种很灵活的方法,需要根据不同情况去做不同的处理。
各种滑动方式的对比
上面分别介绍了三种不同的滑动方式,它们都能实现View的滑动,那么它们之间的差别是什么呢?
先看scrollTo/scrollBy这种方法,它是View提供的原生方法,其作用是专门用于View的滑动,它可以比较方便地实现滑动效果并且不影响内部元素的单击事件。但是它的缺点也是很明显的:它只能滑动View的内容,并不能滑动View的本身。
再看动画,通过动画来实现View的滑动,这要分情况。如果是Android3.0以上并采用属性动画,那么采用这种方式没有明显的缺点;如果是使用View动画或者在Android3.0以下使用属性动画,均不能改变View本身的属性。在实际使用中,如果动画元素不需要响应用户的交互,那么使用动画来做滑动是比较合适的,否则就不太合适。但是动画有一个很明显的优点,那就是一些复杂的效果必须要通过动画才能实现。
最后再看一下改变布局这种方式,它除了使用起来麻烦点以外,也没有明显的缺点,它的主要适用对象是一些具有交互性的View,因为这些View需要和用户交互,直接通过动画去实现会有问题(详情请看使用动画),所以这个时候我们可以使用直接改变布局参数的方式去实现。
针对上面的分析做一个总结,如下所示:
- scrollTo/scrollBy:操作简单,适合对View内容的滑动;
- 动画:操作简单,主要适用于没有交互的View和实现复杂的动画效果;
- 改变布局参数:操作稍微复杂,适用于有交互的View。
下面我们实现一个跟手滑动的效果,这是一个自定义View,拖动它可以让它在整个屏幕上随意滑动。这个View实现起来很简单,我们只要重写它的onTouchEvent方法并处理ACTION_MOVE事件,根据两次滑动之间的距离就可以实现它的滑动了。为了实现全屏滑动,我们采用动画的方式来实现。原因很简单,这个效果无法采用scrollTo来实现。另外,它还可以采用改变布局的方式来实现,这里仅仅是为了演示,所以就选择了动画的方式,核心代码如下所示:
|
|
通过上述代码可以看出,首先我们通过getRawX和getRawY方法来获取手指当前的坐标,注意不能使用getX和getY方法,因为这个是全屏滑动的,所以需要获取当前点击事件在屏幕中的坐标而不是相对于View本身的坐标;其次,我们要得到两次滑动之间的位移,有了这个位移就可以移动当前的View,移动方法采用的是动画兼容库nineoldandroids中的ViewHelper类所提供的setTranslationX和setTranslationY方法。实际上,ViewHelper类提供了一系列get/set方法,因为View的setTranslationX和setTranslationY只能在Android3.0及其以上版本才能使用,但是ViewHelper所提供的方法是没有版本要求的,与此类似还有setX、setScaleX、setAlpha等方法,这一系列方法实际上是为属性动画服务的,更详细的内容会在后续(TODO)进行进一步的介绍。这个自定义View可以在2.x及其以上版本工作,但是由于动画的性质,如果给它加上onClick事件,那么在3.0以上版本它将无法在新位置响应用户的点击,这个问题在前面已经提到过了。