1
头图

公众号名片
作者名片

Introduction to Compose

Jetpack Compose is a new library released at the 2019 Google I/O Conference until the release version 1.0.0 is released in July 2021. Its characteristic is that it can use less Kotlin
Code to complete the UI development on the Android platform more conveniently.

Why was Compose launched

Jetpack Compose is Android’s modern toolkit for building native UI. It simplifies and accelerates UI development on Android. Quickly bring your app to life with less code, powerful tools, and intuitive Kotlin APIs.

From the description of the official website, it can be seen that using Compose can simplify the development of UI on Android, can significantly reduce the time to create a page, and is more'modern'.

With the update and iteration of mobile phone hardware, it has become possible to build more complex pages on mobile phones to meet business needs, based on traditional XML
There are more and more controls corresponding to the way of construction, and maintaining the synchronization of the states between the controls becomes more and more difficult to maintain, and it takes a lot of effort to maintain the unity of the states of the controls.

Based on this, Android launched Compose. The UI declared by Compose is immutable, cannot be referenced by the outside world, and cannot hold state. Use @Composable declare that it runs as a "pure function". When State
The function re-executes to refresh the UI when it changes, which can better implement the characteristics of declarative UI.

What is declarative UI

Traditional interface writing is done through imperative programming. For example, on Android, different types of views are constructed through xml, and then when the state needs to be changed, the method of the view is directly called to change.

 // 通过 findViewById 来查找对应的 TextView
var tv: TextView = findViewById(R.id.tv)
// 直接调用方法来改变 TextView 的颜色
tv.setColor(red)

Declarative UI only needs to describe the current UI state and does not require separate control for switching between different UI states. When changes are needed, only the corresponding state needs to be changed, and the rest of the work is done by the framework.

      // 当改变 name 状态值,就会自动更新 UI 状态
      Text(
            "hello ${name}",
            modifier = Modifier.background(color = Color.Blue)
        )
     

Basic usage

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

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

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    MyApplicationTheme {
        Greeting("Android")
    }
}
  • @Composable: As you can see, as long as the methods related to the controls built by Compose are annotated with @Composable, they can only be called in methods that are also annotated by @Composable.
  • @Preview: Add @Preview annotation before the method to see the related layout without running the program. There will be three options in the upper right corner of Android Studio, if you choose Split and Design
    You can see the corresponding display effect.

Android Studio 预览

  • setContent: setContent role is to develop and function Activity setContentView used is the same, by content incoming @Composable
    Marked methods to build UI.

After running, you can see the text of Hello Android on the phone. In addition to using Text to display the text, Compose also has a variety of corresponding attributes to change the display effect of the control and rich controls to build a complex interface.

Basic controls

Text

Text is similar to TextView of Android View, and like TextView, it has many properties that can be set:

  • text : String : Set text content
  • modifier : Modifier : Text modifier
  • color : Color : The text color setting can be pre-defined by Compose, such as Color.Blue or directly input the color value Color(0xFF000000)
  • fontSize:TextUnit : Set the font size, such as 20.sp
  • fontFamily: FontFamily? : Set the font
  • fontWeight: FontWeight? : font weight
  • lineHeight: TextUnit : set the row height
  • letterSpacing:TextUnit : Set character spacing
  • textDecoration : TextDecoration? : Set strikethrough and underline
  • maxLine : Int : the maximum number of rows displayed
  • fontStyle : FontStyle? : Set the font type, such as FontStyle.Italic
  • textAlign:TextAlign? : display style, such as TextAlign.Left
  • onTextLayout: (TextLayoutResult) -> Unit : Text calculation completed callback
  • overflow: TextOverflow : Text overflow style

Example

          Text(
                text = "Hello BillionBottle",
                modifier = Modifier.padding(5.dp),
                color = Color.Blue,
                textAlign = TextAlign.Start,
                textDecoration = TextDecoration.LineThrough,
                fontStyle = FontStyle.Italic,
                maxLines = 1
            )

Effect:
text

Button

Button is mainly used to respond to the user's click event, it mainly has the following attributes:

  • onClick : () -> Unit : Call back when the button is clicked
  • modifier : Modifier : Modifier of Button
  • enabled : Boolean : Set the validity of the button, the default is true
  • shape: Shape : Adjust the appearance of the button, the default is MaterialTheme.shapes.small
  • border: BorderStroke? : Set the outer border of the button, such as CutCornerShape(30) cut corner shape; RoundedCornerShape(50) round corner shape
  • elevation: ButtonElevation? : Set the height of the button in the Z-axis direction
  • contentPadding: PaddingValues : The distance between the content and the boundary
  • colors: ButtonColors : Set the color of the button, including setting the enable/disable background and content color
  • content: @Composable () -> Unit : Set the content for the Button, you need to pass in the @Compose method

