具有多种动画的菜单弹窗

目的:由于公司项目需求,现需制作一个空调的键位控制的弹窗。

功能:能够自由增删键位的个数,并对键位显示的时候播放多种动画。



        首先我们来复习下初中的数学知识:平面直角坐标系和求坐标系中某个点的坐标值。

   

由于是手工画的,大家将就这看吧。a代表直角三角形的一个锐角,x是横轴坐标,y是纵轴坐标,那么p点的坐标就是(x,y)了。

Tip:其实在android界面中y轴不是朝上的而是朝下的,这个我们等下在讨论。

利用初中知识我们知道:x=r*sin(a)  y=r*cos(a) ,那么p=(r*sin(a),r*cos(a))

然后我们把数学函数转换为java代码 :

x=r*Math.sin(Math.toRadians(a))

y=r*Math.cos(Math.toRadians(a))

p=(x,y)



现在有n个按键要均匀分布在圆形上,并且以点A为起点,点A的坐标就是(0,-y)了,我们现在需要算出点BCDE的坐标。

我们先算点B,算点B我们必须先知道圆的半径和角b,假设半径为r。

由于这些点是均匀分布的,所以角b=360/n。

有前面的知识可知 B=(r*Math.sin(Math.toRadians(360/n)),r*Math.cos(Math.toRadians(360/n))),但是由于点B处于第四象限,所以y坐标应该是负值,所以B=(r*Math.sin(Math.toRadians(360/n)), -r*Math.cos(Math.toRadians(360/n)))。

现在我们继续看点C的坐标,需先求角c,c=2b-90=2b%90,不知道大家能否理解。

以此类推 d=3b%90 ,e=4d%90...,这样我们就能算出剩余的点坐标了。

现在我们已经有了各个点的坐标那么只需将各个点从原点(0,0)移动到各自的位置上即可。下面我就用代码的方式把前面的结论用代码的方式表达出来。

package com.mmy.kotlinsample.popup

import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.app.Activity
import android.content.Context
import android.graphics.Point
import android.graphics.drawable.ColorDrawable
import android.util.Log
import android.view.Gravity
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.PopupWindow
import android.widget.RelativeLayout
import com.mmy.menupopup.R


/**
 * @file       ControlPopup.kt
 * @brief      描述
 * @author     lucas
 * @date       2018/5/8 0008
 * @version    V1.0
 * @par        Copyright (c):
 * @par History:
 *             version: zsr, 2017-09-23
 */
class ControlPopup constructor(val context: Context, val array: IntArray) : PopupWindow() {
    val r = 400.0//半径
    val pints = ArrayList<Point>()//用户存储每个点的坐标
    var degrees: Double = 0.0
    val location = kotlin.IntArray(2)
    var mRootView: RelativeLayout? = null
    val set = AnimatorSet()

    init {
        width = ViewGroup.LayoutParams.MATCH_PARENT
        height = ViewGroup.LayoutParams.MATCH_PARENT
        setBackgroundDrawable(ColorDrawable(context.resources.getColor(android.R.color.transparent)))
        isOutsideTouchable = true

        //第一个按键固定在正下方
        val element = Point(0, r.toInt())
        element.direction(true, false)
        pints.add(element)
        //算出每个角的度数
        degrees = 360 / array.size.toDouble()
        //算出剩下的图标的坐标
        for (i in 1 until array.size) {
            //夹角度数
            val d = degrees * i
            var point: Point? = null
            //判断是在那个象限,确定方向
            val radians = Math.toRadians(d % 90)
            when (d.toInt()) {
                in 0..90 -> {//第四象限 x,-y
//                    Log.d("popup","90:${d % 90},x:${Math.sin(d % 90) * r},y:${Math.cos(d % 90) * r}")
                    point = Point((Math.sin(radians) * r).toInt(), (Math.cos(radians) * r).toInt())
                    point.direction(true, false)
                }
                in 90..180 -> {//第一象限 x,y
                    point = Point((Math.cos(radians) * r).toInt(), (Math.sin(radians) * r).toInt())
                    point.direction(true, true)
                }
                in 180..270 -> {//第二象限-x,y
                    point = Point((Math.sin(radians) * r).toInt(), (Math.cos(radians) * r).toInt())
                    point.direction(false, true)
                }
                else -> {//第三象限-x,-y
                    point = Point((Math.cos(radians) * r).toInt(), (Math.sin(radians) * r).toInt())
                    point.direction(false, false)
                }
            }
            if (point != null)
                pints.add(point)
        }

        mRootView = LayoutInflater.from(context).inflate(R.layout.popup_control, null, false) as RelativeLayout
        contentView = mRootView
        mRootView?.setOnClickListener { dismiss() }
        //添加按键
        array.forEach {
            val imageView = ImageView(context)
            imageView.setImageResource(it)
            val params = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
            params.addRule(RelativeLayout.CENTER_IN_PARENT)
            imageView.layoutParams = params
            mRootView?.addView(imageView)
        }
        //开始播放动画
        openAnim()
    }

