Crafting Typewrite Text Animation & Custom Quote Card with Jetpack Compose

Kappdev
5 min readFeb 6, 2024

--

Welcome 👋! In this article, we’ll create a Typewrite Text Animation with Jetpack Compose. This simple yet captivating animation presents boundless opportunities for creativity 🌟. Let’s dive in together🚀

Typewrite Text Animation

Let’s begin by crafting the composable function that will power our typewriting text animation.

Function signature

@Composable
fun TypewriteText(
text: String,
modifier: Modifier = Modifier,
isVisible: Boolean = true,
spec: AnimationSpec<Int> = tween(durationMillis = text.length * 100, easing = LinearEasing),
style: TextStyle = LocalTextStyle.current,
preoccupySpace: Boolean = true
)

Parameters

  1. text ➜ The complete text that will be gradually revealed as if being typed.
  2. modifier ➜ The Modifier to be applied to the layout.
  3. isVisible ➜ Determines if the text should be visible or not. When set to true, the text gradually appears; when false, it stays hidden.
  4. spec ➜ Specifies the animation specifications. By default, it’s a linear animation with a duration proportional to the text length, revealing each symbol in 100 milliseconds.
  5. style ➜ Defines the style of the text. The default value is set to the current local text style.
  6. preoccupySpace ➜ Defines whether to preoccupy space during animation. If true, the space needed for text will be preoccupied before the animation to avoid changes during the animation.

Implementation

With the function signature defined, let’s proceed to implement the TypewriteText function and bring our animation to life.

@Composable
fun TypewriteText(
// Parameters
) {
// State that keeps the text that is currently animated
var textToAnimate by remember { mutableStateOf("") }

// Animatable index to control the progress of the animation
val index = remember {
Animatable(initialValue = 0, typeConverter = Int.VectorConverter)
}

// Effect to handle animation when visibility changes
LaunchedEffect(isVisible) {
if (isVisible) {
// Start animation if visible
textToAnimate = text
index.animateTo(text.length, spec)
} else {
// Snap to the beginning if not visible
index.snapTo(0)
}
}

// Effect to handle animation when text content changes
LaunchedEffect(text) {
if (isVisible) {
// Reset animation and update text if visible
index.snapTo(0)
textToAnimate = text
index.animateTo(text.length, spec)
}
}

// Box composable to contain the animated and static text
Box(modifier = modifier) {
if (preoccupySpace && index.isRunning) {
// Display invisible text when preoccupation is turned on
// and the animation is in progress.
// Plays the role of a placeholder to occupy the space
// that will be filled with text.
Text(
text = text,
style = style,
modifier = Modifier.alpha(0f)
)
}

// Display animated text based on the current index value
Text(
text = textToAnimate.substring(0, index.value),
style = style
)
}
}

Now that we’ve completed our animated text, let’s move on to crafting a custom animated quote card.

Animated Quote

Before we delve into the function, let’s start by creating a Quote data class to represent the data.

data class Quote(
val text: String,
val author: String
)

Additionally, let’s define a custom quote text style.

@Composable
fun quoteTextStyle() = TextStyle(
fontSize = 16.sp,
lineHeight = 18.sp,
fontFamily = FontFamily.Serif,
fontStyle = FontStyle.Italic,
color = MaterialTheme.colorScheme.onSurface
)

Following our initial setup, we can move forward with defining the QuoteCard function.

@Composable
fun QuoteCard(
quote: Quote,
modifier: Modifier = Modifier,
millisPerSymbol: Int = 100,
authorDelay: Int = 300,
)

Parameters

  1. quote ➜ The quote we want to display.
  2. modifier ➜ The Modifier to be applied to the quote layout.
  3. millisPerSymbol ➜ Specifies the duration (in milliseconds) for each symbol during the typing animation, allowing you to control the speed.
  4. authorDelay ➜ Specifies the delay (in milliseconds) before the typing animation for the author’s name starts.

Implementation

Once everything is ready, we can implement the final quote card.

@Composable
fun QuoteCard(
// Parameters
) {
// Calculate typing animation durations based on text and author lengths
val textDuration = (quote.text.length * millisPerSymbol)
val authorDuration = (quote.author.length * millisPerSymbol)

// Quote card with rounded corners and shadow
Surface(
modifier = modifier,
shape = RoundedCornerShape(topEnd = 32.dp, bottomStart = 32.dp),
shadowElevation = 16.dp
) {
Column(
modifier = Modifier.padding(16.dp),
) {
// Typewriting animation for the quote text
TypewriteText(
text = quote.text,
style = quoteTextStyle(),
spec = tween(
durationMillis = textDuration,
easing = LinearEasing
)
)
// Typewriting animation for the author with specified delay
TypewriteText(
text = "- ${quote.author}",
style = quoteTextStyle(),
spec = tween(
durationMillis = authorDuration,
delayMillis = textDuration + authorDelay,
easing = LinearEasing
),
modifier = Modifier.align(Alignment.End)
)
}
}
}

Congratulations🥳! We’ve successfully built it👏. For the complete code implementation, you can access it on GitHub Gist🧑‍💻. In the next section, we’ll explore the usage of the functions.

Advertisement

Are you learning a foreign language and struggling with new vocabulary? Then, I strongly recommend you check out this words-learning app, which will make your journey easy and convenient!

WordBook

Usage

Now is the time to try it out in an example, where quotes will change on click.

First, we need quotes to display, so let’s create them.

val Quotes = listOf(
Quote(
text = "Keep your face always toward the sunshine, and shadows will fall behind you.",
author = "Walt Whitman"
),
Quote(
text = "Your time is limited, so don't waste it living someone else's life. Don't be trapped by dogma – which is living with the results of other people's thinking",
author = "Steve Jobs"
),
Quote(
text = "The best and most beautiful things in the world cannot be seen or even touched - they must be felt with the heart.",
author = "Helen Keller"
)
)

With all the pieces in place, let’s bring them together to finalize the implementation.

var quote by remember { mutableStateOf(Quotes.random()) }

QuoteCard(
quote = quote,
modifier = Modifier
.fillMaxWidth()
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = {
quote = Quotes.random()
}
)
.padding(16.dp)
)

Output:

You might also like 👇

Thank you for reading this article!❤️ I hope you’ve found it enjoyable and valuable. Feel free to show your appreciation by hitting the clap👏 if you liked it or follow Kappdev for more exciting articles😊.

Happy coding!

--

--

Kappdev

💡 Curious Explorer 🧭 Kotlin and Compose enthusiast 👨‍💻 Passionate about self-development and growth ❤️‍🔥 Push your boundaries 🚀