Jetpack Compose tutorial written for beginners, basic controls and layout

This article is simultaneously published on my WeChat official account. You can follow by scanning the QR code at the bottom of the article or searching for Guo Lin on WeChat. Articles are updated every working day.

Hello everyone, the Jetpack Compose tutorial written for beginners has been updated again.

To be precise, this is the first article in this series. Because the last article is just a prologue, let’s talk to you about why we need to learn Compose. If you still have this doubt, you can go to the Jetpack Compose tutorial written for beginners in the previous article. Why learn Compose?

Compose has a huge knowledge system, so I may write many articles in this series of tutorials. Of course, I am not a Compose master, and I am also a beginner at present. In essence, this tutorial is to share these learning records with you while I am learning Compose by myself. I hope you can follow me to learn with zero foundation.

Compose is a new declarative UI framework for replacing Android View. Since it is a UI framework, our first article will talk about basic controls and layout knowledge.

Create Compose project

My articles are usually more hands-on, I hope everyone can follow me to write code.

To use Compose to write UI interfaces, you first need to introduce Compose-related dependency libraries. However, there are many dependencies related to Compose, and they may change frequently. After all, it is still a very new UI framework, so here I will teach you the easiest way to introduce Compose dependencies.

Open Android Studio, create a new project called ComposeTest, and select Compose Empty Activity, as shown below:

insert image description here

In this way, Android Studio will create a project with a Compose development environment for us, and naturally add all the necessary dependency libraries, as shown below:

dependencies {
    
    
    implementation 'androidx.core:core-ktx:1.7.0'
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
    implementation 'androidx.activity:activity-compose:1.3.1'
    implementation "androidx.compose.ui:ui:$compose_ui_version"
    implementation "androidx.compose.ui:ui-tooling-preview:$compose_ui_version"
    implementation 'androidx.compose.material:material:1.2.0'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
    androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_ui_version"
    debugImplementation "androidx.compose.ui:ui-tooling:$compose_ui_version"
    debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_ui_version"
}

Next, take a look at the automatically generated content in MainActivity:

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

@Composable
fun Greeting(name: String) {
    
    
    Text(text = "Hello $name!")
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    
    
    ComposeTestTheme {
    
    
        Greeting("Android")
    }
}

The automatically generated code here involves some basic knowledge of Compose. We have to understand these basic knowledge clearly before we can start the follow-up study of Compose.

First look at the code in the onCreate function, where a setContent function is called. Note that this name is very particular, because every Android developer will be very familiar with the name of another function: setContentView. Therefore, when we used View to write the interface in the past, we would call the setContentView function to set the interface. After using Compose, since View is no longer used, this new setContent function is used to set the interface instead.

The setContent function will provide a Composable scope, so we can call the Composable function at will in its closure.

So what is a Composable function? To put it simply, it is above a function, declared using @Composable, then it is a Composable function.

Looking at the above code, you will find that the Greeting function is a Composable function.

Note that Composable functions can only be called in Composable scope, so if you try to call Greeting function outside the closure of setContent function, you will find that compilation will not pass.

In addition, all Composable functions also have a customary habit, that is, the first letter of the function name needs to be capitalized. This is very different from Java's function naming habits, because Java function names usually use the camel case with the first letter lowercase.

But it is precisely because of this that the first letter of the Composable function name is specifically required to be capitalized, so that we can quickly judge whether a function is a Composable function through the function name more intuitively, otherwise we need to find the definition location of this function. Check to see if it has @Composable annotations.

Then according to this rule, we can know that the functions ComposeTestTheme and Surface nested in the setContent function are both Composable functions.

Among them, the ComposeTestTheme function is automatically created for us by Android Studio and is mainly used to set and customize the theme of the project. We may discuss this topic in a later article.

The Surface function is a general-purpose function provided in the Material library. Its main function is to make the application better adapt to Material Design, such as controlling the height of the shadow, controlling the color of the content, cropping the shape, and so on. Since we are still beginners, there is no need to know too much about this Surface function for the time being, so I won't explain too much here.

In addition, you will find that Android Studio also generated a DefaultPreview function for us. It is also a Composable function, but it has one more @Preview annotation than ordinary Composable functions. This annotation indicates that this function is used to quickly preview the UI style.

