Android Compose 介绍与实践

这篇具有很好参考价值的文章主要介绍了Android Compose 介绍与实践。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

简介

Jetpack Compose 是 Google 官方 2019 年推出的UI框架,它可简化并加快 Android 的 UI 开发工作。使用更少的代码、强大的工具和直观的 Kotlin API,快速构建 App 的 UI。2021年迎来 Compose 的正式版,来了解一下这个官方强推的,布局机制、渲染机制、具体写法等可以说是全新的UI框架了。

先来看一段简单的 Compose 代码:

Column {
    Text("Hello world")
    Image()
}

OK这就是一个完整的UI界面了,对比原来定义在 xml 文件中的方式有着天壤之别,展现一个UI不再是去创建一个 TextView 之类的控件,而是变成了一次函数调用。虽然 Text 以大写开头,但它其实就是一个普通函数,严格说是个带 @Composable 注解的 Compose 函数:

@Composable
fun Text(...) {
    ...
}

来看一段完善一些的 Compose 代码:

@Composable
fun NewsStory() {
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            Image(
                painter = painterResource(R.drawable.header),
                contentDescription = null,
                modifier = Modifier
                    .height(180.dp)
                    .fillMaxWidth()
                    .clip(shape = RoundedCornerShape(4.dp)),
                contentScale = ContentScale.Crop
            )
            Spacer(Modifier.height(16.dp))

            Text(
                "A day wandering through the sandhills " +
                     "in Shark Fin Cove, and a few of the " +
                     "sights I saw",
                style = typography.h6,
                maxLines = 2,
                overflow = TextOverflow.Ellipsis)
            Text("Davenport, California", style = typography.body2)
            Text("December 2018", style = typography.body2)
        }
    }
}

以 Column、Row 代替 LinearLayout 等布局,以 Text、Image 等代替 TextView、ImageView 等控件,以 Modifier 等用作细节和修饰,所以其实 Compose 就是这样由多个函数调用组合起来,形成一个完整的 UI 界面。

Compose 改变了原有的基于 xml 和 View 的体系,纯在代码中实现页面UI,那么它比起老的方式有什么优势呢?

Compose 的特点

Jetpack Compose is Android’s modern toolkit for building native UI.

这是官方对 Compose 的定义,比起旧有体系,Compose 更加 “现代”。

现有的 Android 视图体系从 2010年以来没有发生太大变化,10年间无论从硬件规格还是APP复杂度都发生了极大变化,这套已经跑了10年的技术体系也已经显得有些落伍。

声明式 vs 命令式

说起 Compose 最大的特点,就是它是声明式的,而现有体系是命令式的

  • 命令式:现有视图体系要先将UI定义在 xml 文件中,当需要刷新时,需要在代码中先 findViewById 获取控件的引用,再下达如 setText、setVisibility 等命令,主动要求更新状态、刷新UI。

随着界面越来越复杂,控件越来越多,各控件状态难以保持同步,UI显示不一致的Bug频发。我们的很多精力花费在了如何能准确且不遗漏地更新所有该更新的控件上。

  • 声明式:声明式UI以一个“纯函数”的方式运行,当 State 变化时函数根据传入参数重新执行刷新UI。

Compose 会对界面中用到的数据自动进行订阅——不管是字符串还是图像还是别的什么,Compose 全部能够自动订阅——这样当数据改变的时候,Compose 会直接把新的数据更新到界面。

var text bymutableStateOf("Hello")

个人理解就是,只需要把界面给提前“声明”出来,先定义好在各种 state 时 UI 应该是个什么样子,当数据产生变化时,就不再需要去主动下达 setVisibility 等各种命令,界面会自动更新。

现有的 Data Binding 其实就是声明式的,但它通过数据更新的只能是界面元素的值,而 Compose 可以更新界面中的任何内容,包括界面的结构。

比如以下根据数据变化整个UI结构,Data Binding 就无法做到:

@Composable
fun MessageList(messages: List<String>) {
    Column {
        if (message.size == 0) {
            Text("No messages")
        } else {
            message.forEach { message ->
                Text(text=messag)
            }
        }
    }
}
高性能的重组(重绘)

在上面的例子里,当 message 发生变化时,MessageList 重新执行,这个过程叫重组(recomposition)。Composee 的 UI 正是通过不断重组来实现刷新。

但如果数据变化时会触发重组,大面积的重组是否会影响性能呢?

Compose 会通过在 Gap Buffer 这样的线性结构上进行 diff 实现局部刷新。 Gap Buffer 可以理解为一个树形结构经 DFS 处理后的数组,数组单元通过 key 标记其在树上的位置信息。Compose 在编译期为 Composable 生成带有位置信息的 key,存入到 Gap Buffer 数组的对应位置。运行时可以根据 key 来识别 Composable 节点是否发生了位置变化,以决定是否参与重组。同时,Gap Buffer 还会记录 Composable 对象关联的状态(State 或 Parameters),仅仅当关联状态变化时,Composable 才会参与重组,函数才会重新执行。