    private fun closeAnim() {
        for (i in 0 until pints.size) {
            pints[i].tostring()
            val childAt = mRootView?.getChildAt(i)
            //x轴平移
            val animatorX = ObjectAnimator.ofFloat(childAt, "translationX", 0.0f, 0.0f)
            //y轴平移
            val animatorY = ObjectAnimator.ofFloat(childAt, "translationY", 0.0f, 0.0f)
            set.play(animatorX).with(animatorY)
            set.duration = 1000
            set.start()
        }
    }

    private fun openAnim() {
        for (i in 0 until pints.size) {
            pints[i].tostring()
            val childAt = mRootView?.getChildAt(i)
            //x轴平移
            val animatorX = ObjectAnimator.ofFloat(childAt, "translationX", 0.0f, pints[i].x.toFloat())
            //y轴平移
            val animatorY = ObjectAnimator.ofFloat(childAt, "translationY", 0.0f, pints[i].y.toFloat())
            set.play(animatorX).with(animatorY)
            set.duration = 1000
            set.start()
        }
    }

    //显示弹窗
    fun show(activity: Activity){
        showAtLocation(activity.findViewById(android.R.id.content),Gravity.CENTER,0,0)
        openAnim()
    }

    //关闭弹窗
    fun close(){
        closeAnim()
        dismiss()
    }


    /**
     * pint扩展个方向的方法
     * Tip:y的值为正数就是在上方,反之在下方
     *      x值为正数就在右侧,反之左侧
     */
    fun Point.direction(xDirection: Boolean, yDirection: Boolean) {
        if (!xDirection)
            this.x = 0 - this.x
        if (yDirection)
            this.y = 0 - this.y
    }

    fun Point.tostring() {
        Log.d("popup", "x:${this.x},y:${this.y}")
    }
}

以上代码只是完成了按键的添加和按键的平移动画,看似比较呆板,现在我们来给动画加点灵魂。

首先我们了解下弹性动画,通过插值器interpolator来实现效果。

Interpolator


通过上面的链接大家可以生成对应的函数。然后我们还需要重写个插值器类。

package com.mmy.menupopup

import android.view.animation.Interpolator

/**
 * @file       SpringScaleInterpolator.kt
 * @brief      描述
 * @author     lucas
 * @date       2018/5/10 0010
 * @version    V1.0
 * @par        Copyright (c):
 * @par History:
 *             version: zsr, 2017-09-23
 */
class SpringScaleInterpolator(val factor:Float) :Interpolator{
    override fun getInterpolation(input: Float): Float {
        return (Math.pow(2.0, (-10 * input).toDouble()) * Math.sin((input - factor / 4) * (2 * Math.PI) / factor) + 1).toFloat()
    }
}

通过参数factor来调整动画的弹性效果。并把其添加至动画中。

