Jetpack compose

开发语言kotlin

界面实现逻辑,通过composable函数实现各个组件,再通过布局讲设计的组件排版。

1.Composable组件函数

通过@Composable函数实现组件,再通过布局空间实现组件的位置。

#属于Activity的一个子类,用于实现Jetpack
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?){
        super.onCreate(savedInstanceState)
        //setContent 是 Android 开发中用于设置 Activity 或 Compose UI 内容的一个方法。
        //具体的含义和用法取决于你是使用传统的 View 系统还是使用 Jetpack Compose 构建 UI。
        //使用setContent能够调用Composable组件函数
        setContent {
            Mydemo1("Android")
        }
    }

    //要写一个 Composable 函数,我们需要添加一个 @Composable 的注解。
    @Composable
    fun Mydemo1(name: String) {
        Text(text = "Hello $name")
    }

    //预览Composable函数需要在 @Composable 之前添加 @Preview 注解。
    //并且不能在预览函数中不能存在任何参数
    @Preview
    @Composable
    fun PreviewMydemo1() {
        Mydemo1("Android")
    }
}

2.初识布局

在Jetpack里,布局空间类似于一个div(盒子),拥有像padding(内边距)等属性。布局的默认空间大小为能够容纳内部组件的最小空间大小。如果需要在不增加内部组件数量的情况下增大布局的大小可以通过使用空间占位组件(Spacer(Modifier.padding(horizontal = 8.dp))

    @Composable
    fun MessageCard(msg: Message) {
        Column {

            Spacer(Modifier.padding(vertical = 30.dp))
            Row(
                modifier = Modifier.padding(all = 8.dp) // 在我们的 Card 周围添加 padding
            ) {
                Spacer(Modifier.padding(horizontal = 10.dp))
                Image(
                    painterResource(id = R.drawable.mypng1),
                    contentDescription = "profile picture",
                    modifier = Modifier
                        .size(50.dp) // 改变 Image 元素的大小
                        .clip(CircleShape) // 将图片裁剪成圆形
                )
                Spacer(Modifier.padding(horizontal = 8.dp)) 
                // 添加一个空的控件用来填充水平间距,设置 padding 为 8.dp
                Column {
                    Text(text = msg.author)
                    Spacer(Modifier.padding(vertical = 4.dp))
                    Text(text = msg.body)
                }
            }
        }

    }

image-20240812115310246

3.Material design

3.1.Color(颜色)

Material Design 的颜色系统提供了一系列预定义的调色板和工具,帮助设计师选择适当的颜色组合,以确保良好的可读性和视觉效果。Material Design 提供了主色(Primary Colors)、次色(Secondary Colors)以及各种强调色(Accent Colors),每种颜色又有不同的明暗度。

  • 主色:用于界面的主要部分,如 App bar、按钮等。
  • 次色:用于强调与主色调形成对比的元素。
  • 强调色:用于对某些关键内容进行强调或突出,例如浮动操作按钮 (FAB)。

Material Design 提供的颜色调色板通常分为亮色和暗色模式,便于不同光线环境下的使用。

3.2.Typography(排版)

排版在 Material Design 中有明确的规定,包括字体的选择、字号、字重、行高等,以确保文本在各种设备上都具有良好的可读性和一致性。

  • 字体选择:Material Design 推荐使用 Google 的免费字体如 Roboto 和 Noto。
  • 文本样式:Material Design 提供了一系列文本样式,如标题(Headline)、副标题(Subtitle)、正文(Body)、按钮(Button)等,每种样式都有指定的字体大小、字重、行高等。
  • 可读性:排版系统设计时考虑了文本的可读性,例如通过适当的字距和行距,确保文本在各种设备和屏幕尺寸上都能清晰易读。

3.3.主题(Theme)

主题定义了应用程序中颜色、排版、形状等设计要素的整体风格。Material Design 提供了灵活的主题系统,使得开发者可以轻松地自定义应用的外观,以符合品牌的视觉风格。

  • 浅色主题和深色主题:Material Design 提供了浅色和深色两种主题,适应不同的光线条件和用户偏好。
  • 定制主题:开发者可以通过调整主色、次色、背景色、文本颜色等元素来自定义主题。
  • 形状与组件样式:主题还可以影响组件的形状(例如按钮的圆角)、阴影效果、以及各种 UI 元素的样式。

4.列表与动画

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?){
        super.onCreate(savedInstanceState)
        setContent {
            X810Theme {
//                MessageCard(msg = Message("2024/08/12", "开始学习Jetpack Compose"))
                Conversation(messages = SampleData.conversationSample)
            }
        }
    }

    data class Message(val author: String, val body: String)
    //要写一个 Composable 函数,我们需要添加一个 @Composable 的注解。
    @Composable
    fun MessageCard(msg: Message) {
        Row(
            modifier = Modifier.padding(all = 8.dp), // 在我们的 Card 周围添加 padding
        ) {
            Spacer(Modifier.padding(horizontal = 10.dp))
            Image(
                painterResource(id = R.drawable.a02),
                contentDescription = "这是一个测试图片",
                modifier = Modifier
                    .size(60.dp) // 改变 Image 元素的大小
                    .clip(CircleShape) // 将图片裁剪成圆形
                    .border(1.5.dp, MaterialTheme.colorScheme.secondary, shape = CircleShape)
            )
            Spacer(Modifier.padding(horizontal = 8.dp)) // 添加一个空的控件用来填充水平间距,设置 padding 为 8.dp
            var isExpanded by remember {
                mutableStateOf(false)
            }//定义一个状态用来识别
            
            val surfaceColor:Color by animateColorAsState(
                targetValue = if (isExpanded) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface
            )//定义一个变换的颜色,当状态改变的时候,颜色自动改变
            Column (
                modifier = Modifier.clickable { isExpanded = !isExpanded }
            ){
                Text(
                    text = msg.author,
                    color = MaterialTheme.colorScheme.secondary
                )
                Spacer(Modifier.padding(vertical = 4.dp))
                Surface (
                    shape = MaterialTheme.shapes.medium,
                    shadowElevation = 2.dp,
                    modifier = Modifier.animateContentSize()
                        .padding(1.dp),
                    color = surfaceColor
                ){
                    Text(
                        text = msg.body,
                        modifier = Modifier.padding(all = 4.dp),
                        style = MaterialTheme.typography.bodyMedium,
                        maxLines = if (isExpanded) Int.MAX_VALUE else 1
                    )
                }

            }
        }
    }
    @Composable
    fun Conversation(messages: List<Message>){
        LazyColumn {
            items(messages){message ->
                MessageCard(msg = message)
            }
        }

    }
    @Preview
    @Composable
    fun PreviewMydemo1() {
        MessageCard(msg = Message("Jetpack Compose 博物馆", "我们开始更新啦"))
    }
}

