• Android Jetpack-Compose相关


    封面

    Android Jetpack-Compose相关

    一、什么是Compose?

    Jetpack Compose 是用于构建原生 Android 界面的新工具包。它使用更少的代码、强大的工具和直观的 Kotlin API,可以帮助您简化并加快 Android 界面开发,打造生动而精彩的应用。它可让您更快速、更轻松地构建 Android 界面。

    (注:想要使用Compose编写程序需要先把你的AndroidStudio升级到Android Studio Arctic Fox 或更高版本。)

    二、Compose特点

    更少的代码

    与使用 Android View 系统(按钮、列表或动画)相比,Compose 可使用更少的代码实现更多的功能。

    代码体量更小,编写代码只需要采用 Kotlin,而不必拆分成 Kotlin 和 XML 部分。使用 Compose 编写的代码都很简洁且易于维护。“Compose 的布局系统在概念上更简单,因此可以更轻松地推断。查看复杂组件的代码也更轻松。

    直观

    Compose 使用声明性 API。可以构建不与特定 activity 或 fragment 相关联的小型无状态组件。

    加速开发

    Compose 与所有的现有代码兼容:您可以从 View 调用 Compose 代码,也可以从 Compose 调用 View。

    功能强大

    Compose 不仅解决了声明性界面的问题,还改进了无障碍功能 API、布局等各种内容。将设想变为现实所需的步骤更少了。利用 Compose,可以轻松快速地通过动画让应用变得生动有趣。无论是使用 Material Design 还是自己的设计系统进行构建,Compose 都可以灵活地实现所需的设计。

    三、如何使用Compose?

    Compose只是注解接口,内部原理在自己看懂后,会在往后出一篇介绍原理,现在我们简单介绍如何使用。温馨提示Compose对学习过flutter的开发者来说非常容易入门。
    Compose支持的布局:
    一、Column(列排列的组件);
    二、Row(行排列的组件);
    三、Statck(叠加的组件);
    四、Scaffold(Scaffold、MaterialTheme等是MaterialDesign风格的组件等。这里简单只写出4个是因为这对于学习过flutter的开发者来说,它们4个已经是非常熟悉的老朋友了。
    和flutter一样,Compose让开发者可以专心使用代码写开发UI界面,无需再写一份xml代码。

    声明依赖项

    如需添加 Compose 的依赖项,您必须将 Google Maven 制品库添加到项目中。

    在应用或模块的 build.gradle 文件中添加所需工件的依赖项:

    说明
    compose.animation在 Jetpack Compose 应用中构建动画,丰富用户体验。
    compose.compiler借助 Kotlin 编译器插件,转换 @Composable functions(可组合函数)并启用优化功能。
    compose.foundation使用现成可用的构建块编写 Jetpack Compose 应用,还可扩展 Foundation 以构建您自己的设计系统元素。
    compose.material使用现成可用的 Material Design 组件构建 Jetpack Compose UI。这是更高层级的 Compose 入口点,旨在提供与 www.material.io 上描述的组件一致的组件。
    compose.material3使用 Material Design 3(下一代 Material Design)组件构建 Jetpack Compose 界面。Material 3 中包括了更新后的主题和组件,以及动态配色等 Material You 个性化功能,旨在与新的 Android 12 视觉风格和系统界面相得益彰。
    compose.runtimeCompose 编程模型和状态管理的基本构建块,以及 Compose Compiler 插件针对的核心运行时。
    compose.ui与设备互动所需的 Compose UI 的基本组件,包括布局、绘图和输入。

    四、使用Compose

    使用Compose第一个基本程序:

    ​
    package com.example.compose
    
    import android.os.Bundle
    import androidx.activity.ComponentActivity
    import androidx.activity.compose.setContent
    import androidx.compose.foundation.layout.fillMaxSize
    import androidx.compose.material3.MaterialTheme
    import androidx.compose.material3.Surface
    import androidx.compose.material3.Text
    import androidx.compose.runtime.Composable
    import androidx.compose.ui.Modifier
    import androidx.compose.ui.tooling.preview.Preview
    import com.example.compose.ui.theme.ComposeTheme
    
    class MainActivity : ComponentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent {
                ComposeTheme {
                    // A surface container using the 'background' color from the theme
                    Surface(
                        modifier = Modifier.fillMaxSize(),
                        color = MaterialTheme.colorScheme.background
                    ) {
                        Greeting("Android")
                    }
                }
            }
        }
    }
    
    @Composable
    fun Greeting(name: String) {
        Text(text = "Hello $name!")
    }
    
    @Preview(showBackground = true)
    @Composable
    fun DefaultPreview() {
        ComposeTheme {
            Greeting("Android")
        }
    }
    
    ​
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    效果图:
    Hello Android!

    五、例子

    创建一个项目

    创建1
    点击 Empty Compose Activity

    创建2
    然后改名字改成自己想要的 点击完成即可创建一个项目
    (注:我之前已经创建一个名字为Compose的项目所以下面有一个警告)

    跑马灯例子~
    添加启动类

    class MainActivity : ComponentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent {
                MyApplicationTheme {
                    // A surface container using the 'background' color from the theme
                    Surface(
                        modifier = Modifier.fillMaxSize(),
                        color = MaterialTheme.colors.background
                    ) {
                        TestScreen()
                    }
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    创建方法:

    @Composable
    fun MarqueeText(
        text: String,
        modifier: Modifier = Modifier,
        textModifier: Modifier = Modifier,
        gradientEdgeColor: Color = Color.White,
        color: Color = Color.Unspecified,
        fontSize: TextUnit = TextUnit.Unspecified,
        fontStyle: FontStyle? = null,
        fontWeight: FontWeight? = null,
        fontFamily: FontFamily? = null,
        letterSpacing: TextUnit = TextUnit.Unspecified,
        textDecoration: TextDecoration? = null,
        textAlign: TextAlign? = null,
        lineHeight: TextUnit = TextUnit.Unspecified,
        overflow: TextOverflow = TextOverflow.Clip,
        softWrap: Boolean = true,
        onTextLayout: (TextLayoutResult) -> Unit = {},
        style: TextStyle = LocalTextStyle.current,
    ) {
        // 创建Text控件方法,相当于@Composable fun createText(localModifier: Modifier)
        val createText = @Composable { localModifier: Modifier ->
            Text(
                text,
                textAlign = textAlign,
                modifier = localModifier,
                color = color,
                fontSize = fontSize,
                fontStyle = fontStyle,
                fontWeight = fontWeight,
                fontFamily = fontFamily,
                letterSpacing = letterSpacing,
                textDecoration = textDecoration,
                lineHeight = lineHeight,
                overflow = overflow,
                softWrap = softWrap,
                maxLines = 1,
                onTextLayout = onTextLayout,
                style = style,
            )
        }
    
        var offset by remember { mutableStateOf(0) }
        val textLayoutInfoState = remember { mutableStateOf(null) }
        LaunchedEffect(textLayoutInfoState.value) {
            val textLayoutInfo = textLayoutInfoState.value ?: return@LaunchedEffect
            if (textLayoutInfo.textWidth <= textLayoutInfo.containerWidth) return@LaunchedEffect
            if(textLayoutInfo.containerWidth == 0) return@LaunchedEffect
            // 计算播放一遍的总时间
            val duration = 7500 * textLayoutInfo.textWidth / textLayoutInfo.containerWidth// 父层不要有其他元素,不然这句很容易发生Error java.lang.ArithmeticException: divide by zero(除以零)
            // 动画间隔时间
            val delay = 1000L
    
            do {
                // 定义动画,文字偏移量从0到-文本宽度
                val animation = TargetBasedAnimation(
                    animationSpec = infiniteRepeatable(
                        animation = tween(
                            durationMillis = duration,
                            delayMillis = 1000,
                            easing = LinearEasing,
                        ),
                        repeatMode = RepeatMode.Restart
                    ),
                    typeConverter = Int.VectorConverter,
                    initialValue = 0,
                    targetValue = -textLayoutInfo.textWidth
                )
                // 根据动画帧时间,获取偏移量值。
                // 起始帧时间
                val startTime = withFrameNanos { it }
                do {
                    val playTime = withFrameNanos { it } - startTime
                    offset = animation.getValueFromNanos(playTime)
                } while (!animation.isFinishedFromNanos(playTime))
                // 延迟重新播放
                delay(delay)
            } while (true)
        }
    
        SubcomposeLayout(
            modifier = modifier.clipToBounds()
        ) { constraints ->
            // 测量文本总宽度
            val infiniteWidthConstraints = constraints.copy(maxWidth = Int.MAX_VALUE)
            var mainText = subcompose(MarqueeLayers.MainText) {
                createText(textModifier)
            }.first().measure(infiniteWidthConstraints)
    
            var gradient: Placeable? = null
    
            var secondPlaceableWithOffset: Pair? = null
            if (mainText.width <= constraints.maxWidth) {// 文本宽度小于容器最大宽度, 则无需跑马灯动画
                mainText = subcompose(MarqueeLayers.SecondaryText) {
                    createText(textModifier.fillMaxWidth())
                }.first().measure(constraints)
                textLayoutInfoState.value = null
            } else {
                // 循环文本增加间隔
                val spacing = constraints.maxWidth * 2 / 3
                textLayoutInfoState.value = TextLayoutInfo(
                    textWidth = mainText.width + spacing,
                    containerWidth = constraints.maxWidth
                )
                // 第二遍文本偏移量
                val secondTextOffset = mainText.width + offset + spacing
                val secondTextSpace = constraints.maxWidth - secondTextOffset
                if (secondTextSpace > 0) {
                    secondPlaceableWithOffset = subcompose(MarqueeLayers.SecondaryText) {
                        createText(textModifier)
                    }.first().measure(infiniteWidthConstraints) to secondTextOffset
                }
                // 测量左右两边渐变控件
                gradient = subcompose(MarqueeLayers.EdgesGradient) {
                    Row {
                        GradientEdge(gradientEdgeColor, Color.Transparent)
                        Spacer(Modifier.weight(1f))
                        GradientEdge(Color.Transparent, gradientEdgeColor)
                    }
                }.first().measure(constraints.copy(maxHeight = mainText.height))
            }
    
            // 将文本、渐变控件 进行位置布局
            layout(
                width = constraints.maxWidth,
                height = mainText.height
            ) {
                mainText.place(offset, 0)
                secondPlaceableWithOffset?.let {
                    it.first.place(it.second, 0)
                }
                gradient?.place(0, 0)
            }
        }
    }
    
    /**
     * 渐变侧边
     */
    @Composable
    private fun GradientEdge(
        startColor: Color, endColor: Color,
    ) {
        Box(
            modifier = Modifier
                .width(10.dp)
                .fillMaxHeight()
                .background(
                    brush = Brush.horizontalGradient(
                        0f to startColor, 1f to endColor,
                    )
                )
        )
    }
    
    private enum class MarqueeLayers { MainText, SecondaryText, EdgesGradient }
    
    /**
     * 文字布局信息
     * @param textWidth 文本宽度
     * @param containerWidth 容器宽度
     */
    private data class TextLayoutInfo(val textWidth: Int, val containerWidth: Int)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163

    调用方法:

    @Composable
    fun TestScreen() {
        val content = "FJNU哈哈哈哈哈啊哈哈哈哈哈哈哈哈哈"
        Column(modifier = Modifier.padding(top = 200.dp)) {
            MarqueeText(
                text = content,
                color = Color.Black,
                fontSize = 24.sp,
                modifier = Modifier
                    .padding(start = 50.dp, end = 50.dp)
                    .background(Color.White)
            )
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    最后记得在xml文件里面修改一下你自己改的名字即可

    最终实现效果

    jieguo1
    jieguo2

    (因为我不会做动图所以就分两次截图)…

    总结:

    Compose的所有控件都是独立于android平台(这里的独立是相对原生Android View系统而言的,指上层独立,底层还是依赖原声,这样设计是为了与原生View系统仍保持交互)。原生Android可以实现的都可以使用Compose来加速开发,无法做到原生Android做不到的。Compose与原生Android各有各的好处,Compose 对于大多数开发者指标产生的影响是积极的。考虑到这一点,再加上 Compose 大大提高了开发人员的生产力。对大部分人来说,Compose 无疑是 Android UI 开发的未来。

    作者:王润熙
    https://blog.csdn.net/qq_52903932/article/details/128149694?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22128149694%22%2C%22source%22%3A%22qq_52903932%22%7D

  • 相关阅读:
    橙狮体育 java开发 一面
    模式识别与机器学习复习 | 第4讲
    哈希表相关知识
    mac安装maven后mvn命令可用,一段时间后又遇到zsh: command not found: mvn的问题
    RNA剪接增强免疫检查点抑制疗效
    DataFrame基础知识
    Jenkins系列之pipeline语法介绍与案例
    Spring中的<lookup-method>标签的功能简介说明
    第一讲之递归与递推下篇
    PMO大会的主办方是PMO评论
  • 原文地址:https://blog.csdn.net/fjnu_se/article/details/128163203