In other words, the UI written in the DefaultPreview function can realize quick preview without running the program on the mobile phone. The operation method is to click the Split option in the upper right corner on the main editing interface of Android Studio, and then compile the project, as shown in the following figure:

insert image description here

However, I personally think that this quick preview function is not very easy to use, especially in the early days when there were many bugs. So if your project is not very big and it doesn't take long to compile once, then I think it may be a better choice to run it directly on the phone to check the effect.

So now let's run the project to the mobile phone and have a look:

insert image description here

This is what the code automatically created by default achieves, a very simple Hello Android.

The initial code has been explained quite clearly, let us start to learn some basic controls and layout knowledge.

basic control

I believe you must know that there are countless controls in View that we can use. So does Compose.

Therefore, if you want to master all the control knowledge of Compose through an article, it is obviously impossible.

Therefore, here we will only learn the common usage of some basic controls in Compose, aiming to help you get started quickly. After getting started, you can quickly master more complicated usages by searching or asking ChatGPT.

1. Text

Text is undoubtedly the most commonly used control in Compose. It is mainly used to display a piece of text, corresponding to TextView in View.

In fact, the Text control has already been used in the Greeting function that was automatically generated just now, so we can see the words Hello Android on the interface.

The usage of Text is very simple, you only need to specify a text parameter to it, and pass in the content to be displayed.

Modify the code in MainActivity as follows:

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

@Composable
fun SimpleWidgetColumn() {
    
    
    Column {
    
    
        Text(text = "This is Text")
    }
}

As you can see, here we define a Composable function called SimpleWidgetColumn, and then replace the Greeting function called in the Surface function with this, then the final interface will display the content we wrote in the SimpleWidgetColumn function.

Then in the SimpleWidgetColumn function, we first define a Column. This Column will be explained in detail later. For now, you only need to know that it is used to display a column of content vertically, and its function type is LinearLayout in View.

Next, in the Column, we added a Text, and specified that the displayed content is "This is Text" through the text parameter.

Now run the program, the result is shown in the figure below.

insert image description here

So congratulations, the first control of our Compose journey, you have mastered its most basic usage.

However, it can only display a paragraph of text. Obviously, the function is too monotonous. In fact, Text provides a very rich API to allow us to customize the displayed content. Then the most common thing is to change the color and size of the text, we can do it with the following code:

@Composable
fun SimpleWidgetColumn() {
    
    
    Column {
    
    
        Text(
            text = "This is Text",
            color = Color.Blue,
            fontSize = 26.sp
        )
    }
}

Run the code again, the result is as shown in the following figure:

insert image description here

In addition, Text can also achieve many other functions, which can be skipped by observing its parameter list:

fun Text(
    text: String,
    modifier: Modifier = Modifier,
    color: Color = Color.Unspecified,
    fontSize: TextUnit = TextUnit.Unspecified,
    fontStyle: FontStyle? = null,
    fontWeight: FontWeight? = null,
    fontFamily: FontFamily? = null,
    letterSpacing: TextUnit = TextUnit.Unspecified,
    textDecoration: TextDecoration? = null,
    textAlign: TextAlign? = null,
    lineHeight: TextUnit = TextUnit.Unspecified,
    overflow: TextOverflow = TextOverflow.Clip,
    softWrap: Boolean = true,
    maxLines: Int = Int.MAX_VALUE,
    onTextLayout: (TextLayoutResult) -> Unit = {
    
    },
    style: TextStyle = LocalTextStyle.current
) {
    
    
    ...
}

As I said just now, here we only talk about some common usages to help you get started quickly, and more rich usages are waiting for you to explore by yourself.

2. Button

Button should be the most commonly used control after Text. I believe that everyone knows what it is used for without my introduction, because it has exactly the same name as the Button in View.

The difference is that in View, Button is a subclass of TextView, and there is an inheritance relationship between them. Therefore, Button is actually a functional extension based on TextView, making the control clickable.

In Compose, there is no relationship between Button and Text. They are two separate controls, and usually they need to be used together.

So let's try to add Button first, the code is as follows:

fun SimpleWidgetColumn() {
    
    
    Column {
    
    
        ...
        
        Button(onClick = {
    
     /*TODO*/ }) {
    
    
            
        }
    }
}

Run the program, and you will see the result as shown in the figure below:

insert image description here

