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