您的当前位置:首页正文

Android的Compose

2024-11-13 来源:个人技术集锦

Jetpack Compose 是用于构建原生 Android 界面的新工具包,无需修改任何 XML 布局,也不需要使用布局编辑器。相反,只需调用可组合函数来定义所需的元素,Compose 编译器即会完成后面的所有工作。

简而言之,使用Compose,不再需要xml编写页面。


可组合函数(Composable function)

Compose是围绕可组合函数构建的,只需要描述应用界面的外观并提供数据依赖,而不必关注界面的构建过程(如初始化元素、将其附加到父项等)。而创建Composable function,只需要添加注解@Composable到函数名称前。

首先,我们构建创建一个应用:ComposeTutorial。在AS中选择Empty Activity创建。

添加文本元素

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {//在此处定义布局
            //此处的Text方法是由Compose界面库定义的文本Composable function
            Text("Hello world!")
        }
    }
}

setContent块定义了activity的布局,在此处我们添加了Text即“Hello World!”。

自定义可组合函数

如果需要将一个函数转换为Composable function,我们需要添加注解“@Composable”。

修改MainActivity:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MessageCard("Android")//使用我们自定义的Composable function
        }
    }
}

@Composable//添加注解,使该函数成为Composable function
fun MessageCard(name: String) {
    Text(text = "Hello $name!")
}

在AS中预览Composable function

借助@Preview注解,可以在AS中预览Composable function,无需安装到设备或虚拟器中。

唯一要求是该注解不能用于接收参数的函数中,因此在MainActivity新增如下函数:

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

在重新构建后,该函数没有被调用,应用本身不会改变,但是AS对于所有添加了@Preview注解的界面元素可以进行预览,点击如下两个按钮之一即可:


布局(Layout)

在此处我们实现一个简单的聊天界面,显示发送者和消息内容,点击消息时可缩放。

添加多个文本

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            //向Composable function中传入发送者名称和消息内容 
            MessageCard(Message("Android", "Jetpack Compose"))
        }
    }
}

//新建一个Message类,包含消息发送者和消息内容
data class Message(val author: String, val body: String)

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

@Preview
@Composable
fun PreviewMessageCard() {
    MessageCard(
        msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!")
    )
}

这段代码会在内容视图中创建两个文本元素。不过,由于未提供有关如何排列这两个文本元素的信息,因此它们会相互重叠,使文本无法阅读。

使用Column、Row、Box

Column,直译为“圆柱体、长列”。

使用该函数修改MessageCard,可以垂直排列元素,使其不再重叠文本。

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

同样的,可以使用Row函数水平排列元素,而使用Box函数可以堆叠元素。

添加图片元素

使用Resource Manager从照片库中导入图片,修改MessageCard:

@Composable
fun MessageCard(msg: Message) {
    //使用Row方法,水平排列图片和消息
    Row {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = "Contact profile picture",
        )
    
        Column {
            Text(text = msg.author)
            Text(text = msg.body)
        }
  
    }
  
}

配置布局(Modifier)

使用修饰符(Modifier)实现。

Compose中的每一个组件都具有Modifier属性,通过他我们可以设置组件的大小、间距、外观,甚至添加互动事件,如点击、触摸事件。

@Composable
fun MessageCard(msg: Message) {
    //在消息周围添加8dp的距离
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = "Contact profile picture",
            modifier = Modifier
                //设置图片大小
                .size(40.dp)
                //将图片修剪为圆形
                .clip(CircleShape)
        )

        //在图片和消息之间添加一个水平的空间(Spacer),间距为8dp
        Spacer(modifier = Modifier.width(8.dp))

        Column {
            Text(text = msg.author)
            // 在发送者和消息内容之间添加一个垂直的空间
            Spacer(modifier = Modifier.height(4.dp))
            Text(text = msg.body)
        }
    }
}

Material Design

Compose 旨在支持 Material Design 原则。它的许多界面元素都原生支持 Material Design。

使用

Jetpack Compose 原生提供 Material Design 3 及其界面元素的实现。我们使用 Material Design 样式改进MessageCard可组合项的外观。

在创建ComposeTutorial项目时,会同时创建一个名为“ComposeTutorialTheme”的Material主题,和一个来自Material Design 3的“Surface”。我们将用到这两位来封装MessageCard函数。

Material Design是围绕Color、Typography、Shape来构建的,我们将逐一添加这些元素。

修改MainActivity:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            //使用ComposeTutorialTheme和Surface封装MessageCard函数
            ComposeTutorialTheme {
                //将给定的组件或布局填满父容器的界面
                Surface(modifier = Modifier.fillMaxSize()) {
                    MessageCard(Message("Android", "Jetpack Compose"))
                }
            }
        }
    }
}

@Preview
@Composable
fun PreviewMessageCard() {
    //在@Preview中也要封装,沿用应用主题中定义的样式,保持统一
    ComposeTutorialTheme {
        Surface {
            MessageCard(
                msg = Message("Lexi", "Take a look at Jetpack Compose, it's great!")
            )
        }
    }
}
  

颜色