Although the button has been displayed, but there is no text on it.

And if you still follow the thinking in View and try to set a text attribute for Button to specify the text content, you will find that Button in Compose does not have this attribute at all.

As I just said, there is no inheritance relationship between Button and Text in Compose, so it does not inherit various capabilities from Text.

So how can we specify text content for Button? As I mentioned just now, it can be used with Text.

Modify the code in the SimpleWidgetColumn function as follows:

@Composable
fun SimpleWidgetColumn() {
    
    
    Column {
    
    
        ...
        
        Button(onClick = {
    
     /*TODO*/ }) {
    
    
            Text(
                text = "This is Button",
                color = Color.White,
                fontSize = 26.sp
            )
        }
    }
}

Here we put the Text in the closure of the Button, so that we can specify the displayed content for the Button. Of course, it is not necessary to use Text here, you can also write other controls here.

Now run the program again, the effect is as shown in the figure below:

insert image description here

In addition, we noticed that there is also an onClick parameter on the Button parameter list, which is a required parameter. When the button is clicked, the logic specified in this parameter will be executed.

In order to demonstrate this function, let's make a Toast prompt pop up when the button is clicked.

Modify the code in the SimpleWidgetColumn function as follows:

@Composable
fun SimpleWidgetColumn() {
    
    
    Column {
    
    
        ...
        
        val context = LocalContext.current
        Button(onClick = {
    
    
            Toast.makeText(context, "This is Toast", Toast.LENGTH_SHORT).show()
        }) {
    
    
            Text(
                text = "This is Button",
                color = Color.White,
                fontSize = 26.sp
            )
        }
    }
}

Note that in order to pop up Toast, you need a Context parameter. To get the Context object in the Composable function, you can call LocalContext.current to get it.

Now when the Button is clicked, the effect is as shown in the figure below:

insert image description here

3. TextField

TextField corresponds to EditText in View, which is an input box, so it is also a very commonly used control.

However, TextField is not as easy to use as EditText. Its usage design fully fits the idea of ​​​​declarative UI, and we still know very little about this idea, so you may find this control difficult to use for the time being.

First, let's try to add a TextField to the interface, the code is as follows:

@Composable
fun SimpleWidgetColumn() {
    
    
    Column {
    
    
        ...

        TextField(value = "", onValueChange = {
    
    })
    }
}

There are two required parameters on the TextField parameter list, among which the value parameter is used to specify the text content displayed in the current input box, and the onValueChange parameter is used to monitor the change of the text content in the input box.

Now run the program, the effect is as shown in the figure below:

insert image description here

As you can see, the input box has been displayed.

It's relatively simple so far, but when you try to enter something in the input box, you will find that no matter what you type on the keyboard, the input box will not be displayed.

This is the biggest difference from EditText, because EditText must be able to display the content you input.

So why can't TextField display the entered content? This requires a review of the declarative UI programming ideas we mentioned in the previous article. For those who haven’t read it, please refer to the Jetpack Compose tutorial written for beginners. Why learn Compose?

The declarative UI workflow is a bit like refreshing a web page. That is, when we describe a control, we need to attach its state. Then when any state needs to be changed, you only need to refresh the elements on the interface like refreshing a web page, and then the natural state can be updated.

The content displayed in TextField is a state, because with your input, the content displayed on the interface also needs to be updated accordingly.

So here, when entering content in the TextField, first of all, we did not do the operation of refreshing the page. Secondly, even if the refresh operation is performed, after the TextField is refreshed, it is found that the content specified by the value parameter is still an empty string, so the content we input still cannot be displayed on the screen.

Now that the cause of the problem has been explained clearly, how can we solve it? This requires the help of Compose's State component. But this is another knowledge point. I plan to explain it in a later article. I don’t want to be too divergent in this article. For the time being, let’s focus on the basic controls and layout, so let’s skip this question first.

TextField also provides a very rich API to allow us to customize it. For example, EditText has a hint attribute, which is used to display some prompt text in the input box, and then once the user enters any content, these prompt text will disappear. So how does TextField achieve similar functionality? The code looks like this:

@Composable
fun SimpleWidgetColumn() {
    
    
    Column {
    
    
        ...

        TextField(
            value = "",
            onValueChange = {
    
    },
            placeholder = {
    
    
                Text(text = "Type something here")
            }
        )
    }
}