Example

  Button(
  onClick = {},
  modifier = Modifier.padding(12.dp),
  colors = ButtonDefaults.buttonColors(
      backgroundColor = Color.Green,
      contentColor = Color.Blue
  ),
  elevation = ButtonDefaults.elevation(
      defaultElevation = 12.dp,
      pressedElevation = 12.dp
  ),
  border = BorderStroke(width = 1.dp, color = Color.Blue)
) {
  Text(text = "BillionBottle")
}

Effect:

button

Image

Image corresponds to the ImageView of Android View and can be used to display pictures. It mainly has the following attributes:

  • bitmap: ImageBitmap : You can directly pass in ImageBitmap to construct, if you want to display the drawable
    The pictures under the folder, you can pass var imageBitmap = ImageBitmap.imageResource(id = R.drawable.xxx)
  • contentDescription: String? : Accessibility services can be read and identified
  • modifier : Modifier : Image modifier
  • aligment : Aligment : alignment
  • contentScale : ContentScale : The display mode of the picture
  • alpha : Float : Set the transparency, the default is 1.0f
  • colorFilter : ColorFilter : You can set color filters

Example

   // 使用 drawable 下的图片资源显示图片
Image(
    painter = painterResource(R.drawable.xxx),
    contentDescription = "",
)

Surface

When we want to add a background color to a custom component, we need to use Surface , which has the following properties:

  • modifier: Modifier : You can set modifiers for Surface
  • shape: Shape : Set the shape, the default is RectangleShape
  • color: Color : Set the background color
  • contentColor: Color : Set the color for the Text in the Surface. When the Text does not specify a color, the color is used
  • border: Border? : Set the outer border
  • elevation: Dp : Set the height of Surface in the Z-axis direction
  • content: @Composable () -> Unit : To set the content layout for Surface, you need to pass in the @Compose method

Example


    Surface(modifier = Modifier.padding(4.dp), color = Color.Gray) {
        Column {
            Text(modifier = Modifier.align(Alignment.CenterHorizontally), text = "custom")
            Image(
                modifier = Modifier.size(150.dp),
                painter = ColorPainter(color = Color.Green),
                contentDescription = "image color"
            )
        }
    }

Effect:
surface

Canvas

Canvas is a component that performs drawing in a specified area on the screen. Note that you need to add a modifier to specify the size when using it, you can pass Modifier.size
Set a fixed size, you can also use Modifier.fillMaxSize , ColumnScope.weight set the relative parent component size. If the parent component is not set size, then Canvas
A fixed size must be set.

