目的:由于公司项目需求,现需制作一个空调的键位控制的弹窗。
功能:能够自由增删键位的个数,并对键位显示的时候播放多种动画。
image image.gif
首先我们来复习下初中的数学知识:平面直角坐标系和求坐标系中某个点的坐标值。
image
image.gif
由于是手工画的,大家将就这看吧。a代表直角三角形的一个锐角,x是横轴坐标,y是纵轴坐标,那么p点的坐标就是(x,y)了。
Tip:其实在android界面中y轴不是朝上的而是朝下的,这个我们等下在讨论。
利用初中知识我们知道:x=rsin(a) y=rcos(a) ,那么p=(rsin(a),rcos(a))
然后我们把数学函数转换为java代码 :
x=r*Math.sin(Math.toRadians(a))
y=r*Math.cos(Math.toRadians(a))
p=(x,y)
image image.gif
现在有n个按键要均匀分布在圆形上,并且以点A为起点,点A的坐标就是(0,-y)了,我们现在需要算出点BCDE的坐标。
我们先算点B,算点B我们必须先知道圆的半径和角b,假设半径为r。
由于这些点是均匀分布的,所以角b=360/n。
有前面的知识可知 B=(rMath.sin(Math.toRadians(360/n)),rMath.cos(Math.toRadians(360/n))),但是由于点B处于第四象限,所以y坐标应该是负值,所以B=(rMath.sin(Math.toRadians(360/n)), -rMath.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来实现效果。
image image.gif
通过上面的链接大家可以生成对应的函数。然后我们还需要重写个插值器类。
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()}")
}
})
}
最后效果
image image.gif
网友评论