preface
A lot of people have played the new micro channel shitty animation, so first imitate a micro channel bomb throwing animation, in the follow-up have time to do a complete, the effect is as follows:
The specific implementation
One of the most troublesome is to draw a parabola, the effect of the explosion just played an animation, and wechat seems to be drawn through the code, may not be an animation, but no other people that technology, can only find an animation to make do.
Second order Bessel curve
Parabola in this case is done by a second order Bezier curve, so let’s first understand what a second order Bezier curve is, and as you can see from the figure below, the second order Bezier curve has three key points, which we can call the starting point, the ending point, and the control point.
Start and end coordinates are well understood, locus of control can understand as the turning point of began to decline, and ancient great god would have to provide good mathematics formula, we only need to provide the formula with the several parameters can get x, y, of course, a parameter is the time, had the time control, we can put him in specified seconds smooth mapped.
The formula is as follows:
x = (1 - t)^2 * 0 + 2 t (1 - t) * 1 + t^2 * 1 = 2 t (1 - t) + t^2
y= (1 - t)^2 * 1 + 2 t (1 - t) * 1 + t^2 * 0 = (1 - t)^2 + 2 t (1 - t)
Copy the code
Custom second order Bezier curve calculator
When you think of animation, you might first think of the ObjectAnimator class. Yes, parabola is also done through ObjectAnimator, but we need to customize a TypeEvaluator to serve x and Y for second-order Bezier curves.
The TypeEvaluator has only one method, defined as follows:
public abstract T evaluate (float fraction,
T startValue,
T endValue)
Copy the code
Fraction means the proportion between the startValue and the endValue. StartValue and endValue are the startValue and the endValue respectively. This proportion can also be regarded as time. 1 when the animation is complete.
Therefore, the following code is obtained by inserting the second-order Bessel curve formula:
class PointFTypeEvaluator(var control: PointF) : TypeEvaluator<PointF> {
override fun evaluate(fraction: Float, startValue: PointF, endValue: PointF): PointF {
return getPointF(startValue, endValue, control, fraction)
}
private fun getPointF(start: PointF, end: PointF, control: PointF, t: Float): PointF {
val pointF = PointF()
pointF.x = (1 - t) * (1 - t) * start.x + 2 * t * (1 - t) * control.x + t * t * end.x
pointF.y = (1 - t) * (1 - t) * start.y + 2 * t * (1 - t) * control.y + t * t * end.y
return pointF
}
}
Copy the code
The animation
Then play it back using ObjectAnimator.
val animator = ObjectAnimator.ofObject(activityMainBinding.boom, "mPointF",
PointFTypeEvaluator(controlP), startP, endP)
Copy the code
Note that this View needs to have a point method. The parameter is PointF, and the method is used to set x and y.
public void setPoint(PointF pointF) {
setX(pointF.x);
setY(pointF.y);
}
Copy the code
Of course, the location of the wechat bomb is random, we also add a random.
class MainActivity : AppCompatActivity(a){
lateinit var binding: ActivityMainBinding;
private fun getRandom(max: Int, min: Int): Int {
val random = java.util.Random()
return random.nextInt(max - min + 1) + min
}
private fun getRandomPointF(a):PointF{
val outMetrics = DisplayMetrics()
val offset = 100
windowManager.defaultDisplay.getMetrics(outMetrics)
val width = outMetrics.widthPixels
val height = outMetrics.heightPixels
return PointF(getRandom(width / 2 + offset, width / 2 - offset).toFloat(a).getRandom(height / 2 + offset, height / 2 - offset).toFloat(a))
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main); binding.button.setOnClickListener { binding!! .boom.visibility = View.VISIBLE val startP = PointF() val endP = PointF() val controlP = PointF() val randomPointF = getRandomPointF() startP.x =916f
startP.y = 1353f
endP.x = randomPointF.x
endP.y = randomPointF.y
controlP.x = randomPointF.x + getRandom(200.50)
controlP.y = randomPointF.y - getRandom(200.50)
val animator = ObjectAnimator.ofObject(binding.boom, "point",
PointFTypeEvaluator(controlP), startP, endP)
animator.start()
}
}
}
Copy the code
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<com.airbnb.lottie.LottieAnimationView
android:visibility="gone"
android:id="@+id/lottie"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:lottie_fileName="boom.json"></com.airbnb.lottie.LottieAnimationView>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start"></Button>
<com.example.kotlindemo.widget.MyImageView
android:id="@+id/boom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/ic_boom"
android:visibility="gone"></com.example.kotlindemo.widget.MyImageView>
</RelativeLayout>
</layout>
Copy the code
The effect is as follows:
Explosion effect
Explosion effects are used for animation, using Lottie frame, here is the address of the explosion file download.
https://lottiefiles.com/download/public/9990-explosion
Copy the code
With the end of the coordinate point, just need to move the LottieAnimationView to the corresponding position to play, play hidden, complete code as follows:
package com.example.kotlindemo
import android.animation.Animator
import android.animation.ObjectAnimator
import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
import android.graphics.PointF
import android.media.projection.MediaProjectionManager
import android.os.Build
import android.os.Bundle
import android.util.DisplayMetrics
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import com.example.kotlindemo.databinding.ActivityMainBinding
import com.example.kotlindemo.widget.PointFTypeEvaluator
import meow.bottomnavigation.MeowBottomNavigation
import kotlin.random.Random
class MainActivity : AppCompatActivity(a){
lateinit var binding: ActivityMainBinding;
private fun getRandom(max: Int, min: Int): Int {
val random = java.util.Random()
return random.nextInt(max - min + 1) + min
}
private fun getRandomPointF(a):PointF{
val outMetrics = DisplayMetrics()
val offset = 100
windowManager.defaultDisplay.getMetrics(outMetrics)
val width = outMetrics.widthPixels
val height = outMetrics.heightPixels
return PointF(getRandom(width / 2 + offset, width / 2 - offset).toFloat(a).getRandom(height / 2 + offset, height / 2 - offset).toFloat(a))
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main); binding!! .button.setOnClickListener { binding!! .boom.visibility = View.VISIBLE val startP = PointF() val endP = PointF() val controlP = PointF() val randomPointF = getRandomPointF() startP.x =916f
startP.y = 1353f
endP.x = randomPointF.x
endP.y = randomPointF.y
controlP.x = randomPointF.x + getRandom(200.50)
controlP.y = randomPointF.y - getRandom(200.50)
val animator = ObjectAnimator.ofObject(binding.boom, "point",
PointFTypeEvaluator(controlP), startP, endP)
animator.duration = 600
animator.addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(animation: Animator) {}
override fun onAnimationEnd(animation: Animator) { val measuredHeight = binding.lottie.measuredHeight val measuredWidth = binding.lottie.measuredWidth binding.lottie.x = randomPointF.x - measuredWidth /2
binding.lottie.y = randomPointF.y - measuredHeight / 2
binding.lottie.visibility = View.VISIBLE
binding.boom.visibility = View.GONE
binding.lottie.playAnimation()
binding.lottie.addAnimatorListener(object : Animator.AnimatorListener {
override fun onAnimationStart(animation: Animator) {}
override fun onAnimationEnd(animation: Animator) {
binding.lottie.visibility = View.GONE
}
override fun onAnimationCancel(animation: Animator) {}
override fun onAnimationRepeat(animation: Animator) {}})}override fun onAnimationCancel(animation: Animator) {}
override fun onAnimationRepeat(animation: Animator) {}
})
animator.start()
}
}
}
Copy the code