Android Jetpack Compose summary

Introduction

Jetpack Compose is a new way to build native UI. The writing method is very similar to Flutter. Students who know Flutter can get started quickly.
Official website: https://developer.android.com/jetpack/compose
official demo: https://github.com/android/compose-samples
official introduction: https://developer.android.com/jetpack/compose/setup

Environment and version

The minimum support is Android API 21, version 5.0, which must use kotlin language, and the minimum version is Android Studio 4.0.
Jetpack Compose is currently in the experimental stage, now it is 0.1-dev2, and it is estimated that it will be another year before the official version of 1.0.
Subsequent versions may add more kotlin features, enrich animation and other performance issues.

About how to use in existing projects:
https://developer.android.com/jetpack/compose/setup#add-compose

how to use?

Compose a new empty project directly in the AS 4.0, there is a sample code:
Insert picture description here
added before the function @Composeannotations, you can return to a similar Flutter in the Widget UI
add @Composeannotation functions can call each other, these functions will be compiled plug-in processing , So if a function does not generate UI, then do not use this annotation.
@PreviewAnnotation can be previewed in real time on the right. After changing the function, refresh a preview. The outer function to which the annotation is added cannot have parameters, but a function with parameters can be nested inside to preview. You can @Previewadd a name in the back, such as:@Preview("Text preview")

The concepts of Column and Row are the same as in Flutter, including the concept size mainAxisSizeand alignment of the main axis and the secondary axis crossAxisAlignment, and a code example:

@Composable
fun MyScreenContent(
    names: List<String> = listOf("Android", "there"),
    counterState: CounterState = CounterState()
) {
    Column(crossAxisAlignment = CrossAxisAlignment.Center
      crossAxisSize = LayoutSize.Expand,
        mainAxisSize = LayoutSize.Expand) {
        for (name in names) {
            Greeting(name = name)
            Divider(color = Color.Black)
        }
        Divider(color = Color.Transparent, height = 32.dp)
        Counter(counterState)
    }
}

@Preview("MyScreen preview")
@Composable
fun DefaultPreview() {
    MyApp {
        MyScreenContent()
    }
}

You can use HeightSpacer(24.dp)or WeightSpacer(24.dp)to directly add a width and height interval

According to official suggestions, we can split the UI into multiple small Compose functions. Each function will eventually be compiled by the plugin to generate a View, and then these Compose functions can be reused.

@Composable
fun MyScreenContent(
    names: List<String> = listOf("Android", "there"),
    counterState: CounterState = CounterState()
) {
    Column(modifier = ExpandedHeight, crossAxisAlignment = CrossAxisAlignment.Center) {
        Column(modifier = Flexible(1f), crossAxisAlignment = CrossAxisAlignment.Center) {
            for (name in names) {
                Greeting(name = name)
                Divider(color = Color.Black)
            }
        }
        Counter(counterState)
    }
}

In Column, you can set ExpandedHeight on the parameter modifier, similar to the meaning of setting the height match_parent, the width is the same.

About how to use Theme and customize Theme

There are many colors and font styles in MaterialTheme. After wrapping MaterialTheme in the outer layer, you can use the theme data in the internal Compose function, such as:style = +themeTextStyle { h1 }

@Composable
fun Greeting(name: String) {
    Text (
        text = "Hello $name!",
        modifier = Spacing(24.dp),
        style = +themeTextStyle { h1 }
        color = +themeColor { surface }
    )
}

You can modify a certain attribute value on an existing theme by using the copy function, such as:

textStyle = (+themeTextStyle { body1 }).copy(color = Color.Yellow)

Custom Theme

import androidx.compose.Composable

@Composable
fun CustomTheme(children: @Composable() () -> Unit) {
    // TODO 
}
import androidx.compose.Composable
import androidx.ui.core.CurrentTextStyleProvider
import androidx.ui.graphics.Color
import androidx.ui.material.MaterialColors
import androidx.ui.material.MaterialTheme
import androidx.ui.text.TextStyle

val green = Color(0xFF1EB980.toInt())
val grey = Color(0xFF26282F.toInt())
private val themeColors = MaterialColors(
    primary = green,
    surface = grey,
    onSurface = Color.White
)

@Composable
fun CustomTheme(children: @Composable() () -> Unit) {
    MaterialTheme(colors = themeColors) {
        val textStyle = TextStyle(color = Color.Red)
        CurrentTextStyleProvider(value = textStyle) {
            children()
        }
    }
}

Effects and memo

The role of memo:

1. When recompositions (that is, when the Model data inside the UI component changes, the UI component will be rebuilt), save the state value, as follows:

@Composable
fun MyScreenContent(
    names: List<String> = listOf("Android", "there"),
    counterState: CounterState = CounterState()
) { ... }

There is a problem with the above code. When re-building, the original counterState value will be lost, each time a new counterState object.
You can solve the problem after modifying it using memo as follows:

@Composable
fun MyScreenContent(
    names: List<String> = listOf("Android", "there"),
    counterState: CounterState = +memo { CounterState() }
) { ... }

2. When reorganizing, remember some internal calculation results to prevent multiple calculations

If you need to perform calculations in the middle of the composition, but do not want to perform calculations each time you recombine the function, you can remember the calculation, even if the Composable function is recombined, the calculation will not be performed again.