Here, a placeholder is specified by the placeholder parameter, which is actually similar to the hint function. When the user does not enter any content in the input box, the content in the placeholder is displayed. Once the user enters any content, the placeholder will disappear. The basic effect is shown in the figure below:

insert image description here

In addition, you may think that the default background color of the TextField input box is too ugly. We can easily adjust the background color of the TextField input box with the following code:

@Composable
fun SimpleWidgetColumn() {
    
    
    Column {
    
    
        ...

        TextField(
            value = "",
            onValueChange = {
    
    },
            placeholder = {
    
    
                Text(text = "Type something here")
            },
            colors = TextFieldDefaults.textFieldColors(
                backgroundColor = Color.White
            )
        )
    }
}

This TextFieldDefaults can support adjusting any color you want to adjust, not just the background color of the input box. For specific usage, refer to its parameter list and you will know.

Now run the program again, the effect is as shown in the figure below:

insert image description here

4. Image

Image corresponds to ImageView in View, which is used to display pictures, so there is no doubt that this is definitely another common control that must be mastered.

The good news is that Image usage is fairly simple, so we should be up and running in no time.

As we all know, pictures in Android usually have two common forms, one is a drawable resource, and the other is a bitmap object. Image provides support for both types of images.

First, let's take a look at how to load a picture in the form of a drawable resource. The code is as follows:

@Composable
fun SimpleWidgetColumn() {
    
    
    Column {
    
    
        ...

        Image(
            painter = painterResource(id = R.drawable.dog),
            contentDescription = "A dog image"
        )
    }
}

There are two required parameters on the Image parameter list, where the painter parameter is used to specify the drawable resource to be displayed, and the contentDescription parameter is used to specify the text description for this resource.

This text description is mainly to provide pronunciation assistance for groups with visual impairments in the accessibility mode. There is also type functionality on ImageView, but only as an optional property. When it comes to Compose's Image, it becomes a mandatory parameter.

Of course, if you just don't want to specify contentDescription for the picture, you can also pass null directly.

Now run the program, the effect is as shown in the figure below:

insert image description here

How about it, is it very simple to use?

Let's take a look at how to load a picture in the form of a bitmap:

@Composable
fun SimpleWidgetColumn() {
    
    
    Column {
    
    
        ...

        val bitmap: ImageBitmap = ImageBitmap.imageResource(id = R.drawable.dog)
        Image(
            bitmap = bitmap,
            contentDescription = "A dog image"
        )
    }
}

The code is also very simple. Here we first use the ImageBitmap.imageResource function to convert the drawable resource into an ImageBitmap object, and then transfer it to the Image control.

It should be noted that what Image receives is the proprietary ImageBitmap object in Compose, not the traditional Bitmap object. If what you want to pass in here is a traditional Bitmap object, then you have to additionally call the asImageBitmap function to convert it, as shown below:

@Composable
fun SimpleWidgetColumn(bitmap: Bitmap) {
    
    
    Column {
    
    
        ...

        Image(
            bitmap = bitmap.asImageBitmap(),
            contentDescription = "A dog image"
        )
    }
}

So far, we have shown only local image resources. So what if you want to display a web image resource?

Unfortunately, the image provided by Compose does not have this capability, and we need to rely on third-party dependent libraries. Of course, ImageView does not have this ability, so we used to use third-party libraries like Glide.

At present, the third-party Compose image loading libraries recommended by Google are Coil and Glide. I know that everyone will feel very kind when they see Glide, and they may be more inclined to use this. But in fact, Coil is a new image loading library developed based on coroutines. Its usage is more suitable for Kotlin and simpler, so I recommend using this new library.

To use Coil, we first need to introduce it into our project:

dependencies {
    
    
    implementation("io.coil-kt:coil-compose:2.4.0")
}

Next, use the AsyncImage control provided by Coil to easily load network image resources. The code is as follows:

@Composable
fun SimpleWidgetColumn() {
    
    
    Column {
    
    
        ...

        AsyncImage(
            model = "https://img-blog.csdnimg.cn/20200401094829557.jpg",
            contentDescription = "First line of code"
        )
    }
}

Re-run the program to see the effect, but before that, don't forget to declare the network permissions in the AndroidManifest.xml file.

insert image description here

