HomeAndroidForm Morphing in Android. Create and draw morph animations on… | by...

Form Morphing in Android. Create and draw morph animations on… | by Chet Haase | Android Builders | Apr, 2023


Creating and animating rounded shapes with AndroidX, Half II

In the earlier article, I confirmed use the brand new AndroidX :graphics:graphics-shapes: library that Sergio Sancho and I just lately launched to create and draw rounded polygonal shapes. Making it simpler to create these sort of shapes was half of what we wished to perform… nevertheless it wasn’t our primary purpose. What we actually wished to do was to make it simpler to routinely animate (aka, “morph”) between these shapes.

Spoiler alert: Now you may.

Welcome to Half II of this sequence, by which I’ll present use this new library to just do that: create and draw morph animations on rounded shapes. Simply.

Once I first began trying into the issue, it was clear that not solely was morphing between shapes a tough downside, nevertheless it’s really (so far as I can inform) an unsolved analysis downside.

That’s: it’s potential to create nice animations between arbitrary shapes. However the extra random these shapes get, the extra this turns into a “design-time” downside, the place the developer/designer must become involved to determine precisely how they need the animation to look. The issue is that animating between two very dissimilar, and probably very advanced, shapes can lead to some very bizarre animations. So it takes some intervention on the a part of the person/designer to resolve construction the animation (and presumably the underlying geometry of the shapes) to stop issues from trying too odd.

To assist image why that is the case, let’s say you need to animate from some triangular object to rounded rectangle form, like this:

The general method to such an operation is to make the underlying constructions (a sequence of vector operations) related, such that you may then simply animate between the factors making up these constructions. You may think about, for conditions just like the one above, an method the place you added placeholder curves at applicable locations on the straightforward form on the left that develop into the curves wanted to realize the form on the precise. This will not be trivial code, however it’s potential to automate this method.

Now think about that your design workforce requested you to animate the triangle to this finish form as a substitute:

Given the mix of the discontinuities and a number of sub-shapes of the thing on the precise, along with the sheer complexity of the tip form in comparison with the beginning form, it’s not clear how one would create such an animation. On the very least, it appears tough, and even unimaginable, to routinely calculate such an animation at runtime that seemed cheap. This, then, turns into a design-time downside the place somebody wants to determine how the easier ought to develop to the extra difficult one: the place new sub-shapes would animate out from, animate the seems of disconnected shapes, and so forth.

There are instruments for such issues. For instance, design instruments similar to After Results allow creating and fine-tweaking such animations. And within the Android world, Alex Lockwood created a software known as ShapeShifter which additionally permits modifying advanced morphs like this.

However in our case, we wished all animations to occur routinely. That’s, given any begin or finish form, we wished to have the ability to calculate a morph between the 2 and run it on the fly, with out it trying bizarre.

So: how to do that? I confirmed above why this isn’t a tractable downside. Within the common case, you merely can’t deal with arbitrary objects in a approach that permits all morphs to be cheap. And but we nonetheless wished to animate between our curved shapes.

It was time to constrain the issue a tad.

The issue above, for common shapes, is that pesky phrase: common. Positive, if somebody throws an arbitrary advanced and discontiguous form at us, we can not deal with it elegantly. However, for our functions, we didn’t must. That’s: we wished to have the ability to animate between the curved shapes that this library permits; we didn’t want to unravel morphing for all shapes. So we constrained the issue to just do that.

Particularly, the rounded polygonal shapes that our library produces are (a) contiguous (there aren’t any separate sub-objects like we see within the map form above) and (b) non self-intersecting* (the vertices proceed in an ordered style across the define). Furthermore, we create all of those objects with the identical actual construction internally (a sequence of Bézier cubic curves from begin to end); the one structural distinction between any two shapes is simply the place and amount of these curves.

* The non-self-intersection constraint will not be strictly true. One of many constructors for RoundedPolygon takes an arbitrary checklist of vertices, as proven in a the ultimate triangle-ish instance of the earlier article. So that you can really create your individual hopelessly advanced self-intersecting arbitrary form utilizing that API… and you’ll then run into a few of the issues described above. So you will want to, er, constrain your self (and your shapes) to observe related guidelines as we do with the opposite polygonal constructors with a view to allow cheap morph outcomes.

