Video first

If you can easily watch videos, just go to Bilibili or YouTube.

Below is a transcript of the video. If you’ve seen the video, skip the script below and go straight to the bottom of the comments section.

Word version

Hi, I’m Zhu Kai from throwline.

Last year, I sent several videos about Compose, including the introduction of Compose and the principle, but there was no explanation of the actual code. I did have a sample lecture for my Compose course, but it was a three-hour lecture, and some people thought it was too long and tedious.I’m really…

So today, I’m going to give you a demo of the Compose code.

This is a fictional App I wrote for Compose called “watch”. “Fiction” means I made up the functions because I just wanted to create the interface and interaction. The interface looks complicated, but composing with Compose is extremely simple. Today, I’m going to walk you through the Compose interface code in the smoothest way possible and show you what the Compose interface code looks like.

function

I’ll start with a brief description of the software’s “features”. I have to put “features” in quotes because they’re fake.

At the top is some basic information, such as who the current user is, a notification button, and a search box.

And then in the middle is the spectator.

At the top you can choose who you want to watch — actually you can’t, I didn’t write that feature, so I won’t click on it; Down here is this guy you’re watching and he likes things, like you see this is watching me, right? That’s all I like. You can also click on one of the items to see the details.A preview image is at the top of the details page, and various details are at the bottom of the image.

The details page is then closed to reveal the subject’s recent movements.At the bottom of the page, there is a navigation bar to switch TAB pages, but I did not do the navigation function of this bar, just do the click effect.

There’s a simple, fictional example App called Onlookers. So let’s take a look at how it’s coded.

code

First, because the interface is composed, I’m going to delete setContentView() from the Compose interface and replace it with the Compose specific setContent {} :

setContentView(R.layout.activity_main) ... setContent { }Copy the code

Then you can write the content.

Since this is an up-down column layout, WITHOUT further ado, I made the navigation bar at the bottom. Use a Row() for a horizontal layout with four consecutive ICONS inside:

setContent {
  NavBar()
}

->
  
