Follow the Wechat account: Jetpack Compose Museum and join the Compose Chinese Technology community for more quality technology tutorials!

Currently, there is an ongoing Chinese manual project for Jetpack Compose, which aims to help developers better understand and master the Compose framework. It is still under construction, and everyone is welcome to follow and join! The author of this article is spicy Chicken Leg burger, the article has been published in the manual, welcome to check.

introduce

Jetpack Compose is a modern toolkit for building native Android UIs. Jetpack Compose simplifies and speeds UP UI development on Android with less code, powerful tools, and the intuitive Kotlin API.

In this tutorial, you will build a simple UI component using declarative functions. You will not edit any XML layouts or use layout editors. Instead, you call the Jetpack Compose function to specify the element you want, and the Compose compiler does the rest.

💡 Note: See the Jetpack release notes for the latest updates.

Step 1: Composable function

The Jetpack Compose is built around the Composable function. These functions let you programmatically define your application’s user interface by describing its shape and data dependencies, rather than focusing on the UI building process. To create a Composable function, simply add the @composable annotation to the function name.

Add a Text element

First, we’ll add a Text element to the onCreate method to display a “Hello World!” You can do this by defining a block of content and then calling the Text() function

The setContent block defines an Avtivity layout that we can call to the Composable function, which can only be called from other Composable functions

Jetpack Compose uses a Kotlin compiler plug-in to convert these Composable functions into UI elements for your application. For example, the Text() function defined by the Compose UI library can display a Text label on the screen.

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContent {
            Text("Hello world!")}}}Copy the code

Define a composable function

A Composable function can only be called from within the scope of other Composable functions. To write a Composable function, we need to add a @composable annotation. To better understand, we can define a MessageCard() function that takes a name parameter and uses this parameter to configure the text element

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContent {
            MessageCard("Android")}}}@Composable
fun MessageCard(name: String) { // Composable functions start with upper case to distinguish them from normal functions
    Text(text = "Hello $name")}Copy the code

Preview your functions in Android Studio

Android Studio lets you preview your Composable functions in the IDE without having to download the app to an Android device or emulator. One limitation, however, is that the Composable function that needs to be previewed must not have any parameters. Because of this limitation, you can’t preview the MessageCard() function directly. However, you can try writing the first function called PreviewMessageCard(), which calls MessageCard() with arguments. Add the @Preview annotation before @Composable.

@Composable
fun MessageCard(name: String) {
    Text (text = "Hello $name!")}@Preview
@Composable
fun PreviewMessageCard(a) {
    MessageCard("Android")}Copy the code

Rerunking your project, the app itself doesn’t change because the new PreviewMessageCard() function isn’t called anywhere, but Android Studio adds a preview window. This window shows a Preview of the UI elements created by the Composable function labeled with the @Preview annotation. If you need to update the preview at any time, click the refresh button at the top of the preview window.

Step 2: Interface

UI elements are hierarchical, with elements contained within other elements. In Compose, you can create a Composable hierarchy by calling the Composable functions from among the other Composable functions.

Adding more than one text

So far, we’ve created our first Composable function and preview! To discover more of the Jetpack Compose functionality, we’ll build a simple message screen with a list of messages that can be expanded with some animation.

Let’s first enrich our MessageCard function by displaying the author’s name and information content. We first need to change our function parameters to accept a message object instead of a string, and add another Text function to the MessageCard function. Be sure to update the preview as well.

data class Message(val author: String, val body: String)

@Composable
fun MessageCard(msg: Message) {
    Text(text = msg.author)
    Text(text = msg.body)
}

@Preview
@Composable
fun PreviewMessageCard(a) {
    MessageCard(
        msg = Message("Jetpack Compose Museum"."We're starting to update."))}Copy the code

This code creates two Text elements within the app. However, because we didn’t arrange how to arrange them, the two Text elements overlapped, making it impossible for us to read.

The use of the Column

The Column function allows you to arrange elements vertically. Add a Column to the MessageCard() function.

You can use Row to arrange items horizontally and Box to stack elements.

@Composable
fun MessageCard(msg: Message) {
    Column {
        Text(text = msg.author)
        Text(text = msg.body)
    }
}
Copy the code

Add an Image element

To enrich our MessageCard, we can add an avatar and use explorer to add an image to the project.

We’ll add a Row() function to give us a nice structure, and our Image element will be added to it.

5
@Composable
fun MessageCard(msg: Message) {
    Row {
        Image(
            painterResource(id = R.drawable.profile_picture),
            contentDescription = "profile picture" // This description is used for accessibility
        )
        Column {
            Text(text = msg.author)
            Text(text = msg.body)
        }
    }
}
Copy the code

Optimize your UI

