textview - 点击阴影缩放动画

2018-09-19  本文已影响327人  前行的乌龟

说在开头


这个动画效果不是我想出来的,是我看到简书- 尘少少少 朋友写的

先放个图大家看看是啥样:


效果图

作者地址:Android简单酷炫点击动画(附源码)

看着是不是挺酷的,我是第一眼就喜欢上了,不过呢,我不需要这么复杂的效果,我只想要点击时 view 大小和阴影缩小就行了

我去翻了翻作者的源码,作者提供的都是不同 view 专用的 view


Snip20180919_4.png

这和我的理念不和,我需要的是非侵入性的动画,于是我自己改一下,提供一个动画工具类,另外在再处理下连点、点击事件、4.X 兼容,基本就能作为开源代码使用了

我改的动画工具类


这个工具类首先是非侵入性的,我处理了作者没处理的 连点、点击事件问题

先来看看我的 API 使用:

        var layoutClickAnimator = LayoutClickAnimator(view_02, 300)
        view_02.setOnClickListener({
            Toast.makeText(application, "AAA", Toast.LENGTH_SHORT).show()
        })

是不是很简单,没有侵入性,不要了直接去掉动画工具类就好了,我写的基本可以拿过来直接用了,我写的也和简单,该页好改

我的效果:


ezgif.com-video-to-gif.gif

项目地址:BW_Libs

思路实现


1. 动画自身代码

这里我和原作者一样,追求一个物理效果,就是我按下不松手就不会回弹,这一下就提高了难度。直接在点击时使用一套动画然后加一个 reverse 返回的 repeatMode 就不好使了。

这里必然会使用 2 套动画,一个是 down 按下时的动画,一个是 up 回弹时的动画,还要在 4.X 时去掉 Z 轴阴影的动画。

添加一个带回弹效果的插值器实际效果会非常好,要不大伙都用呢,然后加入相应的动画执行状态标记

        var animatorSet = AnimatorSet()
        animatorSet.setDuration(time)
        animatorSet.interpolator = OvershootInterpolator(3f)

        val animatorX = ObjectAnimator.ofFloat(view, "scaleX", 1f, (1 - scaleOffset))
        val animatorY = ObjectAnimator.ofFloat(view, "scaleY", 1f, (1 - scaleOffset))

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            val animatorZ = ObjectAnimator.ofFloat(view, "translationZ", transitionZ, transitionZEnd)
            animatorSet.playTogether(animatorX, animatorY, animatorZ)
        } else {
            animatorSet.playTogether(animatorX, animatorY)
        }

        animatorSet.addListener(object : AnimatorListenerAdapter() {

            override fun onAnimationStart(animation: Animator?) {
                isDowning = true
            }

            override fun onAnimationEnd(animation: Animator?) {
                isDowning = false
            }
        })
        var animatorSet = AnimatorSet()
        animatorSet.setDuration(time)
        animatorSet.interpolator = OvershootInterpolator(3f)

        val animatorX = ObjectAnimator.ofFloat(view, "scaleX", (1 - scaleOffset), 1f)
        val animatorY = ObjectAnimator.ofFloat(view, "scaleY", (1 - scaleOffset), 1f)

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            val animatorZ = ObjectAnimator.ofFloat(view, "translationZ", transitionZEnd, transitionZ)
            animatorSet.playTogether(animatorX, animatorY, animatorZ)
        } else {
            animatorSet.playTogether(animatorX, animatorY)
        }

        animatorSet.addListener(object : AnimatorListenerAdapter() {

            override fun onAnimationStart(animation: Animator?) {
                isUping = true
            }

            override fun onAnimationEnd(animation: Animator?) {
                isUping = false
            }
        })

2. 核心触摸事件处理