5. ProgressIndicator

ProgressIndicator corresponds to the ProgressBar in the View, which is used to display the progress bar, which is also one of the core basic controls.

We all know that there are two common forms of ProgressBar in View, namely circular ProgressBar and long strip ProgressBar. ProgressIndicator also has these two forms, and the corresponding controls are CircularProgressIndicator and LinearProgressIndicator, let's learn one by one.

First look at CircularProgressIndicator, its usage is very simple, as follows:

@Composable
fun SimpleWidgetColumn() {
    
    
    Column {
    
    
        ...

        CircularProgressIndicator()
    }
}

Just place a CircularProgressIndicator control, we don't even need to specify any parameters.

Now run the program, the effect is as shown in the figure below:

insert image description here

In addition to the default effect, we can also easily customize the style of the progress bar. For example, the color and line thickness of the progress bar can be modified by the following code:

@Composable
fun SimpleWidgetColumn() {
    
    
    Column {
    
    
        ...

        CircularProgressIndicator(
            color = Color.Green,
            strokeWidth = 6.dp
        )
    }
}

Run the program again, the effect is as shown in the figure below:

insert image description here

Next, let's take a look at the usage of LinearProgressIndicator, which is also very simple:

@Composable
fun SimpleWidgetColumn() {
    
    
    Column {
    
    
        ...

        LinearProgressIndicator()
    }
}

Similarly, we only need to place a LinearProgressIndicator control, and the running effect is shown in the figure below:

insert image description here

As you can see, what is displayed now is a horizontal progress bar in the horizontal direction.

Then everyone must have thought of it, we can use a similar method to customize the style of LinearProgressIndicator:

@Composable
fun SimpleWidgetColumn() {
    
    
    Column {
    
    
        ...

        LinearProgressIndicator(
            color = Color.Blue,
            backgroundColor = Color.Gray
        )
    }
}

Here I specify the foreground color and background color of the horizontal progress bar respectively. Let's experience the effect:

insert image description here

So far, I think the most common controls in Compose have been introduced.

Of course, the UI-related controls are endless, and there will be countless custom controls in the future. But as long as you have a basic understanding of Compose's control knowledge, you can quickly grasp more content by consulting the information when you use it.

Basic layout

Next we start to learn the basic layout.

Compared with View, the layout of Compose is actually much simpler. In the View system, the various layouts are really dizzying and difficult to learn.

Although Compose also has many layouts, there are only three core ones, Column, Row and Box. Look at the following diagram:

insert image description here

Column is to arrange the controls vertically, which we have just experienced. Row is to arrange the controls horizontally. Column and Row correspond to LinearLayout in View.

The Box corresponds to the FrameLayout in the View, so if you explain it like this, everyone should understand it well.

Then some friends may wonder, why are there only these few core layouts of Compose? RelativeLayout and ConstraintLayout are also commonly used. Doesn't Compose have a corresponding layout?

This is because the design and performance of Compose are excellent, and these core layouts alone can basically satisfy us to write various complex UI interfaces.

In fact, there is also ConstraintLayout in Compose, but I don't recommend it very much. The reason is that we used ConstraintLayout in View before, mainly because the performance of View will drop sharply when the layout is too deep, while ConstraintLayout can use one layer of layout nesting to complete complex interface writing, which is Its greatest value lies.

However, Compose does not have this problem at all. Using Compose to write interfaces, you can nest layouts at any depth, and the performance will not be affected at all.

It is also for this reason that ConstraintLayout in Compose does not have much advantage. After all, the layout written using Column and Row is better in terms of readability.

So let's start learning one by one.

1. Column

In fact, we have been using Column when explaining basic controls, because all controls are placed in Column.

The controls in the Column will be arranged vertically from top to bottom, and the effect is what we just saw.

In this case, what else can Column say? In fact, Column has a lot of customizable content.

First of all, you will find that all the controls in the Column are currently aligned to the left, so is there a way for us to align them to the center?

The code is actually very simple, as follows:

@Composable
fun SimpleWidgetColumn() {
    
    
    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
    
    
        ...
    }
}

Here we add two parameters to Column.

The modifier parameter is a very soul component in Compose. All Compose controls need to rely on it. Our next article will discuss Modifier in detail. At present, you only need to know that after calling the fillMaxSize() function, the size of the Column can be filled with the parent layout.