@Composable
fun Greeting(name: String) {

    val formattedName = +memo { name.substringBefore(" ").toUpperCase() }

    Text (
        text = "Hello $formattedName!",
        modifier = Spacing(24.dp),
        style = +themeTextStyle { h3 }
    )
}

@Preview
@Composable
fun DefaultPreview() {
    MaterialTheme {
        Greeting("Android 10")
    }
}

For example, the formattedName calculation process here will not be repeated after using memo, but there is a bug written in this way. If another parameter is passed in the second call, then because memo reuses the original result, it will Causes a bug, so for the parameters that need to be modified, memo can be used in the following way:

@Composable
fun Greeting(name: String) {

    val formattedName = +memo(name) { name.substringBefore(" ").toUpperCase() }

    Text (
        text = "Hello $formattedName!",
        modifier = Spacing(24.dp),
        style = +themeTextStyle { h3 }
    )
}

@Model annotated

After the model annotation marks a data class, you can directly monitor the data changes in the Compose function and automatically update the display,
such as:
definition:

@Model 
class CounterState(var count: Int = 0)

use:

@Composable 
fun Counter(state: CounterState) {
    Button(
        text = "I've been clicked ${state.count} times",
        onClick = {
            state.count++
        }
    )
}

Status promotion, data flow down, event flow up

@Model
class FormState(var optionChecked: Boolean)

@Composable
fun Form(formState: FormState) {
    Checkbox(
        checked = formState.optionChecked,
        onCheckedChange = { newState -> formState.optionChecked = newState })
}

在上面代码中,Checkbox的选中状态,在Checkbox和Form中都不保存,而改为由外部传入,原因是此时外部可能需要使用当前的状态值,那么由外部来创建并传递该参数到Compose函数中,这使得外部调用者提升了状态

⚠️ Note: In composable functions, the state that may be useful for calling the function should be disclosed, because this is the only method that can be used or controlled, called state promotion.

The concept of state promotion is the same as Flutter. In the future, it should also introduce related state management libraries like Provider, BLOC, or Redux in Flutter, because the way of Compose + Model annotation is a MVVM idea and needs a convenience. A tripartite library of data state management does this.

Regarding data flow: The parent Composable function can control its child data. Sub Compose UI should not be read from global variables or global data storage. Composable functions should only receive the required information, so they should be as simple as possible, rather than calling everything the parent Composable function can provide.

@Composable
fun MultipleGreetings(user: User = +memo { User("name", "surname") }) {
    Column {
        Greeting("${user.firstName} ${user.lastName}")
        Greeting("Android 10")
        Button(text = "Change name", onClick = {
            user.firstName = "Changed"
            user.lastName = "New surname"
        })
    }
}

@Composable
fun Greeting(name: String) {

    val formattedName = +memo(name) { name.substringBefore(" ").toUpperCase() }

    Text (
        text = "Hello $formattedName!",
        modifier = Spacing(24.dp),
        style = +themeTextStyle { h3 }
    )
}

比如上面代码中,Greeting从调用方Compose函数(MultipleGreetings)获取数据,作为参数传入,且Greeting只接收一个String,并不是整个User对象。

Event passing up

The event keeps going up through the lambda callback. When the child Composable function receives an event, the change should be propagated back to the Composable that cares about the information.

In our example, we can make Greeting clickable by wrapping the content of Greeting in a Clickable function (available in the library) that takes an onClick listener as a parameter. However, Greeting is a reusable function, it does not know how to handle user interaction. You should use lambda to propagate this information from the bottom of the hierarchy (Clickable composable in Greeting) to the Composable functions at the top, which know how to handle this information, as shown in the following example:

@Composable
fun MultipleGreetings(user: User = +memo { User("name", "surname") }) {

    val onClick = {
        user.firstName = "Changed"
    }

    Column {
        Greeting("${user.firstName} ${user.lastName}", onClick)
        Greeting("Android 10", onClick)
        Button(text = "Change name", onClick = onClick)
    }
}

@Composable
fun Greeting(name: String, onClick: () -> Unit) {

    val formattedName = +memo(name) { name.substringBefore(" ").toUpperCase() }

    Clickable(onClick = onClick) {
        Text (
            text = "Hello $formattedName!",
            modifier = Spacing(24.dp),
            style = +themeTextStyle { h3 }
        )
    }
}

Greeting tells MultipleGreetings that it was clicked by calling the lambda passed by the parent as a parameter. If you run the application, you can see that clicking on any greeting text will propagate the changes and the Greeting instance at the top will be regrouped.

Insert picture description here
Data flow in Compose apps. Data flows down with parameters, events flow up with lambdas.
Data flow in Compose apps. Data flows down with parameters, events flow up with lambdas.


Compose and existing View interoperability

The functions written by Compose can be used in XML, and the existing View of Android can also be written in Compose, such as:
Insert picture description here
Insert picture description here
Summary: Compose draws on the writing method of Flutter and Swift UI, the code is concise, you can preview the effect in real time, as of 2019 On November 19th, the current version was only 0.1. It is expected that after the official release of 1.0, there will be more function updates. A daily small demo can be familiarized with Compose first.

Reference:
https://codelabs.developers.google.com/codelabs/jetpack-compose-basics/#0

Excellent! The latest development of the Android Jetpack Compose UI component library, written exactly like Flutter

The latest progress of Android Studio 4.0, these new features are amazing!

Published 82 original articles · Like 86 · Visit 110,000+

Guess you like

Origin blog.csdn.net/unicorn97/article/details/103137105