Constraining the morphing downside on this approach allowed us to scale back the infinite analysis downside of general-case morphing into an issue of mapping these two lists of curves collectively. And voilà, we had computerized morphing that labored.

The essential method to morphing is to take two probably dissimilar shapes and produce some mapping of 1 form to the opposite, such that the animation is so simple as interpolating between the purpose values of the mapping. In our case, it means mapping one ordered checklist of cubic curves (for the beginning form) to a different checklist of cubic curves (for the ending form). The mapping must account for proximity (figuring out which curves are closest to one another between the shapes) in addition to amount (if the shapes do not need the identical variety of curves, then new curves will probably be created to make sure that the mapping is 1:1 — every curve on one form maps to a selected curve on the opposite).

Function Full

One vital factor to notice in our method is our use of options within the shapes, which is our identify for the sides and corners of shapes. Particularly, we extract details about the place the options happen on every form, together with the convexity of the nook options. Aligning the constructions (detailed within the steps under) makes use of details about these options in order that options morph to related options every time potential.

Probably the most simple method to calculate a morph is to easily pair every curve to the one closest to it on the opposite form (by way of the define progress round each of the curves), including curves every time vital. Nonetheless, this tends to end in animation artifacts, the place curves round corners would possibly break up as much as turn into unrelated curves and edges of the opposite form. We experimented (rather a lot!) and ended up taking a special method that we name “characteristic mapping,” the place we use details about these options, thus preserving a few of the basic traits of every form by way of the morph and, alongside the best way, decreasing a few of the angular artifacts that we had encountered. The top result’s significantly better typically than lots of our experiments alongside the best way.

For instance, on this display screen shot of the demo app, we are able to see that an animation from the triangle-ish form to the star maps all 4 current nook options of the triangle (the three convex corners and the concave one alongside the underside) to related options within the star. In the meantime, the opposite two convex nook options of the star develop out of the sides of the triangle.

Morphing Steps

In our implementation, the morph setup course of is damaged up into three levels:

  • Measurement**: Measurement determines the place every curve is situated alongside the general define of its form. That is recorded in an outlineProgress parameter (a worth from 0 to 1), indicating the place every curve level is between the beginning (0) and finish (1) of the complete form define. This step additionally extracts the options of every form, recording details about which curves lie alongside edges, convex corners, or concave corners. This info is vital within the later Mapping part to make sure clean animations.
  • Mapping: Given the checklist of options for every form, we are able to now create a mapping between these options (e.g., a convex nook on the beginning form ought to map to the closest convex nook on the tip form). As soon as that’s performed, we are able to equally map the remainder of the curves on the objects between these shapes, primarily based on the relative proximity of those curves to one another between the mapped options.
  • Matching: As soon as the constructions are mapped (for his or her present options/curves), we are able to then create the morph construction which matches and maps the curves of each shapes, successfully creating an inventory of values that may be simply and rapidly interpolated between the shapes throughout the animation.
    An vital a part of this course of is what we name reducing.*** As we stroll round each form outlines, matching the curves on one to the curves on the opposite, we have to insert curves on probably each objects to create an general construction that simply interprets from one to the opposite. For instance, within the above instance of animating the triangle-ish form to the star, all 4 options on the triangle map to related options on the star, however two further options for the celebs different factors have to be inserted within the morph construction in order that they’ll animate into being as we morph from the triangle to the star. That is performed by reducing the sting curves on the triangle form to create the curves that can morph into the convex corners on the star form.

** It’s value noting that the present implementation of the library makes use of a quite simple (and quick!) algorithm for calculating distances, utilizing the angle measurement for every level relative to the polygon’s middle. This method is ample (and quick!) for many instances, however will be suboptimal in some conditions, similar to the place the vertices are usually not all equidistant from the middle. A greater, extra common method is to make use of the precise curve size (or a minimum of an approximation to it); search for that change to the library finally, both by default measurer or a minimum of as an choice.