@Composable
private fun NavBar() {
  Row {
Icon(painterResource(r.rawable.icon1), "Icon ") Icon(painterResource(r.rawable.icon2), Icon(painterResource(r.rawable.icon3), "Icon ") Icon(painterResource(r.rawable.icon4)," Icon ")}}Copy the code

It’s ugly. It’s okay. I’ll adjust the ICONS. I pulled the ICONS into separate functions:

Adjust the size of the icon again:

Let’s rearrange their location:

All of a sudden it becomes neat.

Then I colour them again:

Well, it looks a lot better.

To make the ICONS look like clicks, I layer Button() around them:

Okay, so that completes the bottom column.

I’m not going to break down the details of the code in this video, but today I’m going to give you a quick demo and a quick feel for what Compose is like. To see the details of the project can go to see the source code, scan code plus my teaching assistant, let her send you on the line, code “onlookers”. Or watch my Compose course, which is also very detailed, you can also add a teaching assistant and ask the teaching assistant to send you.

Once the navigation bar is complete, you can do the above part. So before I do this, I’m going to use a Column() for vertical layout to separate the whole interface up and down. Below is the navigation bar, and above is what I’m going to write:

Column {
    
    NavBar()
}
Copy the code

Since the interior of this part is also vertically arranged, I set another layer for Column() :

Column {
Column { } NavBar() }Copy the code

And then I’m going to add fillMaxWidth() to fill it up laterally; Give it a weight() to fill the remaining space vertically, i.e. press the navigation bar to the bottom:

Column {
  Column(Modifier
.fillMaxWidth() .weight(1f) .background(Color(0xfffafafa))) { } NavBar() }Copy the code

Ok, now I’m going to fill in this big blank space up here.

Work from top to bottom, so the first step is the bar at the top for basic information.

I can start with a Row() to give a horizontal layout:

Column {
  Column(
Modifier .fillMaxWidth() .weight(1f) .background(Color(0xfffcfcfc)) ) { Row { } } NavBar() }Copy the code

Then fill it with a picture of a head in turn:

Column {
  Column(
Modifier .fillMaxWidth() .weight(1f) .background(Color(0xfffcfcfc)) ) { Row { Image( painterResource(id = R.davable.avatar_rengwuxian), "avatar ",)}} NavBar()}Copy the code

A Column() enclosing two columns vertically:

Column {
Column( Modifier .fillMaxWidth() .weight(1f) .background(Color(0xfffcfcfc)) ) { Row { Image( painterResource(id = R.david.avatar_rengwuxian), "avatar ",) Column {Text(" Welcome back!" ) Text(" xiao Zhu ")}}} NavBar()}Copy the code

And a little bell:

Column {
Column( Modifier .fillMaxWidth() .weight(1f) .background(Color(0xfffcfcfc)) ) { Row { Image( painterResource(id = R.davable.avatar_rengwuxian), "avatar ",)} Column {Text(" Welcome back!" Image(painterResource(r.david.notification_new), "notification_new ")} NavBar()}Copy the code

Now that everything is displayed, let’s style it.

Make the head with rounded corners and adjust the size:

Image(
PainterResource (id = R.rawable.avatar_rengwuxian), "avatar ", Modifier. Size (64.dp).Clip (CircleShape))Copy the code

The colors and styles of the text are also polished:

Text(" Welcome back! , fontSize = 14.sp, color = color (0xffb4b4)) Text("小 小 ", fontSize = 18.sp, fontWeight = fontweight.bold)Copy the code

Make the little bell smaller:

Image(
PainterResource (r.david.notification_new), "Notice ", Modifier. Size (32.dp))Copy the code

Add a rounded base:

Surface(
Modifier. Clip (CircleShape), color = color (0xffFeF7f0)) {Image(painterResource(r.david.notification_new), "Notice ", Modifier.padding(10.dp).size(32.dp) ) }Copy the code

Once you’ve styled them, adjust their layout:

Row(
Modifier.fillMaxWidth(), VerticalAlignment = Alignment. CenterVertically) {Image (painterResource (id = R.d rawable. Avatar_rengwuxian), "head", Padding-bottom (start = 14.dp).weight(1f)) {padding-bottom (start = 14.dp).weight(1f)) {padding-bottom (start = 14.dp) , fontSize = 14.sp, color = color (0xffb4b4b4)) Spacer(modifier = modifier.height (6.dp)) Text(" xiao Zhu ", fontSize = 18.sp, fontWeight = FontWeight.Bold) } Surface( Modifier.clip(CircleShape), Color = color (0xfffef7f0)) {Image(painterResource(r.david.notification_new), "notification ", Modifier .padding(10.dp) .size(32.dp) ) } }Copy the code

Then add a padding() to the entire Row() :

Row(
Modifier .fillMaxWidth() .padding(28.dp, 36.dp, 28.dp, 16.dp), verticalAlignment = Alignment.CenterVertically )Copy the code

Ok, this top bar is done. I pulled its code into a separate function as well, cleanly:

Column {
Column( Modifier .fillMaxWidth() .weight(1f) .background(Color(0xfffcfcfc)) ) { TopBar() } NavBar() }Down below is a search bar: Column(...) {TopBar() SearchBar() }
->

@Composable
fun SearchBar() {

}
Copy the code

The same way, there’s basically nothing to say:

@Composable
fun SearchBar() {
  Row(
Modifier .padding(24.dp, 12.dp) .fillMaxWidth() .height(56.dp) .clip(RoundedCornerShape(28.dp)) .background(Color.White), verticalAlignment = Alignment.CenterVertically ) { var searchText by remember { mutableStateOf("") } BasicTextField( searchText, { searchText = it }, Modifier .padding(start = 24.dp) .weight(1f), TextStyle = textStyle (fontSize = 16.sp)) {if (searchText.isempty ()) {textStyle (" searchText.isempty "); , color = Color(0xffb4b4b4), fontSize = 16.sp) } it() } Box( Modifier .padding(6.dp) .fillMaxHeight() .aspectRatio(1f) .clip(CircleShape) .background(Color(0xffFA9e51))) {Icon(painterResource(r.drawable.ic_search), "search ", Modifier .size(24.dp) .align(Alignment.Center), tint = Color.White ) } } }Copy the code

Just one thing: The prompt is written differently than the traditional EditText(), and I wrote it myself:

This may seem cumbersome, but it adds a lot of flexibility. Like I can add to it the correct icon, add a help text, text, directly, and format completely is not restricted, and in traditional EditText can only rely on a custom View to implement these functions. Compose provides a simpler way to write the Compose prompt: enter a string label parameter for Compose:

TextField(
value = text, onValueChange = { text = it }, label = { Text("Label") } )Copy the code

It’s just that this is written in Material Design style, and I want to customize it completely, so I don’t use it.

Further down the search bar is the core area, which is the information about the person being watched. — Remember I said this is an App called Onlookers? It’s fictional.

At the top of the viewing area is a list of people’s names, which is very simple, the horizontal layout is wrapped with some text, among which the selected text has a round head at the bottom of the bar:

TopBar()
SearchBar()
NamesBar()

...

@Composable
fun NamesBar() {
Val names = listOf (" throw line ", "chu hoi", "laofeng", "Hao Ge", "zhang", } LazyRow(Modifier. Padding (0.dp, 8.dp), contentPadding = PaddingValues(12.dp, 0.dp)) { itemsIndexed(names) { index, name -> Column(Modifier.padding(16.dp, 4.dp).width(IntrinsicSize.Max)) { Text(name, color = if (index == selected) Color(0xfffa9e51) else Color(0xffb4b4b4)) if (index == selected) { Box( Modifier.fillMaxWidth().height(2.dp).clip(RoundedCornerShape(1.dp)) .background(Color(0xfffa9e51)) ) } } } } }Copy the code

The only thing to be said here is that since the contents of the list are slidable, you need LazyRow() instead of Row(). The object of LazyRow() is the RecyclerView of the traditional script, but it is simpler to write, without Adapter or ViewHolder, to loop through each item and then write the layout of each item directly.

Compose will load dynamically, like RecyclerView, for example, because Compose doesn’t compute each item in the same order as the code itself, so it doesn’t explode.

Further down the list of names, there’s an area called ‘loved’, which is what this person likes. So first, a title bar:

TopBar()
SearchBar()
NamesBar()
LovesArea()

...

@Composable
fun LovesArea() {
  Row(
Modifier.padding(24.dp, 12.dp, 24.dp).fillMaxWidth()) {Modifier.padding(24.dp, 12.dp, 24.dp).fillMaxWidth()); FontWeight = fontweight.bold) Spacer(Modifier.weight(1f)) Text(" check all ", fontSize = 15.sp, color = color (0xffB4b4b4))}}Copy the code

