Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Not a Phase - Text with Compose and Canvas
Eevis
Eevis

Posted on • Originally published ateevis.codes

     

Not a Phase - Text with Compose and Canvas

I've continued my journey with Compose and Canvas! After exploring drawing and animating shapes, I wanted to learn more about text. Bi-visibility Day was coming, so I drew a small animation to publish on Instagram. The final animation looks like this:

In this blog post, we will look at how to add text to Canvas and position and animate it. We're also utilizing custom Google Fonts in the drawing.

If you're interested in reading the first two posts, here are the links:

Before We Start

Before we start drawing, I want to say a few words about the design. It has the moon in the waning crescent phase, with a dashed line to complete it to the full moon shape. The text says, "Not a phase".

Now, if you're familiar with the discrimination and stereotypes bisexuals face, you probably already know what all of this means. But for those who are not, one of the stereotypes is that bisexuality is "just a phase on the way to being straight/gay".

But it's not - it's an (umbrella) term for people who feel attraction towards their own and other genders. And even if a bi person is in a monogamous relationship with a person from one gender, it doesn't make them straight/gay. They're still bi.

So yeah, we're here. We exist.

Now, let's get to the coding part.

Drawing the Text

Measuring

Drawing text on Canvas is a two-step process: First, measure the text and then draw it. To start with measuring, we'll need aTextMeasurer, and with Compose-code, we have this neat remember-function we can use:

valtextMeasurer=rememberTextMeasurer()
Enter fullscreen modeExit fullscreen mode

For measuring,TextMeasurer has a functionmeasure, which takes in the text as eitherAnnotatedString orString, and a bunch of other (mainly) optional parameters that affect the size of the text. Things likedensity,layoutDirection,style,fontFamilyResolver, and others.

We will divide the text into two strings, as we want to animate and position them a bit differently. As both of our texts are just simple strings with one style, we can use theString-version for both. The first version of the "Not"-text looks like this:

valnotText=textMeasurer.measure(text="Not",style=MaterialTheme.typography.titleSmall.copy(brush=Brush.linearGradient(colors=Colors.biFlag),),)
Enter fullscreen modeExit fullscreen mode

For themeasure-function, we pass in the text and then styles. We want to use the theme typography here for straightforwardness, so we copy the small title styles and add a brush to have a linear gradient as the text color. Here, we're using the bi-flag colors pink, purple, and blue.

The second text is pretty similar:

valphaseText=textMeasurer.measure(text="a phase",style=MaterialTheme.typography.titleLarge.copy(brush=Brush.linearGradient(colors=Colors.biFlag,),fontSize=30.sp,),)
Enter fullscreen modeExit fullscreen mode

For this text, we're utilizing the large title styles from the theme. In addition to gradient colors, we're setting the font size to 30sp to make the text bigger.

Alright, now we have everything we need from the measuring step. Next up is drawing the texts on canvas.

Drawing

Compose Canvas has a method calleddrawText for drawing text. It takes in aTextLayoutResult, which is the type thatmeasure function returns. In addition, it takes other parameters meant for styling and positioning the text on Canvas.

For thenotText we defined in the previous subsection, thedrawText would look like this:

drawText(textLayoutResult=notText,topLeft=Offset(size.width*0.25f,size.height*0.6f,),)
Enter fullscreen modeExit fullscreen mode

We pass in the text layout result, and then we define thetopLeft offset to position the text correctly.

The other text is a bit different. We want to position it relative to thenotText, so we usenotText for calculating the correct position:

drawText(textLayoutResult=phaseText,topLeft=Offset(x=size.width*0.35f,y=(size.height*0.6f+notText.size.height*0.7f),),)
Enter fullscreen modeExit fullscreen mode

So here, we define the y-offset to be the same as for thenotText, and then we add 70% of the height of thenotText. This could be the whole height, but I wanted to keep less break between the texts.

After these steps, our text looks like this:

Blue background, on which there is the moon in waning crescent phase in solid line and line to complete the circle for the moon. Under it, there's the text 'Not a Phase' with a pink, purple, and blue gradient.

There is just one thing left for the drawing - using custom fonts. Let's talk about that next.

Adding Fonts

For this animation, I wanted to have custom fonts. After playing around with Google Fonts, I decided that the two fonts I'm using are Poppins and Damion.

Android documentation has a page about adding fonts to your project:Work with fonts. However, I accidentally found that Android Studio lets you add Google Fonts as XML files straightforwardly. Here's how it happens:

  1. Go to Resource Manager and select the "Font"-tab.
  2. Click the "+" button to add new resource.
  3. Select "More Fonts...".
  4. Find the Google Font you want to use, select weights, and press OK.
  5. Let Android Studio add everything needed, like the certification for fonts.