*** Begin/finish shapes normally have unequal numbers of curves. Contemplate, for instance, that an unrounded triangle has simply three “curves” to symbolize its three straight edges, whereas a rounded and smoothed triangle could have as many as twelve curves (three edges plus three for every rounded nook — two flanking curves plus the middle round rounding curve). So there’s virtually all the time reducing work to be performed to end in constructions for each shapes which have the identical numbers of curves.

There are a lot of (many) particulars about how the above steps are executed internally. Though I discover them very fascinating… I additionally discover them to be a bit past the scope of an article exhibiting use the API. So I invite you to take a look at the code for the precise particulars. Or you may simply assume that the code does all the stuff above, and I’ll get all the way down to the small print of use the library to really morph some shapes.

After you have two shapes (created with the library APIs described in the earlier article), morphing between these shapes requires simply two steps:

  1. Create the Morph object
  2. Animate the morph’s progress property

… and that’s it. All the tough components of morphing are taken care of for you within the creation of the shapes themselves (with their useful constraints as outlined above) and within the building of the morph object.

For instance, to animate a sharp triangle to a rounded star form, you would possibly do one thing like this:

val pointyTriangle = RoundedPolygon(3)
val roundedStar = Star(5, innerRadius = .5f, CornerRounding(.1f))
val morph = Morph(pointyTriangle, roundedStar)
// ... one straightforward method to run the morph animation...
ObjectAnimator.ofFloat(morph, "progress", 0f, 1f).begin()

The precise animation method can differ broadly from the code within the snippet above. For instance, a Compose app wouldn’t use an ObjectAnimator right here. And chances are you’ll must do extra to make the replace seen within the app (similar to invalidating the view which attracts the Morph object). However this reveals the fundamental method: change the morph’s progress to replace its form between the beginning (0) and finish (1) shapes.

For this and the final use of the APIs, try the ShapesDemo pattern code to see the way it works in observe, in each the Compose and View UI worlds.

As I mentioned earlier, the difficult components of morphing are contained in the library (and are drastically simplified by a few of the constraints mentioned above). The precise use of the API is proscribed to what you see above: create two shapes, create a morph with them, and animate the morph’s progress.

There’s, in fact, a lot that we might add to this library to make it much more highly effective. Most of the issues we take into consideration contain flexibility. For instance, I discussed in a footnote above that we presently use angular measurement to trace the outlineProgress for the shapes’ curves, and that we are going to be altering quickly to make use of curve lengths as a substitute. However why not each? We wish to have an API to permit totally different measurement algorithms for use, whether or not they’re offered by the library, or by builders creating fully customized measurement approaches.

Equally, it is perhaps good to permit builders to plug in numerous matching algorithms. We’re fairly proud of the characteristic mapping consequence mentioned above, however perhaps you desire a barely totally different really feel to your animations (instance: permit concave options to map to convex ones in some instances). You can not try this now… would you prefer to? It could require some work (and intelligent APIs), nevertheless it appears doable… finally, if it could be helpful.

One other characteristic space we’d prefer to discover includes including the flexibility to morph arbitrary Path objects. With the introduction to the platform and to AndroidX of path querying APIs (lastly!), we should always have the ability to assemble a morph between two arbitrary paths, as a substitute of requiring use of the RoundedPolygon API for morphing. The caveat right here (and it’s an enormous one) is what I discussed above; a morph between two very dissimilar Path objects could produce very undesirable outcomes. This API is perhaps far more restrictive (with presumably much less routinely pleasing outcomes), however it could be good to supply some approach, even with limitations, to routinely morph current Path objects.

APIs!

The library is offered in alpha kind from AndroidX:

Pattern code!

The animation at first of the article was taken from a pattern app now hosted on GitHub:

The pattern has each Compose- and View-based apps, exhibiting use the library to create and morph shapes for each toolkits. The Compose model has a further editor view that helps visualize the varied form parameters, together with a debug view that permit’s you peek behind the scenes to see the cubic curves making up these shapes.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments