前言
这篇文章是一个小球无限弹跳的动画效果,不说废话直接上效果吧
效果
效果图代码
UI:R.layout.fragment_anim4
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btn_anim_0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="start"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_anim_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="reset"
app:layout_constraintStart_toEndOf="@id/btn_anim_0"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_messages"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
app:layout_constraintTop_toBottomOf="@id/btn_anim_0" />
<View
android:id="@+id/view_anim"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginLeft="20dp"
android:background="#1F6FE8"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:layout_width="match_parent"
android:layout_height="2px"
android:layout_marginTop="600px"
android:background="#222222"
app:layout_constraintTop_toBottomOf="@id/view_anim" />
</androidx.constraintlayout.widget.ConstraintLayout>
Fragment:AnimFragment4
如果你的demo是Activity,将layout绑定布局并吧initView方法放在onCreate中就好了
package com.qi.fragment.home
import android.animation.Animator
import android.animation.TypeEvaluator
import android.animation.ValueAnimator
import android.graphics.Color
import android.graphics.Outline
import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan
import android.util.Log
import android.view.View
import android.view.ViewOutlineProvider
import android.view.animation.LinearInterpolator
import androidx.core.animation.doOnEnd
import androidx.core.animation.doOnStart
import com.qi.R
import com.qi.fragment.BaseFragment
import com.qi.util.Util
import com.qi.util.Util.location
import kotlinx.android.synthetic.main.fragment_anim4.*
/**
* 估值器 - TypeEvaluator 模运动的小球
* 第二阶段 - 通过组合动画实现小球的无限弹跳
*/
class AnimFragment4 : BaseFragment() {
private var speedX = 250f
private var speedY = -500f
private var high = 600.0
private var curAnim: Animator? = null
companion object {
const val a = 1000
const val decay = 0.8
}
override fun getLayoutId() = R.layout.fragment_anim4
override fun initViews() {
// 裁剪为圆球形
view_anim?.outlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View?, outline: Outline?) {
outline?.setOval(0, 0, view_anim?.width ?: 0, view_anim?.height ?: 0);
}
}
view_anim?.clipToOutline = true
// 无限弹起下落过程
btn_anim_0.setOnClickListener {
getAnim().apply { curAnim = this }.start()
}
// reset
btn_anim_1.setOnClickListener {
curAnim?.cancel()
curAnim?.cancel()
curAnim?.removeAllListeners()
speedX = 250f
speedY = -500f
high = 600.0
view_anim.translationY = 0f
view_anim.translationX = 0f
view_anim.top = 955
view_anim.bottom = 990
view_anim.left = 70
view_anim.right = 105
showMessages()
}
}
private fun getAnim(): ValueAnimator {
val t = Util.getAns(0.5 * a, speedY.toDouble(), -high).toFloat()
return ValueAnimator.ofObject(
TypeEvaluator<Status> { fraction, _, _ ->
val curT = fraction * t
Status(speedX * curT, speedY * curT + 0.5f * a * curT * curT)
},
Status(),
Status()
).apply {
interpolator = LinearInterpolator() // 时间匀速变化
duration = (t * 1000).toLong()
addUpdateListener {
(it.animatedValue as Status).apply {
view_anim.translationX = translationX
view_anim.translationY = translationY
showMessages()
}
}
doOnStart {
Log.d(TAG, view_anim.location)
}
doOnEnd {
speedX *= decay.toFloat()
speedY = -decay.toFloat() * (speedY + a * t)
high = 0.0
}
doOnEnd {
view_anim.top += (animatedValue as Status).translationY.toInt()
view_anim.bottom += (animatedValue as Status).translationY.toInt()
view_anim.left += (animatedValue as Status).translationX.toInt()
view_anim.right += (animatedValue as Status).translationX.toInt()
// 跳出屏幕或跳跃太小就停下来,防止无限循环
if (view_anim.right < Util.width && (animatedValue as Status).translationX > 1) {
getAnim().apply { curAnim = this }.start()
}
}
}
}
private fun showMessages() {
val sb = SpannableStringBuilder()
.append("left:", "#FF0000")
.append(view_anim.left.toString()).append("\n")
.append("right:", "#FF0000")
.append(view_anim.right.toString()).append("\n")
.append("top:", "#FF0000")
.append(view_anim.top.toString()).append("\n")
.append("bottom:", "#FF0000")
.append(view_anim.bottom.toString()).append("\n")
.append("TranslationX:", "#FF0000")
.append(view_anim.translationX.toString()).append("\n")
.append("TranslationY:", "#FF0000")
.append(view_anim.translationY.toString()).append("\n")
.append("SpeedX:", "#FF0000")
.append(speedX.toString()).append("\n")
.append("speedY:", "#FF0000")
.append(speedY.toString()).append("\n")
tv_messages.text = sb
}
data class Status(
val translationX: Float = 0f, // x方向位移
val translationY: Float = 0f, // y方向位移
val rotation: Float = 0f, // 旋转角度:单位 °
val alpha: Float = 1f, // 透明度
val scaleX: Float = 1f, // 横向缩放
val scaleY: Float = 1f // 纵向缩放
)
private fun SpannableStringBuilder.append(
text: CharSequence,
color: String
): SpannableStringBuilder {
this.append(text)
this.setSpan(
ForegroundColorSpan(Color.parseColor(color)),
this.length - text.length,
this.length,
0
)
return this
}
}
Util.kt
如果你没有AppContext,自己写个方法获取applicationContext也是一样的,这里就不展示了
package com.qi.util
import android.content.Context
import android.view.View
import android.view.WindowManager
import com.qi.application.AppContext
import kotlin.math.pow
object Util {
// 一元二次方程求解
fun getAns(a: Double, b: Double, c: Double): Double {
val delta = b * b - 4 * a * c
return (-b + delta.pow(0.5)) / (2 * a)
}
val View.location
get() = "top:$top, bottom:$bottom, left:$left, right:$right,translationX:$translationX, translationY:$translationY"
val windowManager by lazy {
AppContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager
}
val width by lazy {
windowManager.defaultDisplay.width
}
val height by lazy {
windowManager.defaultDisplay.height
}
}
总结
感觉这个估值器还是有一定的缺点的,对于这些带衰减的动画效果,没有安置一个时间来控制,所以我写起来自己实现了时间,就显得很复杂了。但好歹还是有些收获,虽然以后可能用不太到- -
网友评论