Android的Compose

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

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


可组合函数(Composable function)

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

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

添加文本元素

Kotlin 复制代码
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:

Kotlin 复制代码
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新增如下函数:

Kotlin 复制代码
@Preview
@Composable
fun PreviewMessageCard() {
    MessageCard("Android")
}

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


布局(Layout)

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

添加多个文本

Kotlin 复制代码
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,可以垂直排列元素,使其不再重叠文本。

Kotlin 复制代码
@Composable
fun MessageCard(msg: Message) {
    Column {
        Text(text = msg.author)
        Text(text = msg.body)
    }
}

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

添加图片元素

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

Kotlin 复制代码
@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属性,通过他我们可以设置组件的大小、间距、外观,甚至添加互动事件,如点击、触摸事件。

Kotlin 复制代码
@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:

Kotlin 复制代码
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!")
            )
        }
    }
}
  

颜色

Kotlin 复制代码
@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 可组合项中即可。

Kotlin 复制代码
@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(形状)

Kotlin 复制代码
@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颜色、文本和背景时,系统会自动适应深色背景。

Kotlin 复制代码
//可以在文件中以单独函数的形式创建多个预览,也可以向同一个函数中添加多个注解
@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。

Kotlin 复制代码
@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会自动重绘相关的组件。

Kotlin 复制代码
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
                )
            }
        }
    }
}

注:需要添加以下导入内容才能正确使用 Kotlin 的委托属性语法(by 关键字):
import androidx.compose.runtime.getValue

import androidx.compose.runtime.setValue

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

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

Kotlin 复制代码
@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
                )
            }
        }
    }
}
相关推荐
火柴就是我2 分钟前
mmkv的 mmap 的理解
android
没有了遇见7 分钟前
Android之直播宽高比和相机宽高比不支持后动态获取所支持的宽高比
android
shenshizhong42 分钟前
揭开 kotlin 中协程的神秘面纱
android·kotlin
vivo高启强1 小时前
如何简单 hack agp 执行过程中的某个类
android
沐怡旸1 小时前
【底层机制】 Android ION内存分配器深度解析
android·面试
你听得到112 小时前
肝了半个月,我用 Flutter 写了个功能强大的图片编辑器,告别image_cropper
android·前端·flutter
KevinWang_2 小时前
Android 原生 app 和 WebView 如何交互?
android
用户69371750013842 小时前
Android Studio中Gradle、AGP、Java 版本关系:不再被构建折磨!
android·android studio
杨筱毅3 小时前
【底层机制】Android低内存管理机制深度解析
android·底层机制
二流小码农3 小时前
鸿蒙开发:this的指向问题
android·ios·harmonyos