Android--Compose 初级教程

150 阅读5分钟
  • Defining composable functions
  • Adding different elements in your composable
  • Structuring your UI component using layout composables
  • Extending composables by using modifiers
  • Creating an efficient list
  • Keeping track of state and modifying it
  • Adding user interaction on a composable
  • Animating messages while expanding them

1.使用compose 方法定义View组件

Jetpack Compose,是由compose函数构建而成的。每个compose方法,只能被compose方法所调用。要让一个方法成为compose方法,只需要添加@Composable注解就行。举例:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            Text("Hello World")
//            ComposeTutorialTheme {
//                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
//                    Greeting(
//                        name = "Android",
//                        modifier = Modifier.padding(innerPadding)
//                    )
//                }
//            }
        }
    }
}

public fun ComponentActivity.setContent(
    parent: CompositionContext? = null,
    content: @Composable () -> Unit
) 

Text是一个compose函数,它接收一些初始化参数,Text的作用是展示一个文本。Text在setContent里面被调用,setContent方法里面的content参数,接收一个compose方法。

自定义compose方法,使用composable注解:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
              MessageCard("Android")
        }
    }
}

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

预览compose方法所声明的UI效果,在已经添加了@Composable注解的情况下,通过给方法添加@Preview注解,来达到不用安装app到设备,就能预览UI的效果。使用@Preview注解的方法,不能接收参数。

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

@Preview
@Composable
fun PreviewMessageCard(){
    MessageCard("Android ")
}

2.对UI组件进行布局,layout

使用Column组件将所有子组件竖直排列,布局UI组件,例子 :

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
              MessageCard(msg = Message("John","Jetpack Compose"))
        }
    }
}

data class Message(val author: String, val body: String)

@Composable
fun MessageCard(msg: Message) {
    Column {
        Text(text = msg.author)
        Text(text = msg.body)
    }
}

使用Row组件,将子组件水平排列。例子:


class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
              MessageCard(msg = Message("John","Jetpack Compose"))
        }
    }
}

data class Message(val author: String, val body: String)

@Composable
fun MessageCard(msg: Message) {
    Row {
        Image(
            painter = painterResource(R.mipmap.profile_picture),
            contentDescription = "Contact profile picture",
        )

        Column {
            Text(text = msg.author)
            Text(text = msg.body)
        }

    }

}

使用modifiers属性,修饰layout组件的间隔,子组件的间隔,修饰组件的边角等,并且也可以控制组件是否可以点击。 例子:

@Composable
fun MessageCard(msg: Message) {
    // Add padding around our message
    // 内部的间距,padding是8dp
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.mipmap.profile_picture),
            contentDescription = "Contact profile picture",
            modifier = Modifier
                // Set image size to 40 dp
                .size(40.dp)
                // Clip image to be shaped as a circle
                .clip(CircleShape)
        )

        // Add a horizontal space between the image and the column
        // 水平间隔是8dp
        Spacer(modifier = Modifier.width(8.dp))

        Column {
            Text(text = msg.author)
            // Add a vertical space between the author and message texts
            //垂直间隔8dp
            Spacer(modifier = Modifier.height(4.dp))
            Text(text = msg.body)
        }
    }
}

3.设置Material Design的主题

要对相应的UI设置相应的主题,只需要将实现主题的compose方法,包裹着对应的UI组件。比如, ComposeTutorialTheme主题,包裹要被该主题修饰的UI组件:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            ComposeTutorialTheme {
                Surface(modifier = Modifier.fillMaxSize()) {
                    MessageCard(Message("Android", "Jetpack Compose"))
                }
            }
        }
    }
}

@Composable
fun ComposeTutorialTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    // Dynamic color is available on Android 12+
    dynamicColor: Boolean = true,
    content: @Composable () -> Unit
) 

主题,是由如下三个元素构建而成的: ColorTypography, and Shape

@Composable
fun MaterialTheme(
    colorScheme: ColorScheme = MaterialTheme.colorScheme,
    shapes: Shapes = MaterialTheme.shapes,
    typography: Typography = MaterialTheme.typography,
    content: @Composable () -> Unit
) 

使用主题色Color,修饰UI组件: 比如下面,将Image组件,修改为圆形,并且圆形边框的大小为1.5dp,边框颜色,设置为主题色的primary。

     Image(
            painter = painterResource(R.mipmap.profile_picture),
            contentDescription = null,
            modifier = Modifier
                .size(40.dp)
                .clip(CircleShape)
                .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
        )


使用Typography属性,对UI组件进行排版。比如:设置Text组件的style属性

    Column {
            Text(
                text = msg.author,
                color = MaterialTheme.colorScheme.secondary,
                style = MaterialTheme.typography.titleSmall
            )

            Spacer(modifier = Modifier.height(4.dp))

            Text(
                text = msg.body,
                style = MaterialTheme.typography.bodyMedium
            )
        }

shape,修饰UI组件的形状,通过使用Surface方法包裹UI组件,并提供相应的修改参数,就可以修改UI组件的形状。 比如:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            ComposeTutorialTheme {
                Surface(modifier = Modifier.fillMaxSize()) {
                    MessageCard(Message("Android", "Jetpack Compose"))
                }
            }
        }
    }
}

深色模式,Dark mode theme,和浅色模式light mode theme:

