Android webView 实现阻尼回弹效果

 iOS webView默认滑动到顶部或者底部的时候,还可以继续通过手指拉扯滑动,松手后回弹;而Android webView默认是不行的,要实现跟iOS一样的效果,就需要自定义webView。

ios中对可以滚动的视图都在系统层面上实现了触碰到边缘的阻尼回弹效果,用户一看便知自己的操作已经到了边界。android中也有类似的方案,不过当到达边界的时候不是用阻尼的方式,而是逐渐显示一个渐变颜色。

显然iOS的效果对用户体验来说更加的友好,Android可以通过自定义View的方式来实现类似iOS一样的阻尼回弹效果,大致的逻辑是,通过监听Touch事件,当滑动到底部或顶部的时候,继续向下/向上拖动时,通过添加偏移量重新layout控件,让控件超出原来设定的区域,达到可以继续拖扯的效果,当手指松开时,控件再次layout到原来位置,同时添加阻尼回弹效果,即可实现类似于iOS的回弹效果,代码具体如下(以webView为例,ScrollView 的做法一致):

class ScrollableWebview @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null
) : WebView(context, attrs) {
    //Y轴本次点击的位置
    private var currentY = 0

    //Y轴本次down点击的位置
    private var startY = 0

    //Y轴上次move事件点击的位置
    private var lastY = 0

    //Y轴上两次move事件之间的偏移量
    private var offset = 0

    //Y轴上两次move事件之间的偏移量*系数
    private var curOffset = 0
    private val scrollFactor = 0.35f
    private val scrollIv = 200
    private var canScroll = false
    private val rect = Rect()

    //是否正在scrolled中
    private var isScrolled = false
    override fun onOverScrolled(scrollX: Int, scrollY: Int, clampedX: Boolean, clampedY: Boolean) {
        super.onOverScrolled(scrollX, scrollY, clampedX, clampedY)
        //这个方法在webview内容滑动到顶部和底部时调用
        log("onOverScrolled scrollX $scrollX scrollY $scrollY clampedX $clampedX clampedY $clampedY")
        canScroll = clampedY
    }

    companion object {
        private const val TAG = "ScrollableWebview"
    }

    private fun log(message: String) {
        Log.d(TAG, message)
    }

    override fun onWindowFocusChanged(hasWindowFocus: Boolean) {
        super.onWindowFocusChanged(hasWindowFocus)
        Log.d(TAG, "onFinishInflate left $left top $top  right $right bottom $bottom")
        rect.set(left, top, right, bottom)

    }

    override fun onTouchEvent(ev: MotionEvent): Boolean {
        //拦截这个方法
        when (ev.action) {
            MotionEvent.ACTION_DOWN -> {
                startY = ev.y.toInt()
            }
            MotionEvent.ACTION_MOVE -> {
                if (canScroll) {
                    currentY = ev.y.toInt()
                    offset = currentY - lastY
                    curOffset = (offset * scrollFactor).toInt()
                    lastY = currentY
                    if (currentY != startY && 0 < abs(offset) && abs(offset) < scrollIv) {
                        isScrolled = true
                        layout(
                            left,
                            top + curOffset,
                            right,
                            bottom + curOffset
                        )
                    }
                }
            }
            MotionEvent.ACTION_UP -> {
                if (isScrolled) {
                    upDownMoveAnimation()
                    layout(rect.left, rect.top, rect.right, rect.bottom)
                }
            }
            else -> {}
        }

        return super.onTouchEvent(ev)
    }

    // 初始化上下回弹的动画效果
    private fun upDownMoveAnimation() {
        val animation = TranslateAnimation(
            0.0f, 0.0f,
            top.toFloat(), rect.top.toFloat()
        )
        animation.duration = 800
        animation.fillAfter = true
        //设置阻尼动画效果
        animation.interpolator = SpringInterpolation()
        this.animation = animation
    }

    class SpringInterpolation : Interpolator {
        override fun getInterpolation(input: Float): Float {
            val minus10 = -10
            val int4 = 4
            val factor=4
            return (2.0.pow((minus10 * input).toDouble()) * sin((input - factor / int4) * (2 * Math.PI) / factor) + 1).toFloat()
        }
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值