Android uses COS and sin to draw compound curve animation

Time:2022-5-7
catalogue
  • preface
  • First analysis
  • Second analysis
  • summary

preface

During the development of new requirements in the first two weeks, an animation similar to this was designed:

It’s not difficult to see. Even if I can’t understand it again, hey, there’s no design draft.

As an Android Developer who seldom writes animation, when I see the design draft of slow in and slow out curves, my mood is as follows:

Although the default interpolator for Android animation, acceleratedeelerateinterpolator, has such a slow in and slow out effect:

I can’t break a whole animation into four animation to write. Let alone, the code I wrote for the first time really did so.

First analysis

Based on the principle of writing one line less and never one word more, the boss asked his colleagues for their opinions. The boss waved his hand: path interpolator (later proved to be a problem).

After a brief look at the use method, you need to use path. After another look, good guy, you may use Bezier curve. Give up~

In order to solve the problem quickly, the above-mentioned scheme is used:

private fun animateTagView(tagView: TextView) {
 //[0200] interval animation 
 val valueAnimatorOne = ValueAnimator.ofInt(0, 200)
 valueAnimatorOne.addUpdateListener {
  val per = it.animatedValue as Int / 200f
  tagView.rotation = 4 * per
  tagView.scaleX = (1 - 0.1 * per).toFloat()
  tagView.scaleY = (1 - 0.1 * per).toFloat()
 }
 valueAnimatorOne.duration = 200
 //[200560] interval animation
 val valueAnimatorTwo = ValueAnimator.ofInt(200, 560)
 valueAnimatorTwo.addUpdateListener {
  val per = (it.animatedValue as Int - 200) / 360f
  tagView.rotation = 3 - 11 * per
  tagView.scaleX = (0.9 + 0.1 * per).toFloat()
  tagView.scaleY = (0.9 + 0.1 * per).toFloat()
 }
 valueAnimatorTwo.duration = 360
 //[560840] interval animation
 val valueAnimatorThree = ValueAnimator.ofInt(560, 840)
 valueAnimatorThree.addUpdateListener {
  val per = (it.animatedValue as Int - 560) / 280f
  tagView.rotation = -8 + 12 * per
  tagView.scaleX = (1 - 0.2 * per).toFloat()
  tagView.scaleY = (1 - 0.2 * per).toFloat()
 }
 valueAnimatorThree.duration = 280
 //Animation of [8401000]
 val valueAnimatorFour = ValueAnimator.ofInt(840, 1000)
 valueAnimatorFour.addUpdateListener {
  val per = (it.animatedValue as Int - 840) / 160f
  tagView.rotation = 4 - 4 * per
  tagView.scaleX = (0.8 + 0.2 * per).toFloat()
  tagView.scaleY = (0.8 + 0.2 * per).toFloat()
 }
 valueAnimatorFour.duration = 160
 //Serial animation using animatorset
 val animationSet = AnimatorSet()
 animationSet.playSequentially(valueAnimatorOne, valueAnimatorTwo, valueAnimatorThree, valueAnimatorFour)
 tagView.post {
  tagView.pivotX = 0f
  tagView.pivotY = ad_tag_two.measuredHeight.toFloat()
  animationSet.start()
 }
}

The whole animation is divided into four attribute animations [0200], [200560], [560840] and [8401000]. Because the product says that it only needs to be played once, the problem can be solved by assembling the animation with animatorset.

Second analysis

Although the solution obtained for the first time can solve the problem, animatorset will not work if circular playback is encountered. Is there any other solution?

Taking advantage of the weekend, I learned pathinterpolator and found that it can’t solve the problem, or it’s not easy to solve the problem. Although the above curve can be drawn in segments with third-order Bezier curve, pathinterpolator requires the starting point and ending point to be (0,0) and (1,1) respectively.

Since the interpolator doesn’t work, you can try the estimator, but one estimator can’t solve the two animations of rotation and scaling. It seems that animatorupdatelistener can solve the problem.

In retrospect, the interpolator converts the uniform time segment into the behavior of acceleration or deceleration. We can also convert the uniform time segment into the corresponding curve as long as we do two things:

Use the linear interpolator.
Split the above curve and express it through different sin or cos methods.
Taking rotation animation as an example, the split sin function:

Another animation function can refer to the code:

override fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)
 setContentView(R.layout.activity_main)

 val tvContent = findViewById<TextView>(R.id.tv_content)
 val valueAnimatorOne = ValueAnimator.ofFloat(0.0f, 1.5f)
 valueAnimatorOne.addUpdateListener {
  //Set rotation and scale through the corresponding sin and COS
  val per = it.animatedValue as Float
  var rotation: Float = 0f
  var scale: Float = 0f
  if(per >= 0 && per < 0.2f){
   rotation = sin((per / 0.2f) * Math.PI.toFloat() - Math.PI.toFloat() / 2) * 1.5f + 1.5f
   scale = cos(per / 0.2f * Math.PI.toFloat()) * 0.05f + 0.95f
  }
  if(per >= 0.2f && per < 0.56f){
   rotation = sin(Math.PI.toFloat() / 2 + Math.PI.toFloat() * ( per - 0.2f) / 0.36f) * 5.5f - 2.5f
   scale = cos((per - 0.2f) / 0.36f * Math.PI.toFloat() + Math.PI.toFloat()) * 0.05f + 0.95f
  }
  if(per >= 0.56f && per < 0.84f){
   rotation = sin(Math.PI.toFloat() * (per - 0.56f) / 0.28f - Math.PI.toFloat() / 2) * 6f - 2f
   scale = cos((per - 0.56f) / 0.28f * Math.PI.toFloat()) * 0.1f + 0.9f
  }
  if(per in 0.84f..1f){
   rotation = sin(Math.PI.toFloat() / 2 + Math.PI.toFloat() * (per - 0.84f) / 0.16f ) * 2f + 2f
   scale = cos((per - 0.84f) / 0.16f * Math.PI.toFloat() + Math.PI.toFloat()) * 0.1f + 0.9f
  }
  //Set stop time
  if(per > 1f && per <= 1.5f){
   rotation = 0f
   scale = 1.0f
  }
  tvContent.rotation = rotation
  tvContent.scaleX = scale
  tvContent.scaleY = scale
 }
 //Set linear interpolator
 valueAnimatorOne.interpolator = LinearInterpolator()
 //Animation time
 valueAnimatorOne.duration = 1500
 //Wireless loop
 valueAnimatorOne.repeatCount = -1
 tvContent.post {
  //Set center point
  tvContent.pivotX = 0f
  tvContent.pivotY = tvContent.measuredHeight.toFloat()
  valueAnimatorOne.start()
 }
}

The whole code is relatively simple. The rotation animation curve is obtained by sin, and the scaling is obtained by cos. Finally, change the center point.

summary

This animation case is not difficult. In the case of compound slow in and slow out curve, we can split it into sections and describe it with sin or cos. The advantage is that only one attribute animation can be used and can be played circularly.

If you have a better plan, please communicate in the comment area.

The above is the details of Android using COS and sin to draw composite curve animation. For more information about Android drawing composite curve animation, please pay attention to other relevant articles of developeppaer!