以前买了一本《Android 开发艺术探索》,当时看完也是感觉受益匪浅,书上面也是留下了努力学习的笔记,哈哈,结果不知道怎么搞丢了,也是艰难,最近又新买了一本,看起来还是感觉受益匪浅,哈哈。
先看一个简单的使用Scroller的例子
1562401862002342.gif
从上面的图片中也可以看出来,这里的滚动是指View内容的滚动而非View本身位置的改变。
上面例子中使用到的自定义的TestSmoothScrollView,代码如下
class TestSmoothScrollView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private var scroller: Scroller = Scroller(context)
private val paint = Paint()
private var color: Int = 0
init {
color = context.resources.getColor(R.color.colorAccent)
paint.color = color
paint.style = Paint.Style.FILL
}
override fun onDraw(canvas: Canvas) {
canvas.drawColor(color)
paint.color = context.resources.getColor(R.color.colorPrimary)
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
}
/**
* 使用 scroller滚动
*
* @param destX 在水平方滚动到的目的地
* @param destY 竖直方向上滚动的目的地
*/
fun smoothScrollTo(destX: Int, destY: Int) {
//要滚动的距离
val deltaX = destX - scrollX
val deltaY = destY - scrollY
scroller.startScroll(scrollX, scrollY, deltaX, deltaY, 1000)
invalidate()
}
override fun computeScroll() {
if (scroller.computeScrollOffset()) {
scrollTo(scroller.currX, scroller.currY)
invalidate()
}
}
}
调用TestSmoothScrollView的smoothScrollTo方法即可实现滚动。
btnStartScroll.setOnClickListener {
//向右下方向滚动100像素
smoothScrollView.smoothScrollTo(-100, -100)
}
我们先来看一下TestSmoothScrollView的smoothScrollTo方法
/**
* 使用 scroller滚动
*
* @param destX 在水平方滚动到的目的地
* @param destY 竖直方向上滚动的目的地
*/
fun smoothScrollTo(destX: Int, destY: Int) {
//计算出要滚动的距离
val deltaX = destX - scrollX
val deltaY = destY - scrollY
//注释1处
scroller.startScroll(scrollX, scrollY, deltaX, deltaY, 1000)
//注释2处
invalidate()
}
首先我们根据View当前的scrollX,scrollY 和传入的参数计算出水平和竖直方向上要滚动的距离。然后在注释1处调用了Scroller的startScroll方法。
Scroller的startScroll方法
/**
* 通过提供一个起点,滚动距离和滚动时间开始滚动。
*
* @param startX 水平方向上的滚动起点,单位是像素。
* @param startY 竖直方向上的滚动起点,单位是像素。
* @param dx 水平滚动距离,单位是像素。正值会使View的内容向左滚动。
* @param dy 竖直方向上的滚动距离,单位是像素。正值会使View的内容向上滚动。
* @param 滚动时间,单位是毫秒
*/
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
//为mMode赋值为SCROLL_MODE
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方法中,代码很简单,只是保存了某些值。有几个比较重要的点。
- 将mMode赋值为SCROLL_MODE
- 为开始滚动时间mStartTime赋值
- 计算滚动时间的倒数mDurationReciprocal
但是我们发现这里并没有让View滚动起来。那么View是怎么滚动起来的呢,答案就是TestSmoothScrollView的smoothScrollTo方法的注释2处,调用了invalidate方法。
调用invalidate以后,会导致View重绘,View在重绘过程中又会调用computeScroll方法,而computeScroll又会从Scroller中获取当前的scrollX和scrollY然后通过scrollTo方法实现滚动。接着又调用invalidate方法进行第二次重绘,如此反复直到滑动到最终的位置。
override fun computeScroll() {
//注释1处,
if (scroller.computeScrollOffset()) {
//注释2处,调用scrollTo方法滚动到当前应该到达位置
scrollTo(scroller.currX, scroller.currY)
//继续调用invalidate方法请求重绘。
invalidate()
}
}
我们看下注释1处Scroller的computeScrollOffset方法
/**
* 如果你想知道新的位置,请调用这个方法。如果该方法返回true,说明动画还没结束。
*/
public boolean computeScrollOffset() {
if (mFinished) {
return false;
}
//计算流逝的时间
int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
//如果没有结束
if (timePassed < mDuration) {
switch (mMode) {//注意,我们在上面为mMode赋值为SCROLL_MODE
case SCROLL_MODE:
//插值器根据流逝的时间计算应改变的百分比x
final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
mCurrX = mStartX + Math.round(x * mDeltaX);
mCurrY = mStartY + Math.round(x * mDeltaY);
break;
//...
}
}
else {
//结束
mCurrX = mFinalX;
mCurrY = mFinalY;
mFinished = true;
}
return true;
}
在上面的方法中,如果已经到了滚动的结束时间,那么滚动结束,该方法返回false。
如果时间还没有到滚动的结束时间,步骤如下:
- 计算流逝的时间
- 插值器根据流逝的时间计算应改变的百分比x
- 计算当前应该滚动到的位置赋值给mCurrX,mCurrY。
- 返回true。
如果返回了true,则View会调用scrollTo方法滚动到当前应该到达位置,然后继续调用invalidate方法请求重绘。
if (scroller.computeScrollOffset()) {
//调用scrollTo方法滚动到当前应该到达位置currX,currY
scrollTo(scroller.currX, scroller.currY)
//继续调用invalidate方法请求重绘。
invalidate()
}
整个过程就是这样,下面来一张流程图。
Scroller实现滚动的原理.jpg
参考
- 《Android 开发艺术探索》










网友评论