Jetpack Compose 是用于构建原生 Android 界面的新工具包。它使用更少的代码、强大的工具和直观的 Kotlin API,可以帮助您简化并加快 Android 界面开发,打造生动而精彩的应用。它可让您更快速、更轻松地构建 Android 界面。
(注:想要使用Compose编写程序需要先把你的AndroidStudio升级到Android Studio Arctic Fox 或更高版本。)
更少的代码
与使用 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对学习过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.runtime | Compose 编程模型和状态管理的基本构建块,以及 Compose Compiler 插件针对的核心运行时。 |
compose.ui | 与设备互动所需的 Compose UI 的基本组件,包括布局、绘图和输入。 |
使用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")
}
}
效果图:
点击 Empty Compose Activity
然后改名字改成自己想要的 点击完成即可创建一个项目
(注:我之前已经创建一个名字为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()
}
}
}
}
}
创建方法:
@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)
调用方法:
@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)
)
}
}
最后记得在xml文件里面修改一下你自己改的名字即可
(因为我不会做动图所以就分两次截图)…
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