Now our layout has the right structure, but there are no good spacing between the elements, and the image on the left is too large. To decorate and configure a Composable, Compose uses modifiers, which allow you to change the Composable’s size, layout, appearance or add advanced interactions. For example, you can make an element clickable. You can link them together to create richer Composables, so let’s use some of them to improve the layout.

@Composable
fun MessageCard(msg: Message) {
    Row(
        modifier = Modifier.padding(all = 8.dp) // Add the padding around our Card
    ) {
        Image(
            painterResource(id = R.drawable.profile_picture),
            contentDescription = "profile picture",
            modifier = Modifier
                .size(50.dp) // Change the size of the Image element
                .clip(CircleShape) // Crop the image to a circle
        )
        Spacer(Modifier.padding(horizontal = 8.dp)) // Add an empty control to fill the horizontal spacing. Set the padding to 8.dp
        Column {
            Text(text = msg.author)
            Spacer(Modifier.padding(vertical = 4.dp))
            Text(text = msg.body)
        }
    }
}
Copy the code

Step 3: Material Design

Compose is created to support Material Design principles. Many of its UI elements are Material Design out of the box. In this step, you will design your application with the Material widget.

The use of Material Design

Our MessageCard now has a layout, but it doesn’t look great yet,

Jetpack Compose provides the implementation of Material Design, and we will use the style of Material Design to improve our MessageCard.

First, we wrap our MessageCard function around a Material theme created in your project, which in our case is an ExamplesTheme. Add it to both @preview and setContent.

Material Design is built around three elements. Color, Typography and Shape. Let’s add them one by one.

override fun onCreate(savedInstanceState: Bundle?). {
    super.onCreate(savedInstanceState)
    setContent {
        ExamplesTheme {
            MessageCard(Message("Jetpack Compose Museum"."We're starting to update."))}}}@Preview
@Composable
fun PreviewMessageCard(a) {
    ExamplesTheme {
        MessageCard(
            msg = Message("Jetpack Compose Museum"."We're starting to update."))}}Copy the code

💡 Note: an empty Compose Activity has automatically generated a default theme for your project, allowing you to customize the MaterialTheme. If you name your project differently than ExamplesTheme, you can find your custom theme in the ui.theme package.

(Color)

It’s easy to use the colors in a wrapped theme, and you can use the values in the theme wherever the color is needed.

Let’s decorate the title and add a border to the image.

Row(
    modifier = Modifier.padding(all = 8.dp)
) {
    Image(
        painterResource(id = R.drawable.profile_picture),
        contentDescription = "profile picture",
        modifier = Modifier
            .size(50.dp)
            .clip(CircleShape)
            .border(1.5.dp, MaterialTheme.colors.secondary, shape = CircleShape) // Add a border
    )
    Spacer(Modifier.padding(horizontal = 8.dp))
    Column {
        Text(
            text = msg.author,
            color = MaterialTheme.colors.secondaryVariant // Add color
        )
        Spacer(Modifier.padding(vertical = 4.dp))
        Text(text = msg.body)
    }
}
Copy the code

Typography

Material Typography styles are available in the MaterialTheme as long as you add them to the Text Composables.

Column {
    Text(
        text = msg.author,
        color = MaterialTheme.colors.secondaryVariant,
        style = MaterialTheme.typography.subtitle2 / / add style
    )
    Spacer(Modifier.padding(vertical = 4.dp))
    Text(
        text = msg.body,
        style = MaterialTheme.typography.body2 / / add style)}Copy the code

Shape (Shape)

With the shape, we can add the finishing touches. We implemented a card-like design for the entire MessageCard

@Composable
fun MessageCard(msg: Message) {
    Surface(
        shape = MaterialTheme.shapes.medium, // Use the shape of the MaterialTheme
        elevation = 5.dp,
        modifier = Modifier.padding(all = 8.dp)
    ) {
        Row(
            modifier = Modifier.padding(all = 8.dp)
        ) {
            Image(
                painterResource(id = R.drawable.profile_picture),
                contentDescription = "profile picture",
                modifier = Modifier
                    .size(50.dp)
                    .clip(CircleShape)
                    .border(1.5.dp, MaterialTheme.colors.secondary, shape = CircleShape)
            )
            Spacer(Modifier.padding(horizontal = 8.dp))
            Column {
                Text(
                    text = msg.author,
                    color = MaterialTheme.colors.secondaryVariant,
                    style = MaterialTheme.typography.subtitle2
                )
                Spacer(Modifier.padding(vertical = 4.dp))
                Text(
                    text = msg.body,
                    style = MaterialTheme.typography.body2
                )
            }
        }
    }
}
Copy the code

Enable dark themes

A dark theme (or night mode) can avoid bright displays, especially at night, or just to save on the device’s battery. Because it supports Material Design, Jetpack Compose can handle dark themes by default. After using the Material color, the text and background will automatically adapt to the dark background.

