实现惯性滚动核心步骤就三步:
1.算出手指抬起时页面滚动的速度
2.根据这个速度算接下来每个时间段内应该滚动的距离
3.滚动这个距离
首先我们来算速度,速度怎么算?请回忆以前学物理的时候的打点计时器。我们需要借助一个工具类VelocityTracker,这个工具就很像打点计时器。我们在每次出发触摸事件的时候,调用这个类的addMovement(event)方法,打个点,当我们想要计算速度时,调用获取速度的方法,它能根据这些打点帮我们算出我们想要的速度
然后我们来算手指抬起后每个时间段滚动的距离,这个距离同样不需要我们自己手动算,我们借助工具Scroller类,将算得的速度传给它,然后在手指抬起时调用Scroller类的fling()方法,通知它开始计算距离的工作,然后在我们想要获取滚动距离时,调用一下它的getCurrX()方法或者getCurrY()方法,获取到的值就是我们需要的值
第三步,滚动到这个距离,直接scrollTo()到那个位置上即可
完整代码
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.ViewConfiguration
import android.widget.LinearLayout
import android.widget.Scroller
class HpScrollAndFlingView(context: Context, attrs: AttributeSet): LinearLayout(context, attrs) {
// 上一次触摸事件的y坐标
private var lastY = 0f
// 用来算速度的工具(通过在每次的触摸事件中打点)
private lateinit var mVelocityTracker: VelocityTracker
// 用来根据传入的速度算当前应该滚动到的位置的工具
private val scroller by lazy { Scroller(context) }
override fun onTouchEvent(event: MotionEvent): Boolean {
if (!super.onTouchEvent(event)) {
if (!this::mVelocityTracker.isInitialized) mVelocityTracker = VelocityTracker.obtain()
var isActionUp = false
// 拷贝一份触摸事件,我猜是为了防止污染原事件
val eventCopy = MotionEvent.obtain(event)
val eventY = event.y
when(event.actionMasked) {
MotionEvent.ACTION_DOWN -> lastY = eventY
MotionEvent.ACTION_MOVE -> {
val dy = lastY - eventY
scrollTo(0, (scrollY + dy).toInt())
lastY = eventY
}
MotionEvent.ACTION_UP -> {
// 最后一次打点
mVelocityTracker.addMovement(eventCopy)
isActionUp = true
// 设定一个最大速度,速度太快体验也不好
val maxV = ViewConfiguration.get(context).scaledMaximumFlingVelocity.toFloat()
// 这里的 1000 是你想要的速度单位。值1提供像素/毫秒,1000提供像素/秒
mVelocityTracker.computeCurrentVelocity(1000, maxV)
val yVelocity = -mVelocityTracker.getYVelocity(event.getPointerId(0))
startFling(yVelocity.toInt())
mVelocityTracker.clear()
}
else -> {}
}
if (!isActionUp) {
// 每次触摸事件打点
mVelocityTracker.addMovement(eventCopy)
}
eventCopy.recycle()
return true
}
return false
}
private val refreshRunnable = Runnable {
if (scroller.computeScrollOffset()) {
scrollTo(0, scroller.currY)
postOnAnimationFun()
}
}
private fun postOnAnimationFun() {
// 使Runnable在下一个动画时间步长上执行
postOnAnimation (refreshRunnable)
}
private fun startFling(velocity: Int) {
// 通知scroller开始计算应该活动到的位置
scroller.fling(0, scrollY, 0, velocity, Int.MIN_VALUE, Int.MAX_VALUE, Int.MIN_VALUE, Int.MAX_VALUE)
postOnAnimationFun()
}
override fun scrollTo(x: Int, y: Int) {
val realHeight = getRealHeight()
val newY = if (y < 0) 0
else if (y > realHeight) realHeight
else y
super.scrollTo(x, newY)
}
private fun getRealHeight(): Int {
var height = 0
for(index in 0 until childCount) {
height += getChildAt(index).height
}
return height - this.height
}
}
布局就是在这个类里面填充很多View(放多一些效果好),让它超出屏幕长度,就能滚起来
代码没有考虑很多边缘情况,主要是为了体现惯性滚动的主要实现,对意外情况的考虑需要自行实现
版权声明:本文为qq_45383789原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。