安卓实现View的惯性滚动效果(Fling)

实现惯性滚动核心步骤就三步:
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(放多一些效果好),让它超出屏幕长度,就能滚起来
代码没有考虑很多边缘情况,主要是为了体现惯性滚动的主要实现,对意外情况的考虑需要自行实现

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是RecyclerView实现循环滚动的代码,我会对其中的关键部分进行注释: ```java public class LoopRecyclerView extends RecyclerView { private static final int MAX_SCROLL_ON_FLING_DURATION = 1000; // 最大滑动时间 private static final int FLING_THRESHOLD = 2500; // 滑动速度阈值 private LoopLayoutManager mLoopLayoutManager; public LoopRecyclerView(Context context) { super(context); init(); } public LoopRecyclerView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public LoopRecyclerView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void init() { mLoopLayoutManager = new LoopLayoutManager(getContext()); setLayoutManager(mLoopLayoutManager); } @Override public void setLayoutManager(LayoutManager layout) { if (!(layout instanceof LoopLayoutManager)) { throw new IllegalArgumentException("LoopRecyclerView must use LoopLayoutManager"); } super.setLayoutManager(layout); } @Override public boolean fling(int velocityX, int velocityY) { // 根据滑动速度判断是否需要循环滚动 boolean canLoop = Math.abs(velocityX) > FLING_THRESHOLD; int scrollDuration = canLoop ? MAX_SCROLL_ON_FLING_DURATION : super.computeHorizontalScrollDuration(velocityX); mLoopLayoutManager.setScrollDuration(scrollDuration); return super.fling(velocityX, velocityY); } } ``` 上面的代码中,我们自定义了一个`LoopRecyclerView`,并在其中使用了`LoopLayoutManager`,这个布局管理器可以实现循环滚动。在`init()`方法中,我们将`LoopLayoutManager`设置为RecyclerView的布局管理器。在`setLayoutManager()`中,我们判断传入的布局管理器是否为`LoopLayoutManager`,如果不是,则抛出异常。 最关键的部分在`fling()`方法中,我们根据滑动速度判断是否需要循环滚动。如果滑动速度大于阈值`FLING_THRESHOLD`,则认为需要循环滚动,将滑动时间设置为`MAX_SCROLL_ON_FLING_DURATION`;否则,调用`super.computeHorizontalScrollDuration(velocityX)`计算滑动时间。最后,调用`super.fling(velocityX, velocityY)`实现滚动
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值