You can create multiple previews in your file, as separate functions, or add multiple annotations to the same function.

Let’s add a new preview note and enable night mode on mobile or virtual machine.

@Preview(name = "Light Mode")
@Preview(
    uiMode = Configuration.UI_MODE_NIGHT_YES,
    showBackground = true,
    name = "Dark Mode"
)
@Composable
fun PreviewMessageCard(a) {
    ExamplesTheme {
        MessageCard(
            msg = Message("Jetpack Compose Museum"."We're starting to update."))}}Copy the code

The color choices for light and dark themes are defined in the theme.kt file generated by the IDE.

So far, we’ve created a MessageCard element that displays an image and two different styles of text, and looks good in both light and dark themes

4. Lists and animations

Create a list message card

So far, we’ve only had one message card, which seems a little monotonous, so let’s improve it by having multiple messages. We need to create a function that displays multiple messages. In this case, we can use Compose’s LazyColumn and LazyRow. These Composables only render elements that are visible on the screen, so their design works well for long lists. At the same time, they avoid the complexity of RecyclerView and XML layouts.

import androidx.compose.foundation.lazy.items

@Composable
fun Conversation(messages: List<Message>) {
    LazyColumn {
        items(messages) { message ->
            MessageCard(msg = message)
        }
    }
}

@Composable
fun PreviewMessageCard(a) {
    ExamplesTheme {
        Conversation(messages = MsgData.messages)
    }
}

Copy the code

You can get the code for MsgData here

In this code snippet, you can see that LazyColumn has an Items subitem. It takes a List as a parameter, and its lambda takes a parameter we name message (we can name it whatever we want). This lambda will call each item provided in the List.

Interactive animation effects

Our conversations are getting more and more interesting, so it’s time for some animation! We will animate the size and background color of a card when clicking on it to see the details. To store this local UI state, we need to keep track of whether a message has been expanded. To track this state change, we must use the remember and mutableStateOf functions.

The Composable function can store the local state in memory by using Remember and track changes in the value passed to mutableStateOf. When the value is updated, the Composable function (and its subfunctions) that use the state are automatically redrawn. We call this recomposition.

By using Compose’s state apis, such as Remember and mutableStateOf, any changes to state automatically update the UI.

@Composable
fun MessageCard(msg: Message) {

    var isExpanded by remember { mutableStateOf(false)}// Create a variable that detects if the card has been expanded

    Surface(
        shape = MaterialTheme.shapes.medium,
        elevation = 5.dp,
        modifier = Modifier
            .padding(all = 8.dp)
            .clickable { // Add a new Modifier extension method that makes elements clickisExpanded = ! isExpanded// Write the event content of the click
            }
    ) {
        Row(
            modifier = Modifier.padding(all = 8.dp)
        ) {
            Image(
                painterResource(id = R.drawable.profile_picture),
                contentDescription = "profile picture",
                modifier = Modifier
                    .size(50.dp)
                    .clip(CircleShape)
                    .border(1.5.dp, MaterialTheme.colors.secondary, shape = CircleShape)
            )
            Spacer(Modifier.padding(horizontal = 8.dp))
            Column {
                Text(
                    text = msg.author,
                    color = MaterialTheme.colors.secondaryVariant,
                    style = MaterialTheme.typography.subtitle2
                )
                Spacer(Modifier.padding(vertical = 4.dp))
                Text(
                    text = msg.body,
                    style = MaterialTheme.typography.body2,
                    // Modify the maxLines argument to display only one line of text by default
                    maxLines = if (isExpanded) Int.MAX_VALUE else 1.// Make a Composable animation
                    modifier = Modifier.animateContentSize()
                )
            }
        }
    }
}
Copy the code

Now that we’re done testing each card, let’s move on to doing something else with isExpanded! Like changing the color of the card

    // Create a variable that changes the color based on the value of the isExpanded variable
    val surfaceColor by animateColorAsState(
        targetValue = if (isExpanded) Color(0xFFCCCCCC) else MaterialTheme.colors.surface
    )

    Surface(
        shape = MaterialTheme.shapes.medium,
        elevation = 5.dp,
        modifier = Modifier
            .padding(all = 8.dp) .clickable { isExpanded = ! isExpanded }, color = surfaceColor ) { ... . }Copy the code

Done!

Congratulations, at this point you’ve learned the basics of Compose. You’ve created a simple message screen that effectively displays a list of images and text in the style of Material Design, including a dark theme and preview. You can do this in less than 100 lines of code.

What you’ve learned so far

  • To define aComposablefunction
  • Add different elements to your Composable function
  • Use the Layout Composable to build your UI structure
  • Use modifiers to extend your Composables
  • Create a valid list
  • Track the state, and modify it
  • User interaction was added to a Composable
  • While extending the information, animation processing is carried out

All the code for this article can be found here