Canvas is similar to the original custom View, but it is more convenient. You can draw the effect you want through the drawing method defined by DrawScope. You can use drawArc , drawCircle
, drawLine , drawPoints and other methods to draw graphics (for details, please refer to the method under DrawScope



Canvas(modifier = Modifier.fillMaxSize()) {
    val canvasWidth = size.width
    val canvasHeight = size.height
    // 绘制一条从左下角到右上角的蓝色的线
    drawLine(
        start = Offset(x = canvasWidth, y = 0f),
        end = Offset(x = 0f, y = canvasHeight),
        color = Color.Blue
    )

    // 在以 200,1200 位置 120 为半径绘制一个圆
    drawCircle(color = Color.Green, center = Offset(200f, 1200f), radius = 120f)
}

Effect:

surface

Layout control

Compose provides some available layout components to enable us to better lay out UI elements:

Column

Android's LinearLayout control must be very familiar to people who learn Android, and Column is very similar to the vertical arrangement of LinearLayout. Observe how it is declared


@Composable
inline fun Column(
    modifier: Modifier = Modifier,
    verticalArrangement: Arrangement.Vertical = Arrangement.Top,
    horizontalAlignment: Alignment.Horizontal = Alignment.Start,
    content: @Composable ColumnScope.() -> Unit
) {
    val measurePolicy = columnMeasurePolicy(verticalArrangement, horizontalAlignment)
    Layout(
        content = { ColumnScopeInstance.content() },
        measurePolicy = measurePolicy,
        modifier = modifier
    )
}

Column has two properties to control the layout of children:
verticalArrangement is to control the vertical arrangement of sub-elements, the default is Arrangement.Top , arranged as close to the top of the main axis as possible. It also has several other values to represent different layout methods:

  • Arrangement.BOTTOM : Arrange vertically and as close to the bottom as possible
  • Arrangement.CENTER : Vertically centered
  • Arrangement.SpaceBetween : Evenly distribute sub-elements
  • Arrangement.SpaceEvenly : Make the child elements equally spaced and evenly placed, but there is no space between the head and tail of the element
  • Arrangement.SpaceAround : Make the child elements equally spaced and evenly placed, the interval between the beginning and the end of the child elements is half of the middle

horizontalAlignment controls the horizontal arrangement of sub-elements. The default is Alignment.Start In general, it starts from the left. Alignment
Many arrangements are defined below, and there are three main types Column

  • Alignment.Start : Left aligned
  • Alignment.End : Right aligned
  • Alignment.CenterHorizontally : Align horizontally to the center

How do you put the sub-controls of Column? In fact, its another attribute is content, which is a function that emits sub-interface elements, which contains the required sub-elements.

For example, the following example uses horizontal right alignment and vertical bottom alignment:

@Composable
fun columnColumn() {
       Column(
            // modifier 会在下面说明,主要是用来扩展控件的功能如添加边距,宽高等    
            modifier = Modifier.height(100.dp).padding(5.dp),
            verticalArrangement = Arrangement.Bottom,
            horizontalAlignment = Alignment.End
        ) {
            Text("安卓")
            Text("BillionBottle")
        }
}

Effect:

column

Row

Different from Column, Row is laid out in the horizontal direction, which is very similar to the layout method of LinearLayout setting horizontal arrangement. Row
There are also two attributes to indicate the arrangement in the horizontal and vertical directions. Its attributes and usage are also very similar to Column.


@Composable
inline fun Row(
    modifier: Modifier = Modifier,
    horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
    verticalAlignment: Alignment.Vertical = Alignment.Top,
    content: @Composable RowScope.() -> Unit
) {
    val measurePolicy = rowMeasurePolicy(horizontalArrangement, verticalAlignment)
    Layout(
        content = { RowScopeInstance.content() },
        measurePolicy = measurePolicy,
        modifier = modifier
    )
}

Observing its statement, it can be clearly seen that the two methods of controlling the horizontal and vertical directions are the same. Arrangement mainly for its main axis direction (for Column is the vertical direction, for Row
It is the horizontal direction), Alignment is the arrangement in the other direction, I won’t elaborate on it, let’s see how to use it through an example:


@Composable
fun rowShow() {
    // 创建了一个宽 200 dp,垂直方向上居中,水平对齐的布局
    Row(
        modifier = Modifier.width(200.dp),
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.Start
    ) {
        Text("安卓")
        Text("BillionBottle")
    }
}

Effect:

row

Box

Use Box to superimpose one element on top of another element, similar to FrameLayout layout. Check Box's declaration and related attributes:

@Composable
inline fun Box(
    modifier: Modifier = Modifier,
    contentAlignment: Alignment = Alignment.TopStart,
    propagateMinConstraints: Boolean = false,
    content: @Composable BoxScope.() -> Unit
) {
    val measurePolicy = rememberBoxMeasurePolicy(contentAlignment, propagateMinConstraints)
    Layout(
        content = { BoxScopeInstance.content() },
        measurePolicy = measurePolicy,
        modifier = modifier
    )
}

Among them, modifier and content are the same as before, and contentAlignment is the control box
There are many ways to align the child elements. For example, you can set the top or bottom centering method. For details, please refer to Alignment static properties.

// 2D Alignments.

Annotate related content.

Let's see how to use it:


@Composable
fun boxLayout() {
    Box(
        contentAlignment = Alignment.BottomCenter,
        modifier = Modifier
            .width(100.dp)
            .height(50.dp)
            .padding(bottom = 10.dp),
    ) {

        Text("BillionBottle", modifier = Modifier.background(Color.Yellow))
        Text(
            "安卓",
            modifier = Modifier.background(color = Color.Gray)
        )
    }
}

Effect:

box

Modifier

Compose basically provides modifiers for each component to extend the functionality of the component, including the width and height of the component, accessibility information, user input, and the advanced interaction of the user clicking and scrolling. Modifiers are mainly composed of Modifier
This class is created, and its calling method is that the chain call will return to itself every time it is called to play. Commonly used attributes are background , height , offset , size , clickable
Etc. For details, please refer to official document Modifier

It should be noted that different effects may be displayed on the interface through different calling sequences:

   Column(
        modifier = Modifier
            .width(100.dp)
            .height(100.dp)
    ) {
        Text("BillionBottle")
        Icon(
            Icons.Filled.Favorite,
            contentDescription = "Favorite",
            // 1
            modifier = Modifier
                .background(Color.Green)
                .size(ButtonDefaults.IconSize)
                .padding(2.dp)
        )
    }

Through the preview interface Build & Refresh, it can be seen that if the modifier's .size and .padding are called and exchanged, the size of the Icon will be quite different.

By superimposing and combining various controls, we can construct the interface we want. And for the problem that the excessive nesting of the original Android View may have a performance impact, Compose
It can effectively handle nested layouts, and is an excellent tool for designing complex interfaces.

Give an example

The following is a custom control that displays different userProfiles by passing in name, image, and content:

// @DrawableRes 指明传入的image必须为drawable下的资源文件
@Composable
fun userProfile(name:String,content:String,desc:String = "",@DrawableRes image:Int) {
        // 添加边距
        Row(modifier = Modifier.padding(all = 8.dp)) {
            Image(
                painter = painterResource(image),
                contentDescription = desc,
                modifier = Modifier
                    .size(40.dp)
                    // 将图片裁剪成圆形
                    .clip(CircleShape)
            )

            // 添加 Image 和 Column 间距
            Spacer(modifier = Modifier.width(8.dp))

            Column {
                Text(text = name)
                Spacer(modifier = Modifier.height(4.dp))
                Text(text = content)
            }
        }
}

The displayed effect after passing in the corresponding parameters:

custom

4. State management

The so-called state can be understood as a change of a value can be a change of a Boolean value or a change of an array, or can be understood from the interface as the state of button text and color, and Compose is declarative
UI is mainly reorganized according to state changes. At this time, you need to add state and manage related states.

There is an observable type object MutableState<T> in compose runtime, which can be created mutableStateOf

interface MutableState<T> : State<T> {
    override var value: T
}

// 这三种声明方式都是一样的
val state = remember { mutableStateOf("") }
var value by remember { mutableStateOf("") }
val (value, setValue) = remember { mutableStateOf("") }

remember is to store the state in the Composition, when the reorganization occurs, the original object will be automatically discarded and the value after the changed state will be used. As long as the value of MutableState
Making a change will cause a reorganization of the composable methods that use that state. Not much to say, let's take a look at how state is used:

//module
data class Info(var content:String)

@Composable
fun Greeting(name: String) {
    var info by remember { mutableStateOf(Info("")) }
    MyApplicationTheme {
        // A surface container using the 'background' color from the theme
        Surface(color = MaterialTheme.colors.background) {
            Column(modifier = Modifier.padding(16.dp)) {

                if (info.content.isNotEmpty()){
                    Text(text = info.content)
                }
                OutlinedTextField(
                    value = info.content,
                    onValueChange = {
                        info = Info(it)
                    },
                    label = { Text("title") }
                )
            }
        }
    }
}

The function is very simple, it is in OutlinedTextField
If the content entered with the keyboard is not empty, it can be displayed at the top in real time, mainly through var info by remember { mutableStateOf(Info("")) } to change, when the info
When the reference of this variable changes, Compose will refresh the components that use this variable, and the corresponding component state will also change, so we only need to update the data when using Compose.

But remember can only save the state during reorganization, once other conditions such as screen rotation and other configuration changes, remember
rememberSaveable . At this time, you need to use 061c05435e87f1. As long as it is Bundle type data, rememberSaveable can be automatically saved. How to use:

  • As long as it is of the Parcelize type, it is the same as remember
@Parcelize
data class Info(val content: String): Parcelable

var value by rememberSaveable { mutableStateOf(Info("")) }
  • MapSaver:
data class Info(val content: String)

val infoSaver = run {
    val nameKey = "content"
    mapSaver(
        save = { mapOf(nameKey to it.content) },
        restore = { Info(it[nameKey] as String) }
    )
}
@Composable
fun CityScreen() {
    var infoState = rememberSaveable(stateSaver = citySaver) {
        mutableStateOf(Info(""))
    }
    Column(modifier = Modifier.padding(16.dp)) {

        if (infoState.value.content.isNotEmpty())
            Text(text = infoState.value.content)

        OutlinedTextField(
            value = infoState.value.content,
            onValueChange = {
                 infoState.value = Info("$it")
            },
            label = { Text("title") }
        )
    }
}
  • ListSaver
data class City(val name: String, val country: String)

val CitySaver = listSaver<City, Any>(
    // 数组中保存的值和 City 中的属性是顺序对应的
    save = { listOf(it.name, it.country) },
    restore = { City(it[0] as String, it[1] as String) }
)

@Composable
fun CityScreen() {
    var selectedCity = rememberSaveable(stateSaver = CitySaver) {
        mutableStateOf(City("", ""))
    }
}

Of course, compose also supports other types of State:

  • LiveData
  • Flow
  • RxJava2

Before using other State types, you must convert to State<T> type, so that compose can recognize that this is a state value, and you need to refresh the ui based on this value. For example, if you use LiveData, you must
The Composable method converts to tate type before using it, you can use LiveData<T>.observeAsState() .

summary

This article only briefly introduces the basic content of Android Compose. More rich content and details can be viewed on the official website. With the continuous update of the version, new features will continue to be added, waiting for everyone to explore!

more exciting 161c05435e899e, please pay attention to our public "161c05435e89a0 One Hundred Bottle Technology ", there are irregular benefits!

百瓶技术
127 声望18 粉丝

「百瓶」App 技术团队官方账号。