@Composable
fun MessageCard(msg: Message) {
   Row(modifier = Modifier.padding(all = 8.dp)) {
       Image(
           painter = painterResource(R.drawable.profile_picture),
           contentDescription = null,
           modifier = Modifier
               .size(40.dp)
               .clip(CircleShape)
               //为图像增加一个圆框
               .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
       )

       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               //通过MaterialTheme.colorScheme,使用已封装主题中的颜色设置样式
               color = MaterialTheme.colorScheme.secondary
           )

           Spacer(modifier = Modifier.height(4.dp))
           Text(text = msg.body)
       }
   }
}

Typography(排版)

MaterialTheme 中提供了 Material Typography 样式,只需将其添加到 Text 可组合项中即可。

@Composable
fun MessageCard(msg: Message) {
   Row(modifier = Modifier.padding(all = 8.dp)) {
       Image(
           painter = painterResource(R.drawable.profile_picture),
           contentDescription = null,
           modifier = Modifier
               .size(40.dp)
               .clip(CircleShape)
               .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       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(形状)

@Composable
fun MessageCard(msg: Message) {
   Row(modifier = Modifier.padding(all = 8.dp)) {
       Image(
           painter = painterResource(R.drawable.profile_picture),
           contentDescription = null,
           modifier = Modifier
               .size(40.dp)
               .clip(CircleShape)
               .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

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

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

           //将消息详情封装在Surface中,此时可自定义消息详情的大小、形状等布局
           Surface(shape = MaterialTheme.shapes.medium, shadowElevation = 1.dp) {
               Text(
                   text = msg.body,
                   modifier = Modifier.padding(all = 4.dp),
                   style = MaterialTheme.typography.bodyMedium
               )
           }
       }
   }
}

启用深色主题

又称夜间模式。由于支持Material Design,Jetpack Compose默认能够处理深色主题。

使用Material Design颜色、文本和背景时,系统会自动适应深色背景。

//可以在文件中以单独函数的形式创建多个预览,也可以向同一个函数中添加多个注解
@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", "Hey, take a look at Jetpack Compose, it's great!")
      )
    }
   }
}


列表和动画

创建消息列表(LazyColumn、LazyRow)

LazyColumn、LazyRow是Compose常见的两个组件,他们的优点是可以延迟加载。他们只会加载当前可见的列表项,并在滚动时动态回收其他项,这使得他们适用于展示大量数据或无限滚动的列表。

在使用中,会包含一个items子项。他接受LIst作为参数,并且其lambda会接收到参数。系统会针对提供的List的每个项调用此lambda。

@Composable
fun Conversation(messages: List<Message>) {
    LazyColumn {
        //items子项
        items(messages) { message ->
            MessageCard(message)
        }
    }
}

@Preview
@Composable
fun PreviewConversation() {
    ComposeTutorialTheme {
        Conversation(SampleData.conversationSample)
    }
}

在展开消息时显示动画效果

为了存储某条消息是否已展开,我们使用remember和mutableStateOf函数。

可组合函数可以使用remember将本地状态存储到内存中,并跟踪传递给mutableStateOf的值的变化。而mutableStateOf函数可以在可组合函数内部创建一个可变的状态,并将其与UI组件进行绑定。当状态值发生变化时,Compose会自动重绘相关的组件。

class MainActivity : ComponentActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContent {
           ComposeTutorialTheme {
               Conversation(SampleData.conversationSample)
           }
       }
   }
}

@Composable
fun MessageCard(msg: Message) {
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = null,
            modifier = Modifier
                .size(40.dp)
                .clip(CircleShape)
                .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
        )
        Spacer(modifier = Modifier.width(8.dp))

        // 在此变量中,跟踪当前消息是否展开
        var isExpanded by remember { mutableStateOf(false) }

        // 当点击该消息时,改变展开状态
        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),
                    // 若展开,全部显示;不展开,最大显示一行
                    maxLines = if (isExpanded) Int.MAX_VALUE else 1,
                    style = MaterialTheme.typography.bodyMedium
                )
            }
        }
    }
}

除了remember关键字,我们还可以使用rememberSaveable,它与前者类似,但存储的值可以在重新创建activity和进程后保存下来。需要注意的是,rememberSaveable适用于UI状态,如购物车的商品数量或选定的标签页,但不适用于过渡动画状态等。

同时我们可以加入颜色,在消息缩放时改变消息的颜色。但不能只是简单的改变消息的背景颜色,我们应当加入动画,使得变化时逐步更改。

相同的,在点击缩放时,我们也可以加上动画,使得缩放更加顺滑。

@Composable
fun MessageCard(msg: Message) {
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = null,
            modifier = Modifier
                .size(40.dp)
                .clip(CircleShape)
                .border(1.5.dp, MaterialTheme.colorScheme.secondary, CircleShape)
        )
        Spacer(modifier = Modifier.width(8.dp))

        var isExpanded by remember { mutableStateOf(false) }
        // 该变量会逐步更新颜色,animateColorAsState函数实现颜色之间的过渡动画效果
        val surfaceColor by animateColorAsState(
            if (isExpanded) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface,
        )

        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,
                color = surfaceColor,
                //加入animateContentSize,给缩放加入动画,更加顺滑
                modifier = Modifier.animateContentSize().padding(1.dp)
            ) {
                Text(
                    text = msg.body,
                    modifier = Modifier.padding(all = 4.dp),
                    maxLines = if (isExpanded) Int.MAX_VALUE else 1,
                    style = MaterialTheme.typography.bodyMedium
                )
            }
        }
    }
}
Top