Android进阶知识(九):View的滑动和弹性滑动
Android中常见的实现View的滑动的三种方式:第一种是通过View本身提供的scrollTo/scrollBy方法来实现滑动;第二种是通过动画给View施加平移效果来实现滑动;第三种是通过改变View的LayoutParams使得View重新布局从而实现滑动。
一、View的滑动
- 使用scrollTo/scrollBy
为了实现View的滑动,View提供了专门的方法来实现这个功能,那就是scrollTo和scrollBy。根据源码,scrollBy实际上也是调用了scrollTo方法,它实现了基于当前位置的相对滑动,而scrollTo则实现了基于所传递参数的绝对滑动。
在滑动过程中View内部有两个属性mScrollX和mScrollY其改变规则:在滑动过程中,mScrollX的值总是等于View左边缘和View内容左边缘在水平方向的距离,而mScrollY的值总是等于View上边缘和View内容上边缘在垂直方向上的距离。
需要注意的一点是,mScrollX和mScrollY的单位是像素,并且当View左边缘在View内容左边缘右边时,mScrollX为正值;当View上边缘在View内容下边时,mScrollY为正值。
经过上述的描述,可以发现使用scrollTo和scrollBy来实现View的滑动,只能将View的内容进行移动,而不能将View本身进行移动。并且,scrollTo和scrollBy的滑动过程是瞬间完成的。
- 使用动画
使用动画来移动View,主要是操作View的translationX和translationY属性,既可以采用传统的补间动画,也可以采用属性动画,如果采用属性动画的话,为了能够兼容3.0以下版本,需要采用开源动画库nineoldandroids。如下为使用属性动画实现View滑动的方式。
// 使用属性动画实现View的滑动——平移
ObjectAnimator.ofFloat(view, "translationX", 0f, 100f)
.setDuration(100)
.start();
关于动画的使用方法参看笔记:Android进阶知识(M):帧动画、补间动画和属性动画。
- 改变布局参数
View的第三种实现滑动的方式是改变布局参数,即改变LayoutParams。通过改变LayoutParams的方式去实现View的滑动是一种很灵活的方法,需要根据不同的情况去做不同的处理。实现方式为设置LayoutParams里的marginLeft和marginTop参数。
// 通过改变布局参数使得View滑动
ViewGroup.MarginLayoutParams layoutParams =
(ViewGroup.MarginLayoutParams) view.getLayoutParams();
layoutParams.leftMargin = x + (rawX - startX);
layoutParams.topMargin = y + (rawY - startY);
view.requestLayout(); // 或者 view.setLayoutParams(layoutParams);
- 各种滑动方式的对比
scrollTo/scrollBy方式:View提供的原生方法,操作简单并且不影响内部元素的单击事件,但是只能滑动View的内容,不能滑动View本身。适合对View内容的滑动。
动画:采用属性动画来滑动没有任何缺点,但是使用补间动画时其无法改变View本身的属性。操作简单,主要适用于没有交互的Veiw和实现复杂的动画效果。
改变布局方式:操作稍微复杂,适用于有交互的View。
二、View的弹性滑动
对于非弹性滑动,其无法实现渐近式滑动(弹性滑动),用户体验极差,比如scrollTo/scrollBy以及改变布局参数的方法,由于其瞬时性,因此属于非弹性滑动。实现弹性滑动的方式有很多,但是其共同的思想是:将一次大的滑动分成若干次小的滑动并在一个时间段内完成。弹性滑动的实现方式有:Scroller,动画以及延迟策略等。
- 使用Scroller
Scroller本身无法让View弹性滑动,它需要和View的computeScroll方法配合使用才能共同完成这个功能,使用方式如下。
Scroller mScroller = new Scroller(mContext);
// 缓慢滚动到指定位置
private void smoothScrollTo(int destX, int destY) {
int scrollX = getScrollX();
int deltaX = destX - scrollX;
// 1000ms内滑向destX,效果就是慢慢滑动
mScroller.startScroll(scrollx, 0, deltaX, 0, 1000);
invalidate();
}
@Override
public void computeScroll() {
if(mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
调用startScroll方法后,Scroller内部其实什么都没有做,它只是保存了传递的参数。需要注意的是这里的滑动指的是View内容的滑动而非View本身位置的改变。正在使得View弹性滑动的是invalidate方法,该方法会导致View的重绘,在View中draw方法又会去调用computeScroll方法,而该方法通过调用Scroller的方法进行滑动,在通过调用postInvalidate方法进行二次重绘,以此类推。
而computeScrollOffset会根据时间的流逝来计算当前的scrollX和scrollY的值。
总结一下Scroller的工作原理:Scroller配合View的computerScroll方法完成弹性滑动效果,它不断地让View重绘,而每次重绘距滑动起始时间会有一个时间间隔,通过这个间隔Scroller就可以得出View当前的滑动位置,并通过scrollTo进滑动。
- 使用动画
动画本身就是一种渐近的过程,因此通过它来实现的滑动天然就具有弹性效果。
在上一个话题中已经介绍了动画的使用方式,这里不多做介绍。
- 使用延时策略
延时策略的核心思想是通过发送一系列延时消息从而达到一种渐进式的效果,具体来说可以使用Handler或View的postDelayed方法,也可以使用线程的sleep方法。使用Handler实现的一种延时策略如下所示。
private static final int MESSAGE_SCROLL_TO = 0;
private int mCount = 0;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch(msg.what) {
case MSSAGE_SCROLL_TO:
mCount ++;
if (mCount <= 30) {
float fraction = mCount / 30f;
int scrollX = (int) (fraction * 100);
mImageView.scrollTo(scrollX, 0);
mHandler.sentEmptyMessageDelayed(MESSAGE_SCROLL_TO, 33);
}
break;
}
}
}
参考资料:《Android开发艺术探索》