@Preview(name = "Light Mode")
@Preview(
    uiMode = Configuration.UI_MODE_NIGHT_YES,
    showBackground = true,
    name = "Dark Mode"
)
@Composable
fun PreviewMessageCard(){
    ComposeTutorialTheme {
        Surface {
            MessageCard(
                msg = Message("Lexi", "Take a look at Jetpack Compose, it's great!")
            )
        }
    }
}


4. 添加列表和动画

为了在界面上展示多个Item信息,我们将使用LazyColumn组件和LazyRow组件。使用这两个组件,只有Item展示在界面上的时候,才会渲染对应的UI组件,Item没有展示在界面上的时候,LazyColumn里面的组件是不会渲染的。 使用例子:

@Composable
fun Conversations(messages: List<Message>) {
    LazyColumn {
        items(messages.size) { index ->
            MessageCard(messages[index], PaddingValues(8.dp))
        }
    }
}

@Preview( name = "Light Mode")
@Composable
fun PreviewConversations() {
    ComposeTutorialTheme {
        Conversations(SampleData.conversationSample)
    }
}

LazyColumn有一个子类items,它接收的参数是List。并且items的lamda,会接收一个index参数,通过这个参数就可以获取到对应的Message实例。简单的说,就是,系统会对提供的参数List,对每个Message实例,调用lamda中的方法,在这里是MessageCard(messages[index], PaddingValues(8.dp))。

在展开消息时,显示动画效果。为了具备这样的能力,对于过长的消息,点击时,展开,并且具备展开的效果和背景颜色。To store this local UI state, you need to keep track of whether a message has been expanded or not.为了保存本地的UI状态,你要跟踪,当前Message时展开的,还是不展开的。 为了跟踪这个状态的变化,必须使用remember方法和mutableStateOf(false)变量。

重组: Composable functions can store local state in memory by using remember, and track changes to the value passed to mutableStateOf. Composables (and their children) using this state will get redrawn automatically when the value is updated. This is called recomposition. 通过使用remember方法,compose方法可以保存本地状态到内存中,并且可以跟踪传递给mutableStateOf的值的变化。也就是说,只要值传递给mutableStateOf,我们就可以跟踪该值的变化。然后,所有compose方法,使用该状态的,都会自动更新,当该状态发生变化的时候。这叫做重组。 例子:

@Composable
fun MessageCard(msg: Message, padding:PaddingValues) {
    // Add padding around our message
    // 内部的间距,padding是8dp
    Row(modifier = Modifier.padding(padding)) {
        Image(
            painter = painterResource(R.mipmap.profile_picture),
            contentDescription = null,
            modifier = Modifier
                .size(40.dp)
                .clip(CircleShape)
                .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
        )

        // We keep track if the message is expanded or not in this
        // variable
        var isExpanded by remember { mutableStateOf(false) }


        // We toggle the isExpanded variable when we click on this Column
        Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
            Text(
                text = msg.author,
                color = MaterialTheme.colorScheme.secondary,
                style = MaterialTheme.typography.titleSmall
            )

            Spacer(modifier = Modifier.height(4.dp))

            Surface(
                shape = MaterialTheme.shapes.medium,
                shadowElevation = 1.dp,
            ) {
                Text(
                    text = msg.body,
                    modifier = Modifier.padding(all = 4.dp),
                    // If the message is expanded, we display all its content
                    // otherwise we only display the first line
                    maxLines = if (isExpanded) Int.MAX_VALUE else 1,
                    style = MaterialTheme.typography.bodyMedium
                )
            }
        }
    }
}

在这里Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) ,状态值isExpanded会根据点击的结果,发生变化;变化之后,在任何使用这个状态值的地方,compose方法,都会发生重组,就是UI重新绘制。比如: maxLines = if (isExpanded) Int.MAX_VALUE else 1,

    Text(
                    text = msg.body,
                    modifier = Modifier.padding(all = 4.dp),
                    // If the message is expanded, we display all its content
                    // otherwise we only display the first line
                    maxLines = if (isExpanded) Int.MAX_VALUE else 1,
                    style = MaterialTheme.typography.bodyMedium
                )
    

** 为背景颜色添加动画效果,**

例子:

     // We keep track if the message is expanded or not in this
        // variable
        var isExpanded by remember { mutableStateOf(false) }

        // surfaceColor will be updated gradually from one color to the other
        val surfaceColor by animateColorAsState(
            if (isExpanded) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface,
        )
    

// We toggle the isExpanded variable when we click on this Column
Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
    Text(
        text = msg.author,
        color = MaterialTheme.colorScheme.secondary,
        style = MaterialTheme.typography.titleSmall
    )

    Spacer(modifier = Modifier.height(4.dp))

    Surface(
        shape = MaterialTheme.shapes.medium,
        shadowElevation = 1.dp,
        // surfaceColor color will be changing gradually from primary to surface
        color = surfaceColor,
        // animateContentSize will change the Surface size gradually
        modifier = Modifier.animateContentSize().padding(1.dp)
    ) {
        Text(
            text = msg.body,
            modifier = Modifier.padding(all = 4.dp),
            // If the message is expanded, we display all its content
            // otherwise we only display the first line
            maxLines = if (isExpanded) Int.MAX_VALUE else 1,
            style = MaterialTheme.typography.bodyMedium
        )
    }
}
    

原文来自: developer.android.com/develop/ui/…