5.组合、继承和重组

text发生改变的时候,对于button不会参与重组,而是调用text的lambda函数会参加重组。对于text = "$text $text",并不意味着 text 会被赋值新的对象,因为 text 指向的 MutableState 实例是永远不会变的,变的只是内部的 value

@Composable
fun Foo() {
    var text by remember { mutableStateOf("") }
    Log.d(TAG, "Foo")
    Button(onClick = {
        text = "$text $text"
    }.also { Log.d(TAG, "Button") }) {
        Log.d(TAG, "Button content lambda")
        Text(text).also { Log.d(TAG, "Text") }
    }
}

点击后的日志如下:

2024-08-13 18:50:38.769 13906-13906 aaaa                    com.example.x810                     D  Foo
2024-08-13 18:50:38.769 13906-13906 aaaa                    com.example.x810                     D  Button
2024-08-13 18:50:38.786 13906-13906 aaaa                    com.example.x810                     D  Button content lambda
2024-08-13 18:50:38.789 13906-13906 aaaa                    com.example.x810                     D  Text
2024-08-13 18:51:04.462 13906-13906 aaaa                    com.example.x810                     D  Button content lambda
2024-08-13 18:51:04.462 13906-13906 aaaa                    com.example.x810                     D  Text

只有 非inline函数 才有资格成为重组的最小范围,理解这点特别重要!

将代码稍作改动,为 Text() 包裹一个 Box{...}

@Composable
fun Foo() {

    var text by remember { mutableStateOf("") }

    Button(onClick = { text = "$text $text" }) {
        Log.d(TAG, "Button content lambda")
        Box {
            Log.d(TAG, "Box")
            Text(text).also { Log.d(TAG, "Text") }
        }
    }
}

日志如下:

: Button content lambda
: Box
: Text

ColumnRowBox 乃至 Layout 这种容器类 Composable 都是 inline 函数,因此它们只能共享调用方的重组范围,也就是 Button 的 尾lambda。

如何通过缩小重组范围提高性能?

@Composable
fun Foo() {
    var text by remember { mutableStateOf("") }
    Button(onClick = { text = "$text $text" }) {
        Log.d(TAG, "Button content lambda")
        Wrapper {
            Text(text).also { Log.d(TAG, "Text") }
        }
    }
}
@Composable
fun Wrapper(content: @Composable () -> Unit) {
    Log.d(TAG, "Wrapper recomposing")
    Box {
        Log.d(TAG, "Box")
        content()
    }
}

通过自定义函数属于非inline函数,再通过调用自定义函数缩小重组范围。

日志如下:

2024-08-13 19:04:16.892 14438-14438 aaaa                    com.example.x810                     D  Text
2024-08-13 19:04:19.057 14438-14438 aaaa                    com.example.x810                     D  Text
2024-08-13 19:04:19.761 14438-14438 aaaa                    com.example.x810                     D  Text