The horizontalAlignment parameter can specify the horizontal alignment of the sub-controls in the Column, CenterHorizontally means center alignment, and you can also choose Start and End.

Run the program again, and you can see that all controls are now centered:

Figure 19

Then some friends may say, what if my need is that the alignment of each sub-control in Column is different? Of course, no problem, Compose provides very high customizability, we only need to override the alignment in the modifier parameter of the corresponding sub-control:

@Composable
fun SimpleWidgetColumn() {
    
    
    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
    
    
        Text(
            modifier = Modifier.align(Alignment.End),
            text = "This is Text",
            color = Color.Blue,
            fontSize = 26.sp
        )

        ...
    }
}

In this case, only the first Text control will become right-aligned, and the rest of the other controls will remain center-aligned, as shown in the following figure:

insert image description here

In addition to specifying the alignment of sub-controls in the horizontal direction, we can also specify the distribution of sub-controls in the vertical direction. What does this mean? Let's look at the following code:

@Composable
fun SimpleWidgetColumn() {
    
    
    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.SpaceEvenly
    ) {
    
    
        ...
    }
}

Here we specify a verticalArrangement parameter and set it to SpaceEvenly. SpaceEvenly means that each sub-control in the Column equally divides the vertical space of the Column.

It is easy to understand the effect after running it, as shown in the figure below:

insert image description here

This may be a refreshing function, because there is no similar function in LinearLayout, or LinearLayout needs to use the layout_weight parameter to achieve the same effect.

However, the distribution methods that can be specified by the verticalArrangement parameter are very rich, and it is quite difficult for LinearLayout to perform a completely similar simulation. Since there are many distribution methods that can be specified, I can't show you one by one here, so let's take a look at an official Google animation example to quickly understand the effect of each distribution method:

insert image description here

Well, that's all about Column, and then we start to look at Row.

2. Row

It is quite simple to look at Row after you have mastered Column, because they are basically the same thing, but the direction is different.

First of all, we simply replace Column with Row without adding any parameters. The code is as follows:

@Composable
fun SimpleWidgetColumn() {
    
    
    Row {
    
    
        Text(
            text = "This is Text",
            color = Color.Blue,
            fontSize = 26.sp
        )

        ...
    }
}

Run the program, the effect is as shown in the figure below:

insert image description here

I know this effect is ugly, but it's as expected. Because the controls in the Row are indeed arranged in the horizontal direction, it can only be displayed in this way because the space cannot accommodate so many controls.

But this does not affect us to specify other parameters. For example, if we want to center these sub-controls in the vertical direction, we can write like this:

@Composable
fun SimpleWidgetColumn() {
    
    
    Row(
        modifier = Modifier.fillMaxSize(),
        verticalAlignment = Alignment.CenterVertically,
    ) {
    
    
        ...
    }
}

You will find that the parameters that can be specified in Column and Row are very reasonable.

Because Column is a vertical layout, it can only specify the alignment in the horizontal direction, so the horizontalAlignment parameter is provided.

Conversely, because Row is a horizontal layout, it can only specify the alignment in the vertical direction, so the verticalAlignment parameter is provided.

Now run the program again, the effect is as shown in the figure below:

insert image description here

Similarly, because Column is a vertical layout, it can only specify the distribution method in the vertical direction, so the verticalArrangement parameter is provided.

Then you must have guessed that because Row is a horizontal layout, it can only specify the distribution method in the horizontal direction, so a horizontalArrangement parameter must be provided.

The truth is correct, but unfortunately I can't demonstrate it here, because the content arranged in the horizontal direction has exceeded the displayable size of Row. In the case that the layout can no longer hold the content of the sub-controls, it is meaningless to specify its distribution method, and it will have no effect.

Regarding the distribution methods that the horizontalArrangement parameter can specify and their effects, I think it will be more intuitive to look at this picture directly:

insert image description here

In this way, we have learned all the functions in Row in an equivalent way to Column. But just because the content in Row cannot be displayed, I would like to take this opportunity to talk about how to allow users to scroll to view content beyond the screen.

First of all, if you are looking for a replacement for RecyclerView or ListView in Compose, unfortunately, this is not within the scope of our article today. I will write a special article to explain this part.

