Compose
My small goal for today is to finish up Compose’s freshman tutorial. hh
Compose programming idea
There is no hierarchy difference between the View architecture and Compose.
One of Compose’s biggest and most different aspects is his programming philosophy.
The View system is imperative, and Compose is declarative.
Declarative programming specifications
Imperative as its name suggests, we use code to command it to change. Such as setText, setImageResource, addView, etc.
Declarative is when you need to change state, leave it alone. Yes, just leave it alone, it will change automatically, I just declare this component, and then I won’t tell you to change it again, so that the responsibility of updating the interface falls on the interface itself, which is much easier.
Automatic? Isn’t that reactive programming? Observer mode. I’m familiar with that.
As anyone who has used LiveData knows, using The Observe method greatly facilitates our development and reduces the chances of bugs. Basically, after we write the interface, we do not care about the interface state, by changing the corresponding Livedata, the View automatically adjusts. This also seems to achieve the imperative effect.
But Compose has even stronger support.
@Composable
That’s the way Compose’s Hello World is written
@Composable
fun Greeting(str:String){
Text(text =str)
}
Copy the code
There are a couple of caveats
-
This function comes with the @composable annotation (which tells the compiler that the function is intended to transform data into an interface)
-
Functions annotated with @composable are capitalized.
For example, Compose is a new Compose function. For example, Compose is a new Compose function.
-
This function accepts data. Composable functions can take parameters that allow the application logic to describe the interface. In this case, our Greeting accepts a String.
-
Composition functions can call other composition functions, such as Greeting, which calls the composition function Text
-
This function does not return anything. The interface’s Compose function does not need to return anything.
The transformation of declarative UI
In many object-oriented imperative interface toolkits, you can initialize an interface by instantiating a widget tree. You typically do this by bloating XML layout files. Each widget maintains its own internal state and provides getter and setter methods that allow application logic to interact with the widget.
In Compose’s declarative method, the widget is relatively stateless and provides no setter or getter functions.
This makes it easy to provide state to architectural patterns such as viewModels, as described in the Application Architecture Guide. Composable items are then responsible for converting the current application state to the interface each time observable data is updated.
When the user interacts with the interface, the interface initiates events such as onClick. These events should notify the application logic, which can then change the state of the application. When the state changes, the system calls the composable function again with the new data. This causes interface elements to be redrawn, a process known as “refactoring.”
Summary:
- The logical layer sends a state to the View, and the View itself observes the state data and updates the interface.
- When the user clicks the event, the event will be sent to the logical layer. The logical layer will change the data after processing, and the data change will call the combinable function, and the view will be redrawn.
It seems not too different from MVVM.
restructuring
In the imperative interface model, if you need to change a widget, you can call setters on that widget to change its internal state. In Compose, you can call the composable function again with the new data. Doing so causes the function to be reorganized — the system redraws the widgets emitted by the function using new data as needed. The Compose framework intelligently reorganizes only the components that have changed.
For example, suppose you have the following composable function that displays a button:
@Composable
fun ClickCounter(clicks: Int, onClick: () -> Unit) {
Button(onClick = onClick) {
Text("I've been clicked $clicks times")}}Copy the code
Each time the button is clicked, the caller updates the value of CLICKS. Compose calls the lambda and Text functions again to display the new values; This process is called “reorganization”. Other functions that do not depend on this value are not reassembled.
(It was so scary.) That is, native support for local refreshes. This……
It feels like a refresh. Recombination of interface elements, um recombination.
Recombination is the process of calling a composable function again when the input changes.
Note: The following 3 things cannot be done due to the nature of the recombination process.
- Writing to a property of a shared object Changes a shared variable
- Updating an observable in
ViewModel
Update the ViewModel observables - Updating shared Preferences Updates sharedPreferences
Additional notes on composable functions
1. Composable functions can be executed in any order
Look at the code below and our first idea is to draw StartScreen, then MiddleScreen, EndScreen.
That’s not really the case.
They can be drawn in any order.
It could be EndScreen, MiddleScreen, StartScreen, but… (The main reason for that is that Compose prioritizes some elements so that the higher-priority elements are drawn first and the lower-priority elements are drawn later.)
So don’t update some shared variables in this code, which is prone to unpredictable problems where each function needs to remain independent.
@Composable
fun ButtonRow(a) {
MyFancyNavigation {
StartScreen()
MiddleScreen()
EndScreen()
}
}
Copy the code
2. Combinable functions can be executed in parallel
Compose optimizes recombination by running composable functions in parallel. In this way, Compose can leverage multiple cores and run composable functions at a lower priority (not on screen).
This parallel optimization means that threads are not safe, and if we change the observable in the ViewModel, we get weird bugs.
To ensure that the application works properly, all composable functions should not have collateral effects, but should be triggered by callbacks such as onClick that are always executed on the interface thread.
The side effect here is that some logic operations, composable functions should only have some display logic inside, operations and other logic should not be, because parallel threads are not safe.
Like this is ok
Van ♂ Ruigudd, there is no problem when running
@Composable
fun ListComposable(myList: List<String>) {
Row(horizontalArrangement = Arrangement.SpaceBetween) {
Column {
for (item in myList) {
Text("Item: $item")
}
}
Text("Count: ${myList.size}")}}Copy the code
That’s not going to work
this code will not be thread-safe or correct
@Composable
@Deprecated("Example with bug")
fun ListWithBug(myList: List<String>) {
var items = 0
Row(horizontalArrangement = Arrangement.SpaceBetween) {
Column {
for (item in myList) {
Text("Item: $item")
items++ // Avoid! Side-effect of the column recomposing.
}
}
Text("Count: $items")}}Copy the code
Items++ is the problem, the thread is not safe.
3. Composable functions do their best to avoid things that don’t need to be recombined
/** * Display a list of names the user can click with a header */
@Composable
fun NamePicker(
header: String,
names: List<String>,
onNameClicked: (String) - >Unit
) {
Column {
// this will recompose when [header] changes, but not when [names] changes
Text(header, style = MaterialTheme.typography.h5)
Divider()
// LazyColumn is the Compose version of a RecyclerView.
// The lambda passed to items() is similar to a RecyclerView.ViewHolder.
LazyColumn {
items(names) { name ->
// When an item's [name] updates, the adapter for that item
// will recompose. This will not recompose when [header] changes
NamePickerItem(name, onNameClicked)
}
}
}
}
/** * Display a single name the user can click. */
@Composable
private fun NamePickerItem(name: String, onClicked: (String) - >Unit) {
Text(name, Modifier.clickable(onClick = { onClicked(name) }))
}
Copy the code
Each of these scopes may be the only scope executed during reorganization. When the header changes, Compose might jump to the Column lambda without executing any of its parents. In addition, Compose may choose to skip LazyColumnItems if names are not changed when Column is executed.
Similarly, executing all composable functions or lambdas should have no side effects. When you need to perform a spin-off effect, it should be triggered by a callback.
4. Restructuring is optimistic
Whenever Compose thinks the parameters of a composable item may have changed, it will begin the reorganization. The reorganization is an optimistic operation, which means Compose expects to complete the reorganization before the parameters change again. If a parameter changes before the reorganization is complete, Compose may cancel the reorganization and start over with the new parameter.
When the recombination is canceled, Compose will drop the interface tree from the recombination. If there are any spin-offs that depend on the displayed interface, the spin-offs are applied even if the composition operation is cancelled. This can lead to inconsistent application states.
Ensure that all composable functions and lambdas are idempotent and have no side effects to handle optimistic recombination.
Idempotent: The result of one or more requests for the same operation is the same without side effects caused by multiple clicks.
For example, database increments are non-idempotent, and queries are idempotent.
5. Composable functions can be executed very frequently
In some cases, you might run a composable function for each frame of an interface animation. If this function performs expensive operations (such as reading data from device storage), the interface can get stuck.
For example, if we were to do a read operation on a device, if placed in a combinatorics function it would read hundreds of times a second, which would have a terrible impact on performance.
summary
The function annotated with @composable is a special function.
- The order in which it performs the drawing is arbitrary.
- It might be called by multiple threads.
- When the data changes, it refreshes where it has changed and stays the same where it has not.
- Be optimistic when reorganizing
- It may be called frequently.
Compose basic Codelabe
Set up the environment
I’m not going to talk about that, but there’s one thing that needs to be noted
We need to delete the kotlinCompilerVersion of as
composeOptions {
kotlinCompilerExtensionVersion compose_version
kotlinCompilerVersion '1.5.21'
}
Copy the code
ComposeOptions that’s enough
composeOptions {
kotlinCompilerExtensionVersion compose_version
}
Copy the code
The concept of the Compose composition function
This is the function labelled by @composable, which has some limitations and is described recently by the Composable.
Compose’s declarative UI
Modifier
Modifier touched before, the official definition is the Modifier is a layout operation. You can use the Modifier to set the width, height, Shape, and padding.
Modifier parameters tell a UI element how to layout, display, or behave within its parent layout. Modifiers are regular Kotlin objects.
The other seems to be talking about a Modifier. Padding. Unusual bird
Placement of combinatorial functions
There is no need to put all of the Compose UI logic into the MainActivity. Like this.
The Activity expands inexplicably.
package com.example.compose
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.compose.ui.theme.ComposeTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
setContent {
ComposeTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
//Greeting("Android")
//ManageLayout()
ComposeRecyclerView(messages = (0.100.).map {
Message("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"."BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBB")})}}}}}//Part 8
@Preview
@Composable
fun Pre(a) {
ComposeRecyclerView(messages = (0.100.).map {
Message("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"."BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBB")})}//Part 7
@Composable
fun ComposeRecyclerView(messages: List<Message>) {
LazyColumn {
items(messages) { message ->
ManageLayout(message = message)
}
}
}
//Part 6
@Composable
//@Preview
fun ManageLayout(message: Message) {
Row(
modifier = Modifier.padding(all = 8.dp),
) {
Image(
painter = painterResource(id = R.drawable.img),
contentDescription = "This is a picture of a little sister.",
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.border(
width = 1.5.dp,
color = MaterialTheme.colors.secondary,
shape = CircleShape
)
)
Spacer(modifier = Modifier.width(8.dp))
var isExpandable by remember {
mutableStateOf(false)}val surfaceColor by animateColorAsState(
if (isExpandable) MaterialTheme.colors.primary elseMaterialTheme.colors.surface ) Column(modifier = Modifier.clickable { isExpandable = ! isExpandable }) { Text( text = message.str1, color = MaterialTheme.colors.secondaryVariant, style = MaterialTheme.typography.subtitle2 ) Spacer(modifier = Modifier.height(8.dp))
Surface(
shape = MaterialTheme.shapes.medium,
elevation = 1.dp,
color = surfaceColor,
modifier = Modifier
.animateContentSize()
.padding(1.dp)) {
Text(
text = message.str2,
style = MaterialTheme.typography.body2,
maxLines = if (isExpandable) Int.MAX_VALUE else 1,)}}}}//Part 5
@Composable
@Preview
fun UseImage(a) {
val painter = painterResource(id = R.drawable.img)
Row {
Image(
painter = painter,
contentDescription = "This is a picture of a little sister."
)
Column {
Text(text = "I don't know what to write.")
Text(text = "I really don't know what to write.")}}}//Part 4
@Preview
@Composable
fun UseColumn(a) {
Column(
verticalArrangement = Arrangement.Bottom,
horizontalAlignment = Alignment.Start,
) {
Text(text = "Hello")
Text(text = "World")}}//Part 3
data class Message(
val str1: String,
val str2: String,
)
@Preview
@Composable
fun MessagePreview(a) {
MessageCard(message = Message("Hello"."World"))}@Composable
fun MessageCard(message: Message) {
Row(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
) {
Text(text = message.str1)
Text(text = message.str2)
}
}
//Part 2
@Preview(name = "The first",
widthDp = 100,
heightDp = 50,
showBackground = true,
backgroundColor = 0x12ffff,
showSystemUi = true,
locale = "fr-rFR")
@Composable
fun Greeting(name: String = "World") {
val resource = painterResource(id = R.drawable.abc_vector_test)
Row() {
Text(text = "Hello $name!")
Image(
painter = resource,
contentDescription = "Test Logo")}},// Part 1
@Preview(showBackground = true)
@Composable
fun DefaultPreview(a) {
ComposeTheme {
Greeting("Android")}}Copy the code
Instead, we could put the Compose function directly in another file and call it from within the activity, like this
package com.example.compose
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.compose.ui.theme.ComposeTheme
/ * * *@author ZhiQiang Tu
*@time2021/8/21 and *@signatureWe don't know where we're going, but we're on our way
//Part 8
@Preview
@Composable
fun Pre(a) {
ComposeRecyclerView(messages = (0.100.).map {
Message("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"."BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBB")})}//Part 7
@Composable
fun ComposeRecyclerView(messages: List<Message>) {
LazyColumn {
items(messages) { message ->
ManageLayout(message = message)
}
}
}
//Part 6
@Composable
//@Preview
fun ManageLayout(message: Message) {
Row(
modifier = Modifier.padding(all = 8.dp),
) {
Image(
painter = painterResource(id = R.drawable.img),
contentDescription = "This is a picture of a little sister.",
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.border(
width = 1.5.dp,
color = MaterialTheme.colors.secondary,
shape = CircleShape
)
)
Spacer(modifier = Modifier.width(8.dp))
var isExpandable by remember {
mutableStateOf(false)}val surfaceColor by animateColorAsState(
if (isExpandable) MaterialTheme.colors.primary elseMaterialTheme.colors.surface ) Column(modifier = Modifier.clickable { isExpandable = ! isExpandable }) { Text( text = message.str1, color = MaterialTheme.colors.secondaryVariant, style = MaterialTheme.typography.subtitle2 ) Spacer(modifier = Modifier.height(8.dp))
Surface(
shape = MaterialTheme.shapes.medium,
elevation = 1.dp,
color = surfaceColor,
modifier = Modifier
.animateContentSize()
.padding(1.dp)) {
Text(
text = message.str2,
style = MaterialTheme.typography.body2,
maxLines = if (isExpandable) Int.MAX_VALUE else 1,)}}}}//Part 5
@Composable
@Preview
fun UseImage(a) {
val painter = painterResource(id = R.drawable.img)
Row {
Image(
painter = painter,
contentDescription = "This is a picture of a little sister."
)
Column {
Text(text = "I don't know what to write.")
Text(text = "I really don't know what to write.")}}}//Part 4
@Preview
@Composable
fun UseColumn(a) {
Column(
verticalArrangement = Arrangement.Bottom,
horizontalAlignment = Alignment.Start,
) {
Text(text = "Hello")
Text(text = "World")}}//Part 3
data class Message(
val str1: String,
val str2: String,
)
@Preview
@Composable
fun MessagePreview(a) {
MessageCard(message = Message("Hello"."World"))}@Composable
fun MessageCard(message: Message) {
Row(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
) {
Text(text = message.str1)
Text(text = message.str2)
}
}
//Part 2
@Preview(name = "The first",
widthDp = 100,
heightDp = 50,
showBackground = true,
backgroundColor = 0x12ffff,
showSystemUi = true,
locale = "fr-rFR")
@Composable
fun Greeting(name: String = "World") {
val resource = painterResource(id = R.drawable.abc_vector_test)
Row() {
Text(text = "Hello $name!")
Image(
painter = resource,
contentDescription = "Test Logo")}},// Part 1
@Preview(showBackground = true)
@Composable
fun DefaultPreview(a) {
ComposeTheme {
Greeting("Android")}}Copy the code
Pass the composition function as a function parameter
Content: @composable () -> Unit means that we pass a Composable higher-order function in the same way that a coroutine passes a suspend function.
@Composable
fun PassComposeContent(content: @Composable() - >Unit) {
ComposeTheme {
content()
}
}
Copy the code