android 官方推荐compose还是xml 安卓 compose_flutter

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.runtime

Compose 编程模型和状态管理的基本构建块,以及 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")
    }
}

效果图:

android 官方推荐compose还是xml 安卓 compose_Android_02

五、例子

创建一个项目

android 官方推荐compose还是xml 安卓 compose_android jetpack_03


点击 Empty Compose Activity

android 官方推荐compose还是xml 安卓 compose_Android_04


然后改名字改成自己想要的 点击完成即可创建一个项目

(注:我之前已经创建一个名字为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<TextLayoutInfo?>(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<Placeable, Int>? = 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文件里面修改一下你自己改的名字即可

最终实现效果

android 官方推荐compose还是xml 安卓 compose_Android_05


android 官方推荐compose还是xml 安卓 compose_ci_06

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

总结:

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