Blog Infos
Author
Published
Topics
Published
Topics

In this Article we are going to fly a plane or the screen

Path()

Path is an android class it is very useful or many time only choice in android for creating arbitrary shapes and drawings arbitrary figures , Path is also used in animations. But often times working with paths is very painful. If you don’t want to bear that pain anymore bear with me for rest of the article.

So we will fly a plane on a random path using path animations.

Taking a Random Path in Life

I am going to generate a random path on screen, our plane will fly on that path only.

private fun initPlanePath() {
planePath.reset()
var startX = randomX()
var startY = randomY()
repeat(3) {
planePath.moveTo(startX, startY)
val controlX = randomX()
val controlY = randomY()
val endX = randomX()
val endY = randomY()
planePath.quadTo(controlX, controlY, endX, endY)
startX = endX
startY = endY
}
}
//generate a random number between 0 and view width
private fun randomX() = random.nextInt(0, width).toFloat()
//generate a random number between 0 and view height
private fun randomY() = random.nextInt(0, height).toFloat()
view raw planepath.kt hosted with ❤ by GitHub

result of drawing random path for a random run of app

 

here the key method is quadTo() which is used to generate a quadratic bezier curve , I am creating multiple bezier curves which are connected to each other the starting point of each path (bezier curve) is called a contour in paths language, there is also a cubicTo() which is used to create a cubic bezier curve which has 2 control points.

I encourage you to learn more about bezier curve so that you understand what are control points and how above code works under the hood.

 

let’s Fly a plane

We want to fly a plane along a path to be able to do that we need to know about x and y co-ordinates of points which forms this path so that we can keep setting x and y or our plane on those co-ordinates to produce an animation to know about those co-ordinates we need to understand a really important class.

PathMeasure()

PathMeasure is best friend class of a Path class , a path is kind of shy it won’t directly tell you about its personal things like it’s length , tangent of a line at any point on it’s body , length of a segment between two points , whether or not it is closed etc.

To handle paths you’d like as much information as you can so you may want PathMeasure as common friend , I’ll familiarize you to it along the way.

getPosTan

getPosTan(float distance, float[] pos, float[] tan) this method is used to get co-ordinates of point on a path a given distance.

It takes three arguments

distance we will get the co-ordinates at this distance

pos the x and y co-ordinates are written in this float array

tan the tangent in radian is written in this array

nextContour

Our path is made of several quadTo() commands getposTan() or other methods of PathMeasure do not operate of entire path at once rather they operate on one command at a time in our case you can say a contour is one path which is created by one quadTo() call for moving to the next we call nextContour() this method returns true if there is a next path false if we are done with entire path.

So if we continuously keep getting these co-ordinates for subsequent distances on the path we can put an object at those co-ordinates and it will appear moving , that is what we are going to do now

private fun flyPlane() {
var distance = 0f
val tan = floatArrayOf(0f,0f)
val pos = floatArrayOf(0f, 0f)
val valueAnimator = ValueAnimator.ofFloat(0f, 1f)
valueAnimator.duration = (pathMeasure.length * 5).toLong()
valueAnimator.interpolator = AccelerateDecelerateInterpolator()
valueAnimator.addUpdateListener {
distance = it.animatedValue as Float
pathMeasure.getPosTan(distance * pathMeasure.length, pos, tan)
}
valueAnimator.doOnEnd {
if (pathMeasure.nextContour()) {
flyPlane()
}
}
valueAnimator.start()
}
view raw flyplane.kt hosted with ❤ by GitHub

explanation of above animation

 

here we are animating from 0f to 1fit will give us co-ordinates at different lengths which is tried to explain in image above after getting a co-ordinate we set the x and y of our plane at those co-ordinates when we are done with a contour on a curve we jump to the next one using nextContour() until the entire path is done.