In the situation we are currently encountering, in the View, you can usually nest a ScrollView layout outside the content that needs to be scrolled, so that the content in the ScrollView can be scrolled.

However, Compose does not need additional layout nesting, it only needs to use the modifier parameter, the code is as follows:

@Composable
fun SimpleWidgetColumn() {
    
    
    Row(
        modifier = Modifier
            .fillMaxSize()
            .horizontalScroll(rememberScrollState()),
        verticalAlignment = Alignment.CenterVertically,
    ) {
    
    
        ...
    }
}

Here we connect a horizontalScroll function above the modifier parameter. This function has a ScrollState parameter which is a required parameter. It is used to ensure that the scroll position will not be lost when the mobile phone is rotated horizontally or vertically. Usually, the rememberScrollState function can be called get.

The remember series of functions is a very important part of Compose, and I will introduce them in detail in subsequent articles.

Now run the program again, the effect is as shown in the figure below:

insert image description here

When the content in the Column cannot be displayed, the way to scroll the Column is similar. You only need to change the horizontalScroll to verticalScroll, and I won’t demonstrate it here.

3. Box

Box corresponds to FrameLayout in View, which does not have rich positioning methods, and all controls will be placed in the upper left corner of the layout by default. Let's take a look with an example:

@Composable
fun SimpleWidgetColumn() {
    
    
    Box {
    
    
        ...
    }
}

Here we change the outermost layout to Box, and run the program again, the effect is shown in the figure below:

insert image description here

It can be seen that all sub-controls appear in the upper left corner of the layout, and the controls added later will be pressed on top of the controls added earlier.

Of course, in addition to this default effect, we can also specify the alignment of the control in the layout by modifying the modifier parameter of the child control, which is similar to the usage in Column.

Modify the code in the SimpleWidgetColumn function as follows:

@Composable
fun SimpleWidgetColumn() {
    
    
    Box(modifier = Modifier.fillMaxSize()) {
    
    
        Text(
            modifier = Modifier.align(Alignment.TopStart),
            ...
        )

        Button(
            modifier = Modifier.align(Alignment.TopEnd),
            ...
        })

        TextField(
            modifier = Modifier.align(Alignment.CenterStart),
            ...
        )

        Image(
            modifier = Modifier.align(Alignment.CenterEnd),
            ...
        )

        CircularProgressIndicator(
            modifier = Modifier.align(Alignment.BottomStart),
            ...
        )

        LinearProgressIndicator(
            modifier = Modifier.align(Alignment.BottomEnd),
            ...
        )
    }
}

First, we fill the parent layout by specifying Modifier.fillMaxSize() to the Box, and then add a modifier parameter to each child control, and specify their respective alignments through Modifier.align, so that they will not overlap together.

Now run the program again, the effect is as shown in the figure below:

insert image description here

4. ConstraintLayout

In fact, as I said just now, ConstraintLayout is not so commonly used in Compose, because its biggest advantage is that nesting single-layer layout in Compose is not an advantage.

But it is not ruled out that some friends like ConstraintLayout because they simply like its writing style rather than performance. For these friends, I can only say that Google has fully considered you and provided a Compose version of ConstraintLayout with similar usage.

But I don't plan to explain this part of the content. If you are a friend who is fond of ConstraintLayout, please refer to the following official documents to learn:

https://developer.android.google.cn/jetpack/compose/layouts/constraintlayout

Summarize

This article is almost over here. If you have never touched Compose before, after reading this article, I believe you can already write some simple interfaces.

Finally, I want to share one more gadget with you. Because I have been talking about what a certain Compose control corresponds to in the View in this article, some friends may have more questions: What is the replacement of RecyclerView in Compose? What is the replacement for DrawerLayout in Compose?

Here I recommend a website to everyone: https://www.jetpackcompose.app/What-is-the-equivalent-of-DrawerLayout-in-Jetpack-Compose

You can select a component in the View system on the left page of this website, and then the right page will tell you what its corresponding component is in Compose, which is a very practical website for beginners.

insert image description here

Well, that's all for today's article, see you in the next original article.


Compose is a declarative UI framework based on the Kotlin language. If you want to learn Kotlin and the latest Android knowledge, you can refer to my new book "The First Line of Code 3rd Edition" . Click here for details .

Guess you like

Origin blog.csdn.net/sinyu890807/article/details/131622694