布局层级嵌套

做 Android 开发的都知道一个规矩:布局文件的界面层级要尽量地少,因为层级的增加会大幅拖慢界面的加载。这种拖慢的主要原因就在于各种 Layout 的重复测量。虽然重复测量对于布局过程是必不可少的,但这也确实让界面层级的数量对加载时间的影响变成了指数级。

而 Compose 是不怕层级嵌套的,因为它从根源上解决了这种问题。它解决的方式也非常巧妙而简单——它不许重复测量。

Compose 通过一种叫做 Intrinsic Measurement(固有特性测量)的机制,避免了随着层级增多,重复测量导致绘制时间指数式增加的性能陷阱,也就是说,使用 Compose 时疯狂嵌套,和把所有组件写在同一层级里,性能上是一样的!这是比起原体系的一大进步。

配合其他 Jetpack 组件
@Composable
fun ConversationScreen() {
    val viewModel: ConversatioinViewModel = viewModel()
    val message by viewModel.messages.observeAsState()
    MessageLit(messages)
}

@Composable
fun MessageList(message: List<String>){
    ...
}

Compose 可以配合现有 Jetpack 组件的使用,例如 ViewModel、LiveData 等,对于一个标准的 Jetpack MVVM项目,将很容易将 UI 部分替换成 Compose。

Composalbe 中调用 viewModel() 可以获取当前 Context 的 ViewModel, observeAsState() 将 LiveData 转换为 Compose State 并建立绑定。当 LiveData 变化时,ConversationScreen 会发生重组,内部的 MessageLit 、MessageItem 由于依赖了参数 messages,都会参与重组。

功能完备的UI系统

Compose目前的 UI 系统功能完备,可以完全覆盖 Android 现有视图系统的所有能力。

  • 各种UI组件:所有常见的UI组件在 Compose 中都能找到对应实现,甚至Card、Fab、AppBar等 Material Designe 的控件也一应俱全、开箱即用 。

  • 列表 List:Compose 的列表非常简单,无需再写烦人的 Adapter。

@Composable
fun MessageList(list: List<Message>) {
    Column {  
        LazyList { // this :LazyListScope
            items(list) { item ->
                when(item.type) {
                    Unread -> UnreadItem(message)
                    Readed -> ReadedItem(message)
                }
            }
        }
    }
}
  • 布局 Layout:Compose 提供了多种容器类Composalbe,可以对子组件进行布局,简单易用且功能强大。

  • Row ≈ Horizontal LinearLayout

  • Column ≈ Vertical LinearLayout

  • Box ≈ FragmeLayout

  • 自定义布局:通过简单的函数调用完成 measure 和 layout 过程。

Layout(
    content = content,
    modifier = modifier
) { measurables, constraints ->
    // Measure the composables
    val placeables = measurable.measure(constraints)
    // Layout the comopsables
    layout(width, height) {
        placeables.forEach { placeable ->
            placeable.place(x, y)
        }
    }
}
  • Modifier 操作符:Compose 通过一系列链式调用的 Modifier 操作符来装饰 Composable 的外观。操作符种类繁多,例如 size、backgrounds、padding 的设置以及 click 事件的添加等。

  • 动画 Animatioin:Compose 动画也是基于 State 驱动不断重组完成的。

@Composable
fun AnimateAsStateDemo() {
    var isHighLight by remember { mutableStateOf(false) }
    val color by animateColorAsState (
        if (isHighLight) Red else Blue,
    )    
    val size by animateDpAsState (
        if (isHighLight) LargeSize else SizeSize,
    )
    Box(Modifier.size(size).background(color))
}
开发中预览

目前的基于 xml 的预览效果很鸡肋,导致很多开发者都习惯于实机运行查看UI。Compose 预览机制可以做到与真机无异,真正的所见所即得。

预览时只需创建一个无参的 Composalbe,并添加 @Preview 注解即可。

@Preview
@Composable
fun PreviewGreeting() {
    Greeting("Android")
}
与现有体系良好的互操作性

Compose 能够与现有 View 体系能一起使用,比如在现有布局中使用 Compose,或在 Compose 布局中使用旧视图体系。所以迁移到 Compose 很方便,可以为一个已有项目先引入 Compose,再逐渐切换,不要求一次性将旧UI全替换为新的,有很大的缓冲空间。

实践

现在来尝试动手写一个简单的 Compose 界面。