private fun openAnim() {
    for (i in 0 until pints.size) {
        pints[i].tostring()
        val childAt = mRootView?.getChildAt(i)
        //x轴平移
        val animatorX = ObjectAnimator.ofFloat(childAt, "translationX", 0.0f, pints[i].x.toFloat())
        //y轴平移
        val animatorY = ObjectAnimator.ofFloat(childAt, "translationY", 0.0f, pints[i].y.toFloat())
        set.play(animatorX).with(animatorY)
        set.duration = 1000
        set.interpolator = SpringScaleInterpolator(0.4f)
        set.start()
    }
}

这样我们的动画就具有部分灵魂了。

我们还可以给每个按键在显示的时候添加一定的延时,这样按键的显示会具有一定的先后顺序,这样看起来也比较舒服。

private fun openAnim() {
    for (i in 0 until pints.size) {
        pints[i].tostring()
        mHandler.postDelayed({
            val childAt = mRootView?.getChildAt(i)
            //x轴平移
            val animatorX = ObjectAnimator.ofFloat(childAt, "translationX", 0.0f, pints[i].x.toFloat())
            //y轴平移
            val animatorY = ObjectAnimator.ofFloat(childAt, "translationY", 0.0f, pints[i].y.toFloat())
            val set = AnimatorSet()
            set.play(animatorX).with(animatorY)
            set.duration = duration
            set.interpolator = SpringScaleInterpolator(0.4f)
            set.start()
        }, i * 50L)
    }
}

下面我们继续给中间的图片添加一个类似5.0的转场动画效果。

private fun openAnim() {
    //将目标控件自动到原点
    val midView = mRootView?.findViewById<View>(R.id.v_mid_icon)
    val targetLocation = kotlin.IntArray(2)
    targetView.getLocationOnScreen(targetLocation)
    midView?.viewTreeObserver?.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                midView.viewTreeObserver?.removeOnGlobalLayoutListener(this)
            }
            val midLocation = kotlin.IntArray(2)
            midView.getLocationOnScreen(midLocation)
            //x轴平移
            val animatorX = ObjectAnimator.ofFloat(midView, "translationX", (targetLocation[0].toFloat()-midLocation[0].toFloat()), .0f)
            //y轴平移
            val animatorY = ObjectAnimator.ofFloat(midView, "translationY", (targetLocation[1].toFloat()-midLocation[1].toFloat()), .0f)
            //缩放动画
            val scaleAnimatorX = ObjectAnimator.ofFloat(midView, "scaleX", 1.0f, 1.5f)
            val scaleAnimatorY = ObjectAnimator.ofFloat(midView, "scaleY", 1.0f, 1.5f)
            val set = AnimatorSet()
            set.play(animatorX).with(animatorY).with(scaleAnimatorX).with(scaleAnimatorY)
            set.duration = targetDuration
            set.addListener(object :Animator.AnimatorListener{
                override fun onAnimationRepeat(p0: Animator?) {
                }

                override fun onAnimationEnd(p0: Animator?) {
                    //当目标图片移动结束后开始释放其他按键
                    for (i in 0 until pints.size) {
                        pints[i].tostring()
                        mHandler.postDelayed({
                            val childAt = mContainer?.getChildAt(i)
                            childAt?.visibility=View.VISIBLE
                            //x轴平移
                            val animatorX = ObjectAnimator.ofFloat(childAt, "translationX", 0.0f, pints[i].x.toFloat())
                            //y轴平移
                            val animatorY = ObjectAnimator.ofFloat(childAt, "translationY", 0.0f, pints[i].y.toFloat())
                            val set = AnimatorSet()
                            set.play(animatorX).with(animatorY)
                            set.duration = duration
                            set.interpolator = SpringScaleInterpolator(0.4f)
                            set.start()
                        }, i * delay)
                    }
                }

                override fun onAnimationCancel(p0: Animator?) {
                }

                override fun onAnimationStart(p0: Animator?) {
                }

            })
            set.start()
            Log.d("lucas", "xxx:${midLocation[0].toFloat()},yyy:${midLocation[1].toFloat()}")
        }
    })

}

最后效果


Github地址

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值