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


    (Senior) Android Software Developer (w/m/d)

    Paradox Cat GmbH
    Munich
    • Full Time
    apply now

    (Senior) Android Developer – Machine Learning (w/m/d)

    Paradox Cat GmbH
    Munich
    • Full Time
    apply now

    API Engineer

    American Express
    Phoenix, USA
    • Full Time
    apply now
Load more listings

OUR VIDEO RECOMMENDATION

, ,

Composable Sheep: A Compose Animations Journey

Animations are a staple of fun and beautiful UI. And with Compose here to stay, it’s a good time to learn more about Compose Animations. In this talk, we’ll take a dive in the Compose…
Watch Video

Composable Sheep: A Compose Animations Journey

Nicole Terc
Android Engineer
Clue

Composable Sheep: A Compose Animations Journey

Nicole Terc
Android Engineer
Clue

Composable Sheep: A Compose Animations Journey

Nicole Terc
Android Engineer
Clue

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
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
blog
As an Android Developer I love finding some neat trick or peace of code…
READ MORE

Leave a Reply

Your email address will not be published.

Fill out this field
Fill out this field
Please enter a valid email address.

Menu