最核心的就是触摸事件处理了,我们不光要处理 down 的事件,我们还要处理 up 的事件,要是传入的 view 是 viewGroup 容器类,up 的事件默认是拿不到的,没办法我又不想给每种 view 写一个专门的类所以就没法重写触摸的拦截事件拦截掉本地触摸不再向下传递,但是好在我们还可以添加 touch 的监听,在监听里直接返回 true 消费事件,这样 up 的事件也能拿到,效果也能接受不是

先看代码:

    /**
     * 给指定 view 添加触摸监听
     * 1. 这里的思路是模拟自然,我们按下时开始收缩动画,手不松开时,回弹动画是是不会指定的,所以我们需要分别处理 ACTION_DOWN 和 ACTION_UP,
     * 所以这里我们需要2个动画集合
     * 2. 我试过把动画都写在一个动画集合中,会掉帧,实际效果不理想,虽然只掉了 2 帧
     * 3. 因为考虑要适配所有的 view ,传入的 view 有可能是 layoutGroup 类型的,因为不想写专门的 view ,所以只能添加 addTouchListener ,
     * 直接消费掉事件 return true,要不 ACTION_UP 不会相应的,事件会回传给更上层 view
     * 4. 使用 2 个标记分别标记缩放动画和回弹动画,是为了处理测试让人讨厌的连续点击,因为是2个动画,一个完整的点击会触发 ACTION_DOWN 和 ACTION_UP 2个事件,
     * 所以这里单单使用按钮的防止重复点击的策略就不够了
     * 5. 多加一个逻辑维度,逻辑复杂性立马就提升很多,所以能简单还是要写的简单
     */
    private fun addTouchListener() {
        view.setOnTouchListener(object : View.OnTouchListener {
            override fun onTouch(v: View?, event: MotionEvent?): Boolean {
                val action = event?.action
                if (action == MotionEvent.ACTION_DOWN && !fastClickUtils.isFastClick() && !isDowning && !isUping) {
                    startAnimartorDown()
                }
                if ((action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) && !isUping) {
                    startAnimartorUp()
                    view.callOnClick()
                }
                return true
            }
        })
    }

3. 完整代码

最终代码也就百十来行,但是我改了好多次才算是比较稳定,没 bug 了,单反是要求高的代码就没有好些的,大家平时多写写这样的代码,遇到的问题多了,调试的 bug 多了,以后在写时自然就知道哪里可能会有问题了,这种手上的记忆可是很深刻的


        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            val animatorZ = ObjectAnimator.ofFloat(view, "translationZ", transitionZ, transitionZEnd)
            animatorSet.playTogether(animatorX, animatorY, animatorZ)
        } else {
            animatorSet.playTogether(animatorX, animatorY)
        }

        animatorSet.addListener(object : AnimatorListenerAdapter() {

            override fun onAnimationStart(animation: Animator?) {
                isDowning = true
            }

            override fun onAnimationEnd(animation: Animator?) {
                isDowning = false
            }
        })

        animatorSet.start()
    }

    /**
     * 手松开时回弹动画
     */
    private fun startAnimartorUp() {

        var animatorSet = AnimatorSet()
        animatorSet.setDuration(time)
        animatorSet.interpolator = OvershootInterpolator(3f)

        val animatorX = ObjectAnimator.ofFloat(view, "scaleX", (1 - scaleOffset), 1f)
        val animatorY = ObjectAnimator.ofFloat(view, "scaleY", (1 - scaleOffset), 1f)

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            val animatorZ = ObjectAnimator.ofFloat(view, "translationZ", transitionZEnd, transitionZ)
            animatorSet.playTogether(animatorX, animatorY, animatorZ)
        } else {
            animatorSet.playTogether(animatorX, animatorY)
        }

        animatorSet.addListener(object : AnimatorListenerAdapter() {

            override fun onAnimationStart(animation: Animator?) {
                isUping = true
            }

            override fun onAnimationEnd(animation: Animator?) {
                isUping = false
            }
        })

        animatorSet.start()
    }

}

参考资料:


上一篇 下一篇

猜你喜欢

热点阅读