However, previews don't work correctly if you do it this way and don't import the ttf-files for fonts. So, if you rely on previews when developing, importing those files should resolve the issue.

After the font is available, the next thing to do is to use it in the styles. Here's the code for the font families we're going to use:

valPoppinsFontFamily=FontFamily(Font(R.font.poppins_bold,FontWeight.Bold),)valDamionFontFamily=FontFamily(Font(R.font.damion,FontWeight.Normal),)
Enter fullscreen modeExit fullscreen mode

Then we add the font families to both texts - Damion for the "Not" text and Poppins to the "a phase"-text:

valnotText=textMeasurer.measure(text="Not",style=MaterialTheme.typography.titleSmall.copy(...fontFamily=DamionFontFamily),)
Enter fullscreen modeExit fullscreen mode

And

valphaseText=textMeasurer.measure(text="a phase",style=MaterialTheme.typography.titleLarge.copy(...fontFamily=PoppinsFontFamily),)
Enter fullscreen modeExit fullscreen mode

After these changes, the drawing looks like this:

The same layout as in the previous picture, but the not-text uses Damion-font and a Phase-text uses Poppins-font.

Animating the Text

The last step we'll need to take is animating the text. We will do that by animating colors and floats. To set things up, let's defineinfiniteTransition, which we're going to use later:

val infiniteTransition = rememberInfiniteTransition(    label = "infinite")
Enter fullscreen modeExit fullscreen mode

We also want to show the color animation first on the "not"-text and only after that on the "a phase"-text. One way to accomplish that is to define a helper float, based on which we use to animate the words. We'll get back to the implementation later.

We'll define a variable calledanimationPosition, an infinitely transitioning float from 0f to 4f, which restarts from 0 when it reaches 4. These values could be anything, but after testing, I found that these values worked best when combined with other things in this drawing.

The code foranimationPosition could look like this:

valanimationPositionbyinfiniteTransition.animateFloat(initialValue=0f,targetValue=4f,animationSpec=infiniteRepeatable(tween(durationMillis=10000,easing=EaseIn,),RepeatMode.Restart,),label="animationPosition",)
Enter fullscreen modeExit fullscreen mode

In addition, we will define a helper function for animating the colors. Let's call itbiColorsAnimated, define it to take in a Boolean parameteranimated, and return a list of colors:

@ComposablefunbiColorsAnimated(animated:Boolean):List<Color>{....}
Enter fullscreen modeExit fullscreen mode

Inside the function, we define our animated colors. We first create a list with the colors, and then map through it. For each color, we returnanimateColorAsState's value, which has the typeColor, and finally, we return the list of colors:

valcolors=listOf(biFlag.pink,biFlag.purple,biFlag.blue)returncolors.map{animateColorAsState(targetValue=if(animated)itelsewhite,animationSpec=tween(durationMillis=1000,easing=EaseInBounce,),label=it.toString()).value}
Enter fullscreen modeExit fullscreen mode

This way, we have the bi flag's colors as animated values and can use them with our text.

Finally, we get to tie everything together. For both of the texts, we change the brush gradient's color parameter to use this new function:

valnotText=textMeasurer.measure(text="Not",style=MaterialTheme.typography.titleSmall.copy(brush=Brush.linearGradient(colors=biColorsAnimated(animated=animationPositionin0.5f..1.5f),),...),)valphaseText=textMeasurer.measure(text="a phase",style=MaterialTheme.typography.titleLarge.copy(brush=Brush.linearGradient(colors=biColorsAnimated(animated=animationPositionin2f..3.5f),),...),)
Enter fullscreen modeExit fullscreen mode

We use theanimationPosition value to define if the colors for that text are animated. For the first text, we change the colors from white to the bi flag colors if theanimationPosition is between 0.5f and 1.5f, and for the second, if the value is between 2f and 3.5f.

These changes get us the animation you can see at the beginning of this blog post. You can findthe complete code in this code snippet.

Wrapping Up

In this blog post, we've looked into adding text to Canvas, using custom Google Fonts, and animating colors. There was a lot to cover, but the end result is pretty nice!

I hope you've enjoyed this blog post and learned something. If you want to share your learnings, post on the social media of your choosing, or let me know in the comments if you're reading this on Dev or Medium.

Links in the Blog Post

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Android and Accessibility || Android GDE || Women Techmakers Ambassador
  • Location
    Helsinki, Finland
  • Pronouns
    she/they
  • Work
    Senior Android Developer, Accessibility Specialist
  • Joined

More fromEevis

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp