I’m participating in nuggets Creators Camp # 4, click here to learn more and learn together!

An overview of the

Jetpack Compose is a new tool kit for building native Android interfaces that Google announced at 2019Google I/O and will officially release version 1.0 in July 2021. It simplifies and speeds up interface development on Android, using less code, powerful tools, and an intuitive Kotlin API to make apps live and exciting quickly.

The development environment

In Android Studio Arctic Fox | 2020.3.1 before release, we want to experience the Jetpack Compose to download the Android Studio’s preview Canary, But now the latest version has been updated to Android Studio Bumblebee | 2021.1.1, the Jetpack Compose support has been very perfect, if you still use the old version, don’t hesitate, Learn and experience Jetpack Compose’s advantages over the previous development.

Create a project

After downloading and installing the developer tool, we will try to create a New Jetpack Compose Project by clicking on file-new-new Project in the upper left corner of the developer tool, as shown below. Previously, we would normally select Empty Activity to create an Empty Project. But a closer look shows that there is also an Empty Compose Activity to the left. Select this and click Next to see that an Empty Jetpack Compose project has been created.

Analyze project

First of all, let’s analyze the main interface MainActivity, which inherits from ComponentActivity by default, and finally inherits from Activity. We won’t go into it here. Note that the XML loaded in setContent is not a Composable function, but a Composable function. Jetpack Compose should convert the Composable function into the UI elements of the application.

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyComposeTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colors.background
                ) {
                    Greeting("Android")
                }
            }
        }
    }
}

@Composable
fun MyComposeTheme(darkTheme: Boolean = false, content: @Composable () -> Unit) {}
Copy the code

We notice that the MyComposeTheme function calls a Surface function and the Greeting function is called from the Surface function. The one thing these functions have in common is the @composable annotation. So we probably know that this annotation is used to declare ordinary functions as Composable functions;

@Composable
fun Surface(
    modifier: Modifier = Modifier,
    ... ...
)
@Composable
fun Greeting(name: String) {
    Text(text = "Hello $name!")
}
Copy the code

The @Preview annotation is used to Preview the UI generated by the Composable function. Switch the display mode in the upper right corner of the current page to Split and you will see the UI created by default, which is very simple with only one text.

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    MyComposeTheme {
        Greeting("Android")
    }
}
Copy the code

Instead of XML layout files, we have a Composable function that is continuously nested. The UI is implemented using function declarations, which can simply be considered declarative UI.

Based on using

Practice is the only criterion to test the truth. Let’s write a simple interface for Jetpack Compose. For example, we will implement the following functions: Add an input field and a hover add button. Click the Add button every time the input field is added to a list of images, and click the list to remove the data from the list.

The MyComposeTheme function is a Composable function that inherits from the MaterialTheme function, which represents the theme, and has four properties. They are colors, Typography, Shape and content, so we can roughly know the basic composition of the theme. The MaterialTheme has many built-in Material Design components. So we’ll use the MaterialTheme directly here;

setContent {
    MaterialTheme {
        Surface()
    }
}
Copy the code

In addition, we found that a Surface function was called under the theme, which we can understand as a container. If you are careful, you will notice that the container size of our default project preview interface seems to be only the size of content, but the full screen of the real computer is occupied. This is realized by Surfaced function Modifier.fillmaxSize () property, indicating the maximum number of Surfaced, we can copy this code to a preview function, we can find that the preview interface can also fill the full screen, plus we created a MainBody() function to meet our starting needs;

Surface(
    modifier = Modifier.fillMaxSize(),
    color = MaterialTheme.colors.background
) {
    MainBody()
}
@Composable
fun MainBody() {}
Copy the code

If you are familiar with the Material Design style, that Scaffold provides drawers, Snackbar and many other cool built-in controls. The Scaffold component provides the basic Material Design visual layout structure and can be easily built with those components. The topBar property is used to display a title bar, and there’s also a floatingActionButton property which is used to create a float button, which happens to be what we need, And TopAppBar and ExtendedFloatingActionButton component is the official has been achieved good, attribute is also very simple, here used directly look at the effect;

Scaffold( topBar = { TopAppBar() {Text(text = "Compose") } }, floatingActionButton = { ExtendedFloatingActionButton( onClick = {}, icon = { Icon(Icons.Filled.Add, ContentDescription = "Add", tint = color.white)}, text = {text (text = "Add", Color = color.white)})},)Copy the code

Then we come to realize the content of the main body part, divided into the input box component and a list of components, because the overall longitudinal arrangement, here represented using the Column, let’s create the input box component TextField, and add the tooltip text and some color attribute, but also add a clear icon in the tail, to quickly remove content;

TextField( value = "", onValueChange = { }, placeholder = { Text(text = "Please input content") }, // trailingIcon = @composable {Image(imageVector = Icons.Filled.Clear,}, colors = TextFieldDefaults.textFieldColors( backgroundColor = Color.White, placeholderColor = Color.Black, textColor = MaterialTheme.colors.primary, cursorColor = MaterialTheme.colors.primary ), modifier = Modifier .fillMaxWidth() )Copy the code

This is because the content in the TextField is not automatically updated, so we need to handle it ourselves. This is different from the original ExitText, and we need to use the state in Compose. MutableStateOf creates an observable value, reassembles all composable functions that read that value when the value changes, and Remember is used to store that value, so we can take advantage of this feature to update the contents of the input field so that we can use it normally, And we can also use content judgment to decide whether to display the clear button, and click the button to clear the input content;

val content = remember { mutableStateOf<String>("") }
TextField(
    value = content.value,
    onValueChange = { content.value = it },
    placeholder = {
        Text(text = "Please input content")
    },
    if (content.value.isNotEmpty()) {
        Image(imageVector = Icons.Filled.Clear,
        contentDescription = null,
        modifier = Modifier.clickable {
            content.value = ""
    })
)
Copy the code

Next, let’s look at the list construction. Although we can use the verticalScroll() modifier to make Column scrolable, a large number of data will cause performance problems. Those familiar with RecyclerView should know that the internal optimization of Item is made. Compose also provides LazyColumn and LazyRow for list scrolling, as well as the items extension function for quickly adding element items. To dynamically change the collection, we use mutableStateListOf to observe the list collection.

data class Message(val title: String, val content: String) val messages = remember { mutableStateListOf<Message>() } LazyColumn { items(messages) { msg -> getItem(messages  = messages, msg = msg) } }Copy the code

So now we’re going to create the UI for the Item, and we’re going to make the whole thing horizontal, and then we’re going to make the title and the content vertical, and we’re going to make the Row component horizontal and the Image component, and the clip is going to make the Image a rectangle, The display is set with the contentScale property, similar to scaleType in XML, and the Spacer component is used to control spacing. You can also use padding.

Row(
    modifier = Modifier.fillMaxWidth() .padding(10.dp).clickable {}, 
    verticalAlignment = Alignment.Top     
) {
    Image(
        painter = painterResource(id = R.mipmap.jetpack_compose),
        contentDescription = null,
        modifier = Modifier.height(60.dp).width(80.dp).clip(RectangleShape)
           .border(1.dp, MaterialTheme.colors.primary, RectangleShape),
            contentScale = ContentScale.FillBounds
    )
    Spacer(modifier = Modifier.width(10.dp))
    Column() {
        Text(text = msg.title, fontWeight = FontWeight.W500,
            fontSize = 18.sp,color = Color.Black   
        )
        Text(text = msg.content, textAlign = TextAlign.Center,
             fontSize = 16.sp, softWrap = true, maxLines = 2,
        )
    }
}
Copy the code

Maybe we are used to dividing lines between items. Here we can add a vertical layout on the outermost layer and use the Divider component at the bottom to achieve the dividing effect.

Divider (modifier = modifier. Height (0.5. Dp), color = MaterialTheme. The colors. The primary)Copy the code

Finally, let’s look at how the data is displayed, remember the hover button, did you notice that onClick was used to implement the click event, so here we take the input and add it to the previous Messages observable,

OnClick = {messages.add(Message(" title ${messages.size + 1}", content.value))},Copy the code

Here and the final demand, click the Item to delete the current Item data, although not directly provide the click event layout, but we can with the help of powerful decorator Modifier, it also supports setting background, spacing, size, and shadow can quickly help you to modify or extend can combination, because we are also separate Item function to create, To update the list value, we can do this by passing a parameter;

@Composable
fun getItem(messages: SnapshotStateList<Message>, msg: Message) {}

Modifier.clickable {
        messages.remove(msg)
    }
}
Copy the code

Well, here we basically completed the beginning of the plan to achieve a small demand, although the overall is relatively simple, but the basic involved in layout, list, text, images and data processing and other basic components and logic, let’s look at the completed renderings;

conclusion

Jetpack Compose is compatible with all the existing code, and has built-in support for many components and animations. Come and experience it!