Well, below the title bar is a horizontal list of “loves” :

@Composable
fun LovesArea() {
  Column {
Row(Modifier.padding(24.dp, 12.dp, 24.dp).fillMaxWidth()) {Modifier ("TA love ", fontSize = 16.sp, FontWeight = fontweight.bold) Spacer(Modifier.weight(1f)) Text(" Check all ", fontSize = 15.sp, Color = color (0xffb4b4b4))}Copy the code

Since it’s a list, I use LazyRow() instead of Row() :

LazyRow {
}
Copy the code

The contents are filled in the same way, and then it looks like this:

Then I use the horizontalArrangement parameter of LazyRow() to separate the elements from each other:

LazyRow(horizontalArrangement = Arrangement.spacedBy(24.dp)) {
. }Copy the code

Use contentPadding to add a bit of space on both ends:

LazyRow(horizontalArrangement = Arrangement.spacedBy(24.dp),
contentPadding = PaddingValues(24.dp, 0.dp)) { ... }Copy the code

The contentPadding, which differs from the padding() in that the content is not cut off at both ends when sliding, but instead touches the edge of the sliding area:

Then the “loved” part is done. Doesn’t look bad, does it?

Everything else is written this way, like the “He/She has been” section further down:

And the new page here when the item is clicked:

So I’m not going to go over the layout code. Let’s focus on this on/off animation:

Animation is divided into a variety of, this essentially belongs to the interface jump transition animation. The most traditional way to do this animation is with the Transition API provided by Android. The Transition API doesn’t work as well. If it doesn’t involve activities or fragments, you can use Jetpack’s MotionLayout. For example, the Compose animation API is used to create the transition effect. For example, the Compose animation API is used to create the transition effect. The principle is simple: the detail page is set to four states: closed, opened, open and closed, through which the value of each property in the detail page – such as the size and offset of each component – is gradually manipulated to achieve the display of the detail page and the animation of the closing action.

Pretty good, huh? In fact, if you are not composing, or if you are not using Compose in your company’s project and are using a traditional View system, this effect can be achieved. You have two options: custom View, or MotionLayout. The custom View is a bit trickier, the MotionLayout is a lot simpler, and the performance of Compose is not necessarily worse — it might even be better for now, I’m not sure, because Compose is theoretically better than the View system. This includes MotionLayout, but it’s new, so I’m not sure if the details of each part of it are up to date.

For my information, Compose is also doing more work on animation, including making it easier to create transition animations. It’s not too much trouble to write the entire animation process by hand as I do, but it would certainly be better if there was an official API that made it easier. Who isn’t a little slacker?

For the detailed code of this part of animation, you can go to my source code, add my teaching assistant and ask her to send it to you. Moreover, the official version of my Compose course will follow the official steps to expand the content related to animation.

At the end

That’s all for today. Give me a thumbs up if you liked it. Your support means a lot to me. Compose, as well as the MotionLayout I mentioned earlier, has a lot more to say about various Android developments, which I’ll be sharing more about in the future. Follow me and don’t miss any of my new content. I’m a throwline. I don’t compete with you. I just help you grow.