配置 Kotlin
plugins {
    id("org.jetbrains.kotlin.android") version "1.4.32"
}
配置 Gradle
android {
    defaultConfig {
        ...
        minSdkVersion(21)
    }

    buildFeatures {
        // Enables Jetpack Compose for this module
        compose = true
    }
    ...

    // Set both the Java and Kotlin compilers to target Java 8.

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = "1.8"
        useIR = true
    }

    composeOptions {
        kotlinCompilerVersion = "1.4.32"
        kotlinCompilerExtensionVersion = "1.0.0-beta07"
    }
}
添加 Jetpack Compose 工具包依赖项
dependencies {
    implementation("androidx.compose.ui:ui:1.0.0-beta07")
    // Tooling support (Previews, etc.)
    implementation("androidx.compose.ui:ui-tooling:1.0.0-beta07")
    // Foundation (Border, Background, Box, Image, Scroll, shapes, animations, etc.)
    implementation("androidx.compose.foundation:foundation:1.0.0-beta07")
    // Material Design
    implementation("androidx.compose.material:material:1.0.0-beta07")
    // Material design icons
    implementation("androidx.compose.material:material-icons-core:1.0.0-beta07")
    implementation("androidx.compose.material:material-icons-extended:1.0.0-beta07")
    // Integration with observables
    implementation("androidx.compose.runtime:runtime-livedata:1.0.0-beta07")
    implementation("androidx.compose.runtime:runtime-rxjava2:1.0.0-beta07")

    // UI Tests
    androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.0.0-beta07")
}
用 Compose 来重新写一下 账号登录 页面:
class MainActivity : ComponentActivity() {

    private val isLogInning = mutableStateOf(false)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            LoginScreen(isLogInning)
        }
    }

    private fun login() {
        isLogInning.value = true
        Toast.makeText(this, "登录中", Toast.LENGTH_SHORT).show()
    }

    @Composable
    fun LoginScreen(isLogInning: MutableState<Boolean> = mutableStateOf(false)) {
        Column {
            Image(
                painter = painterResource(R.drawable.titlebar_back_light),
                contentDescription = null,
                modifier = Modifier
                    .height(40.dp)
                    .width(15.dp)
                    .absoluteOffset(10.dp)
            )
            Text(
                text = "登录TP-LINK ID",
                fontSize = 30.sp,
                fontWeight = FontWeight.Bold,
                modifier = Modifier.padding(10.dp)
            )
            TextField(
                value = TextFieldValue(),
                onValueChange = {},
                placeholder = { Text(text = "TP-LINK ID") },
                colors = TextFieldDefaults.textFieldColors(
                    backgroundColor = Color.White,
                    placeholderColor = Color.LightGray
                ),
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(horizontal = 10.dp)
            )
            TextField(
                value = TextFieldValue(),
                onValueChange = {},
                placeholder = { Text(text = "密码") },
                colors = TextFieldDefaults.textFieldColors(
                    backgroundColor = Color.White,
                    placeholderColor = Color.LightGray
                ),
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(horizontal = 10.dp)
            )
            Text(
                text = "忘记密码",
                fontSize = 16.sp,
                color = Color.Gray,
                textAlign = TextAlign.End,
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(horizontal = 10.dp, vertical = 20.dp)
            )
            Button(
                content = {
                    if (isLogInning.value)
                        Text("登录中...")
                    else
                        Text("登录")
                },
                onClick = { login() },
                colors = ButtonDefaults.buttonColors(
                    backgroundColor =
                        if (isLogInning.value)
                            Color(0xFFA6B7F7)
                        else
                            Color(0xFF3C65FC),
                    contentColor = Color.White
                ),
                modifier = Modifier
                    .height(65.dp)
                    .fillMaxWidth()
                    .padding(10.dp)
            )
            Row {
                Text(
                    text = "新用户注册",
                    fontSize = 16.sp,
                    color = Color(0xFF3C65FC),
                    modifier = Modifier.padding(10.dp)
                )
                Text(
                    text = "暂不登录",
                    fontSize = 16.sp,
                    color = Color.Gray,
                    modifier = Modifier
                        .padding(10.dp)
                        .offset(205.dp)
                )
            }
        }
    }

    @Preview(showBackground = true)
    @Composable
    fun DefaultPreview() {
        LoginScreen()
    }
}
  • 简单使用 Column 做一个垂直线性布局

  • .dp 是 Int 的扩展函数,方便在代码中直接定义以 dp 为单位的数值

  • 运用 Modifier 的 offSet、padding、fillMaxWidth 等调整组件细节

  • 定义一个 MutableState 类型的 isLogInning 作为参数传入 Composable 函数 LoginScreen,让 Compose 自动订阅,自动根据这个值的变化而重组 LoginScreen 方法,更新UI显示

  • 当登录按钮点击时回调 onClick 方法,改变 isLogInning 的值

  • 当 isLogInning 的值改变时,能看到“登录”按钮自动改变了颜色,并变为“登录中...”,而我们并没有主动去命令它 setText 和 setBackground。这和 Data Binding 类似,但更加简单和灵活文章来源地址https://www.toymoban.com/news/detail-488748.html

