一、View 的滑动可以通过三种方法来实现:
- 通过 View 本身提供的 scrollTo / scrollBy 方法来实现。
- 通过动画给 View 施加平移效果来实现滑动。
- 通过改变 View 的 LayoutParams 使得 View 重新布局从而实现滑动。
(一) scrollTo / scroll By
scrollTo:
scrollBy:
丛源码来看,scrollBy 调用了 scrollTo, 而最终 scrollTo 调用了 onScrollChanger() 方法。
在滑动过程中:
mScrollX 的值总是等于 View 左边缘和 View 内容左边缘在水平方向上的距离。
mScrollY 的值总是等于 View 上边缘和 View 内容上边缘在竖直方向上的距离。View 的边缘是指 View 的位置,由四个顶点组成,
而View内容的边缘是指 View 中的内容的边缘。scrollTo / scrollBy 只能改变 View 内容的位置而不能改变 View 在布局中的位置。
mScrollX 和 mScrollY 的单位为像素,并且当 View 左边缘在 View 内容左边缘的右边时,mScrollX 为正值,反之为负值;当 View 的上边缘在 View 内容的上边缘的下边时, mScrollY 为正值,反之为负值。
从左向右化滑动,那么 mScrollX 为负值,反之为正值。 从上往下滑动,哪么 mScrollY 为负值,反之为正值。
(二)使用动画
使用动画来移动 View,主要是操作 View 的 translationX 和 translationY 属性,既可以采用传统的 View 动画,也可以采用属性动画,如果采用属性动画的话,需要注意兼容 3.0 以下的版本。
(三)改变布局参数
通过 view.getLayoutParams() 方法获得LayoutParams 对象,在该对象中,通过修改margin、width等属性来实现 View 的滑动效果。
总结:
scrollTo / scrollBy: 操作简单,适合对 view 内容的滑动。
但是它只能滑动 view 的内容,不能滑动 view 的本身。动画:操作简单,主要适用于没有交互的 View 和实现复杂的动画效果。
如果使用属性动画,哪么采用这种方式没有明显的缺点。改变布局参数:操作稍微复杂,适用于有交互的 View。
二、弹性滑动的实现:
(一)使用Scroller
先看代码:
private Scroller scroller = new Scroller(getContext());
public void smoothScroollTo(int destX,int dextY,int duration){
int scrollX = getScrollX();
int deltaX = destX - scrollX;
int scrollY = getScrollY();
int deltaY = dextY - scrollY;
scroller.startScroll(scrollX,scrollY,deltaX,deltaY,duration);
invalidate();
}
@Override
public void computeScroll() {
if (scroller.computeScrollOffset()){
scrollTo(scroller.getCurrX(),scroller.getCurrY());
postInvalidate();
}
}
有几个注意的地方:
1,必须重写 View 的 computeScroll() 方法。
2,scroller.startScroll()其实并没有使 View 进行滑动,而是保存了我们传递的几个参数:
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;
}
在保存好参数之后,我们需要调用 invalidate() 方法让 View 重绘,而在 View 重绘的 draw() 方法又会去调用 computeScroll 方法。
3,computeScrollOffset:
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 的值,返回 true 表示滑动未结束。
4, 使用 scroller 只能使内容进行滑动。
(二)通过动画
属性动画可以设置duration以及插值器来实现。
ObjectAnimator animator = ObjectAnimator.ofFloat(myScrollView, "translationX", 0f, 360f);
animator.setDuration(3000);
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.start();
具体的动画学习放在以后=。=
(三)使用延时策略
使用延时策略的核心思想是,通过发送一系列延时消息从而达到一种渐进式的效果,具体来说可以使用 Handler 或 View 的 postDelayed 方法,也可以使用线程的 sleep 方法。