弹性滑动
在知道了View的滑动以后,我们还应该知道如何实现View的弹性滑动,因为比较生硬的划过去对用户的体验是极差的,所以我们应该实现渐进式滑动。
实现弹性滑动的方式
就是将一次大的滑动分成若干次小的滑动,并且在一定时间段内完成即可
下面我们将介绍三种弹性滑动的方式:
- 使用Srcoller
- 使用动画
- 使用延时策略
1.使用Scroller
在《View的事件体系——View的基础知识》一文中我们简单介绍了Scroller及其简单用法。
本文我们将探究Scroller如何实现弹性滑动。
Scroller scroller=new Scroller(getContext());
//缓慢滚动到指定位置
public void smoothScrollTo(int destX,int destY){
int scrollX = getScrollX();
int delta = destX - scrollX;
//1000ms内滑向destX,效果就是慢慢滑动
scroller.startScroll(scrollX,0,delta,0,1000);
//View的重绘
invalidate();
}
@Override
public void computeScroll(){
if(scroller.computeScrollOffset()){
scrollTo(scroller.getCurrX(),scroller.getCurrY());
//View的重绘
postInvalidate();
}
}
我们已经知道了上述的Scroller典型用法,先看看startScroll方法的源码:
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
mMode = SCROLL_MODE;
mFinished = false;
mDuration = duration;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mStartX = startX;
mStartY = startY;
mFinalX = startX + dx;
mFinalY = startY + dy;
mDeltaX = dx;
mDeltaY = dy;
mDurationReciprocal = 1.0f / (float) mDuration;
}
可以看出Scroller对象并没有做什么,只是调用startScroll方法保存了我们所传递的几个参数,startX和startY代表滑动的起点
,dx和dy表示滑动距离
,duration表示滑动时间。
那么Scroller是如何实现View的弹性滑动的呢?答案就是startScroll下面的invalidate方法。
**invalidate方法会导致View的重新绘制,在Veiw的draw方法中会去调用computeScroll方法,computeScroll在View中是一个空实现,因此我们需要自己去实现一个computeScroll方法。
简单说就是,在View重新绘制后会在draw方法中调用computeScroll,然后computeScroll会像Scroller对象获取到当前的scrollX和scrollY,然后再通过scrollTo方法实现滑动,然后调用postInvalidate方法进行第二次重新绘制,如此反复,直至整个滑动过程结束。
让我们一起看看Scroller的computeScrollOffset方法是如何实现的:
//当您想知道是否到达目标新位置时,请调用此方法。
//如果返回true,则说明动画尚未完成。
public boolean computeScrollOffset() {
if (mFinished) {
return false;
}
//计算出时间流逝的值
int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
//如果时间流失值小于设置的时间,进入循环
if (timePassed < mDuration) {
switch (mMode) {
case SCROLL_MODE:
final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
mCurrX = mStartX + Math.round(x * mDeltaX);
mCurrY = mStartY + Math.round(x * mDeltaY);
break;
case FLING_MODE:
final float t = (float) timePassed / mDuration;
final int index = (int) (NB_SAMPLES * t);
float distanceCoef = 1.f;
float velocityCoef = 0.f;
if (index < NB_SAMPLES) {
final float t_inf = (float) index / NB_SAMPLES;
final float t_sup = (float) (index + 1) / NB_SAMPLES;
final float d_inf = SPLINE_POSITION[index];
final float d_sup = SPLINE_POSITION[index + 1];
velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
distanceCoef = d_inf + (t - t_inf) * velocityCoef;
}
mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;
mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
// Pin to mMinX <= mCurrX <= mMaxX
mCurrX = Math.min(mCurrX, mMaxX);
mCurrX = Math.max(mCurrX, mMinX);
mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
// Pin to mMinY <= mCurrY <= mMaxY
mCurrY = Math.min(mCurrY, mMaxY);
mCurrY = Math.max(mCurrY, mMinY);
if (mCurrX == mFinalX && mCurrY == mFinalY) {
mFinished = true;
}
break;
}
}
else {
mCurrX = mFinalX;
mCurrY = mFinalY;
mFinished = true;
}
return true;
}
可以看出这个方法会根据时间流逝计算出当前的scrollX和scrollY的值,就是用流逝时间的百分比算出scrollX和scrollY的当前值应该为多少,有点类似于动画中插值器的概念,这里我们就不具体探究这个过程。
这个方法的返回值表示是否还需要滑动,true代表滑动未结束需要继续滑动
,false代表滑动结束不需要在滑动
。
Scroller本身并不能实现View的滑动,它需要配合View中的computeScroll方法才能完成弹性滑动的效果,它不断让View进行重绘直至View滑动结束。
Scroller弹性滑动底层还是通过scrollTo方法,所以滑动的是View的内容而非View本身。
2.使用动画
动画本身就是一个循序渐进的过程,因此使用动画具有天然的弹性滑动效果。
ObjectAnimator.ofFloat(button,"translationX",0,100).setDuration(1000).start();
以上代码就是使用属性动画将控件在1000ms内向右移动100个像素。
3.使用延时策略
延时策略的核心思想就是通过发送一系列的延时消息从而达到弹性滑动的效果。
我们可以采用以下三种方法达成目的:
- Handler
- View的postDelayed方法
- Thread的sleep方法
Handler和View的postDelayed方法的思想都是发送一系列的延时消息
,消息中让View进行滑动,当不间断的发送这种消息时,就会达到弹性滑动的效果。
Thread的sleep方法的思想是,通过一个while循环,不断的调用View滑动与sleep方法就可以达到弹性滑动的效果。
这里使用Handler举一个例子:
private static final int MESSAGE_SCROLL_TO=1;
private static final int FRAME_COUNT=10;
private static final int DELAYED_TIME=100;
private int mCount=0;
@SuppressLint("HandlerLeak")
private Handler mHandler=new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what){
case MESSAGE_SCROLL_TO:
mCount++;
if(mCount<=FRAME_COUNT){
//核心思想还是:将一次大的滑动分成若干次小滑动并在一个时间段内完成。
float fraction=mCount/(float) FRAME_COUNT;
int scrollX=(int) (fraction*100);
button.scrollTo(scrollX,0);
mHandler.sendEmptyMessageDelayed(MESSAGE_SCROLL_TO,DELAYED_TIME);
}
default:
break;
}
}
};
上述代码大约花费了1000ms将Vieiw内容向右移动了100像素,因为系统消息的调度也是需要花费时间的。
弹性滑动在Android开发中应用很广泛,以上只是简单的实现思想,在实际开发中可以根据功能的复杂度进行拓展。
View的事件体系相关博客
View的事件体系——View的基础知识
View的事件体系——View的滑动
View的事件体系——弹性滑动
View的事件体系——View的事件分发机制
View的事件体系——View的滑动冲突