到了这里,关于Android Compose 介绍与实践的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android Jetpack Compose之RadioGroup的使用

    Android Jetpack Compose是一个现代化的UI工具包,帮助开发者以声明式的方式构建出美观且功能强大的Android应用。在本文中,我们将详细介绍其中的一个重要组件—— RadioGroup 。 一. RadioGroup简介 Jetpack Compose中并没有像传统View系统中那样直接提供 RadioGroup ,但我们可以很方便地通

    2024年02月06日
    浏览(40)
  • 探索Android Jetpack Compose的Surface组件

    随着声明性 UI 框架 Jetpack Compose 的出现,Android 开发变得更加简洁和直观。在这篇博客中,我们将深入探讨其中的一项基本构建块 —— Surface 组件,了解它如何影响 UI 的显示和设计。 一、Jetpack Compose和Surface组件 二、Surface组件的基本使用 三、影响Surface的属性 一、Jetpack Co

    2024年02月11日
    浏览(39)
  • 对于Android开发,我们为何要学Jetpack Compose?

    Jetpack Compose 是用于构建原生 Android 界面的新工具包。它可简化并加快 Android 上的界面开发,使用更少的代码、强大的工具和直观的 Kotlin API,快速让应用生动而精彩。Compose 使用全新的组件——可组合项 (Composable) 来布局界面,使用修饰符 (Modifier) 来配置可组合项。 为何Jetp

    2024年02月10日
    浏览(31)
  • Android Jetpack Compose中使用字段验证的方法

    数据验证是创建健壮且用户友好的Android应用程序的关键部分。随着现代UI工具包Jetpack Compose的引入,处理字段验证变得更加高效和直观。在这篇文章中,我们将探讨如何在Android应用中使用Jetpack Compose进行字段验证。 字段验证是确保用户在各种输入字段中输入的数据符合特定

    2024年02月11日
    浏览(32)
  • Android Jetpack Compose实现轮播图效果

    在最近思索如何使用Compose方式改进我的开源TMDB电影列表应用程序的主屏幕时,一个激动人心的概念浮现在我的脑海中——为什么不整合一个吸引人的轮播图来展示即将上映的电影呢?在本文中,我将分享我的开发和实现自定义轮播图的经历,提供涉及不同步骤的见解。 首先

    2024年02月08日
    浏览(82)
  • Google推出Telecom Jetpack库,让Android通话应用创建更简单

    Telecom Jetpack库的最新Alpha版本已经推出。该库提供了多个API,以简化Android开发者创建语音和/或视频通话应用程序的过程,支持常见功能,例如接听/拒绝、音频路由等等。 https://developer.android.google.cn/jetpack/androidx/releases/core?hl=zh-cn#core-telecom_version_10_2 首先,新库会处理声明前台

    2024年01月22日
    浏览(49)
  • Android Jetpack Compose之确定重组范围并优化重组

    Compose的重组是智能的,Composable函数在进行重组时会尽可能的跳过不必要的重组,只对需要变化的UI进行重组。那Compose是如何认定UI需要变化呢?或者换句话说Compose是如何确定重组的范围呢。如果重组随意的发生,那么对UI的性能会是一个很不稳定的状态,时而好,时而坏。而

    2024年02月07日
    浏览(36)
  • Android Jetpack Compose之底部导航栏的实现

    写过一段Android jetpack compose 界面的小伙伴应该都用过Compose的脚手架 Scaffold ,利用它我们可以很快的实现一个现代APP的主流界面架构,即一个带顶部导航栏和底部导航栏的界面架构,我们基于这个架构可以快速的搭建出我们想要的页面效果。而今天的文章就是要介绍如何实现

    2024年03月23日
    浏览(35)
  • Android笔记(六):JetPack Compose常见的UI组件

    Text显示的文本来源可以引用res-values-strings.xml中的资源,如第一个显示文本所示。 点击按钮前: 点击按钮后: 点击第一个圆角按钮不放时,显示为按钮:true Button有两方面需要注意: (1) Buttton有一个参数interactionSource,用来监听组件状态的事件源,通过它获取组件的状态来

    2024年02月04日
    浏览(36)
  • Android 在xml 布局中如何嵌套 Jetpack Compose

    最近在项目开发的过程中需要用到 Jetpack Compose,之前没有接触过Compose,所以项目一直没有用到Compose。通过查看官网发现Compose上手比较快,但是准备比较复杂的布局要转换成Compose 不是一件容易的事情。那有没有可能只是对成熟的项目中的xml 布局中的某一部分进行改造,让其

    2024年04月10日
    浏览(32)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包