private fun flyPlane() {
var distance = 0f
val tan = floatArrayOf(0f,0f)
val pos = floatArrayOf(0f, 0f)
val valueAnimator = ValueAnimator.ofFloat(0f, 1f)
valueAnimator.duration = (pathMeasure.length * 5).toLong()
valueAnimator.interpolator = AccelerateDecelerateInterpolator()
valueAnimator.addUpdateListener {
distance = it.animatedValue as Float
pathMeasure.getPosTan(distance * pathMeasure.length, pos,null)
val planeX = pos[0]
val planeY = pos[1]
plane.x = planeX
plane.y = planeY
}
valueAnimator.doOnEnd {
if (pathMeasure.nextContour()) {
flyPlane()
}
}
valueAnimator.start()
}

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Motion Designer is your best friend for creating animations

Animation is a powerful tool to convey messages, evoke emotions, and send feedback on a user’s action. Every detail of a good animation is well thought out and makes sense.
Watch Video

Motion Designer is your best friend for creating animations

Mikhail Zotyev
Senior Flutter Engineer
Wolt

Motion Designer is your best friend for creating animations

Mikhail Zotyev
Senior Flutter Engin ...
Wolt

Motion Designer is your best friend for creating animations

Mikhail Zotyev
Senior Flutter Engineer
Wolt

Jobs

now we just take the pos in which co-ordinates were written during animation and place our plane on these co-ordinates since we are invalidating in value animation on each animated value the animation will look smooth.

 

 

but hey planes do not fly backwards or sideways we can not go against nature and break the laws of physics we need to do something about this.

Taking a right direction in Life

Do you remember the third argument to getPostTan() which gives as tangent at any point on the curve also described in image above, it will help us to align the tip of the plane in the right direction.

private fun flyPlane() {
var distance = 0f
val tan = floatArrayOf(0f,0f)
val pos = floatArrayOf(0f, 0f)
val valueAnimator = ValueAnimator.ofFloat(0f, 1f)
valueAnimator.duration = (pathMeasure.length * 5).toLong()
valueAnimator.interpolator = AccelerateDecelerateInterpolator()
valueAnimator.addUpdateListener {
distance = it.animatedValue as Float
pathMeasure.getPosTan(distance * pathMeasure.length, pos, tan)
val planeX = pos[0]
val planeY = pos[1]
plane.x = planeX
plane.y = planeY
val degrees = atan2(tan[1], tan[0]) * 180.0 / Math.PI
plane.rotation = degrees.toFloat() - 180
}
valueAnimator.doOnEnd {
if (pathMeasure.nextContour()) {
flyPlane()
}
}
valueAnimator.start()
}

here we are getting tangent to pos then converting this angle to degrees are setting rotation of plane accordingly.

 

 

Flying Multiple Planes

One Plane is not enough to defeat our enemy we need more fighters , I am just going to use and array of planes and and array of pathmeasures one path measure for each plane then just modify our fly plane function a bit to accept a plane and a pathmeasure.

private fun flyPlane(plane: ImageView, pathMeasure: PathMeasure) {
var distance = 0f
val tan = floatArrayOf(0f,0f)
val pos = floatArrayOf(0f, 0f)
val valueAnimator = ValueAnimator.ofFloat(0f, 1f)
// fly planes with different speed so that they look separately on the screen
valueAnimator.duration = (pathMeasure.length * random.nextInt(3,8)).toLong()
valueAnimator.interpolator = AccelerateDecelerateInterpolator()
valueAnimator.addUpdateListener {
distance = it.animatedValue as Float
pathMeasure.getPosTan(distance * pathMeasure.length, pos, tan)
val planeX = pos[0]
val planeY = pos[1]
plane.x = planeX
plane.y = planeY
val degrees = atan2(tan[1], tan[0]) * 180.0 / Math.PI
plane.rotation = degrees.toFloat() - 180
}
valueAnimator.doOnEnd {
if (pathMeasure.nextContour()) {
flyPlane(plane, pathMeasure)
}
}
valueAnimator.start()
}

 

If you find it interesting you can find complete code on my Github

 

This article was originally published on proandroiddev.com on June 30, 2022

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog

How to animate BottomSheet content using Jetpack Compose

Early this year I started a new pet project for listening to random radio…
READ MORE
blog
The LookaheadScope (replaced by the previous LookaheadLayout) is a new experimental API in Jetpack…
READ MORE
blog
Expandable views are a common way to hide details of a visualised data structures.…
READ MORE
blog
Working with user interfaces sometimes is not an easy job. On Android you have…
READ MORE
Menu