尽管它仍处于alpha阶段,但Compose已经在Android社区掀起波澜。
在本教程中,你将学习JetpackCompose的基本概念,比如:可组合函数,如何在屏幕上显示内容以及更新内容。在掌握了基本概念之后,你会继续使用JetpackCompose构建一个菜谱App,它展示了由Material-design风格的食谱列表,其中包含卡片、图像和文本。
注意:Compose仍然处于alpha阶段。不应该把它用于正式Apps,因为它的API还不够稳定,从而可能会破坏你的代码。
在使用Compose编写代码之前,请先思考为什么Android团队创建了这个库?
与Android中当前的UI构建过程相比,Compose具有三大优势。
以隐藏视图为例。当使用当前命令式的UI工具包,你通常使用findViewById()来获取你想要隐藏的View的一个引用,然后通过这个引用调用setVisibility()。
因此,在创建视图后,你不用再获取对视图的引用。相反,你只需重新运行使用不同参数创建视图的代码。
注意:当你更新某些内容时,Compose实际上不会重建整个视图。它会智能更新那些需要更新的部分。
Compose是一个不依赖操作系统的库。对开发者来说这是一大优势。
如果现在Google想更新LinearLayout组件,它需要发布一个新的Android系统版本。遗憾的是,由于碎片化问题导致很多人无法使用最新的Android版本。
这意味着你不能依赖新的操作系统发布多年并且大多数用户升级后再使用新的LinearLayout组件。
借助JetpackCompose,开发者可以在不依赖更新操作系统版本的情况下添加新功能。无论用户设备上的操作系统是什么版本,它都可以正常工作。
此外,与LinearLayout组件不同,发布新版本的JetpackCompose不会破坏现有应用程序,因为除非你愿意,否则你不必升级到最新版本的JetpackCompose。JetpackCompose也没有使用Android现有的UI工具包,因此它是一个全新的开始,它提供了一个机会来解决View层次结构存在的一些由来已久的问题。而一旦你升级到新版本的Android,作为操作系统一部分的LinearLayout组件也会随之升级,并且它可能会给依赖它的应用程序带来重大变化。
Compose库的构建方式使你可以在细分的、可重用的构建块中构建UI,而不是在Fragment或Activity中。这种可组合性使代码更加清晰明了并利于重用。
Android已经问世十多年了,它的UI创建代码开始显示出它的年代感。光是View类就有上万行代码并且还需要兼容大量遗留代码。
你可以在本文顶部原文信息中访问原文地址来获取本教程所需要的资源,也可以通过访问原文资源来获取。
使用金丝雀版本的AndroidStudio来打开你下载资源中的starter项目。
编译并运行,你将看到一个白色屏幕,其中包含一个HelloWorld文本。
现在打开app/build.gradle文件并查看dependencies代码块。你会看到四个有趣的依赖:
defcomposeVersion="1.0.0-alpha06"...implementation"androidx.compose.ui:ui:$composeVersion"implementation"androidx.compose.foundation:foundation:$composeVersion"implementation"androidx.ui:ui-tooling:$composeVersion"implementation"androidx.compose.material:material:$composeVersion"Compose不仅仅是你导入的一个库,它是包含不同库的一个套件,用于构建你可能想要的不同UI。在这个项目中,你将使用构建基本布局所需的基本构建代码块。
除了上述依赖,你还可以看到在android->buildFeatures代码块中compose被设置为true:
buildFeatures{composetrue}你已经了解了基本的JetpackCompose项目所需的依赖,现在,可以真正开始学习它了。
由于JetpackCompose是使用代码的方式来构建UI,所以你不需要任何XML文件。这意味着你不需要在Activity或Fragment中使用setContentView(),而是使用setContent()来设置UI。
现在,打开MainActivity.kt并使用下面的代码替换现有的setContentView()调用:
除了setContent()之外,上面的代码片段中还有另一个新玩家:Text()。
在JetpackCompose中,你使用被@Composable注解标记的函数来构建UI。如果你在Mac上使用Command-Click或者在Windows上使用Control-click来点击Text(),你将看到类似下面的内容:
@ComposablefunText(...)Text()实际上是被@Composable注解标记的一个函数。Text()可组合函数负责在屏幕上绘制文本。你可以把它认为是Compose版本的TextView。
注意:通常你应该使用驼峰方式来命名函数。当你创建可组合函数时,你应该使用大写的函数名称以明确的表示你正在构建一个可组合函数。类似于Flutter小部件的工作方式或KotlinCoroutine函数(如Job()的命名方式)。
编译并运行,你将看到屏幕中的Text():
你可以通过TextStyle来自定义你的文本。通过将现有的Text()替换为以下内容来尝试一下:
Text("Hello,World!",style=TextStyle(color=Color.Red))再次确保导入了合适的androidx.ui包。编译并运行,现在你将看到红色的文本。
使用JetpackCompose时,你将使用普通的Kotlin代码和函数参数而不是XML样式和属性来自定义你的UI。你将在下一节中亲自尝试。
JetpackCompose的最大好处之一是你可以使用许多细分函数以模块化方式构建UI,而不是为每个Activity使用一个巨大的XML文件。
现在你已经熟悉了Text,你可以创建你第一个@Composable函数了。
在MainActivity下面添加以下函数:
@ComposablefunGreeting(){Text("Hello,World!",style=TextStyle(color=Color.Red))}恭喜,你刚刚创建了第一个自定义可组合函数!
为了使用它,使用Greeting()替换在setContent()中对Text()的调用:
setContent{Greeting()}编译并运行,你将看到如之前耀眼般的红色文本。
使用大量细分函数是创建可在不同屏幕上重复使用的UI块的好方式。
不过,有件事需要特别牢记:你只能在另外一个@Composable函数中调用@Composable函数,否则,App将会崩溃。
这一点与KotlinCoroutines非常相似,你只能在另外一个挂起函数或协程中调用挂起函数。
通常,当你编写一个ActivityXML的UI时,你可以使用布局预览来查看你的视图,而无需构建和运行你的应用程序。
JetpackCompose附带了一个类似的工具。
在之前创建的Greeting()函数之上且@Composable之下添加@Preview:
@Composable@PreviewfunGreeting(){Text("Hello,World!",style=TextStyle(color=Color.Red))}导入注解后,你会在屏幕右侧的预览面板中看到可组合函数的预览。
每次更新正在预览的可组合函数时,都必须重新编译才能看到更新后的视图。只能预览不带任何参数的可组合函数。
你可以预览你的组件了,现在是时候学习如何使用布局了。
屏幕上只有一个Text并不能构成一个特别有趣的应用程序。然而,当屏幕上显示三个Text时应该会带来绝对引人入胜的体验!
更新Greeting()以使用Text()三次:
Text("Hello,World!",style=TextStyle(color=Color.Red))Text("Hello,SecondWorld!",style=TextStyle(color=Color.Red))Text("Hello,ThirdWorld!",style=TextStyle(color=Color.Red))猜想一下上述可组合函数会如何显示?编译并运行,然后在预览窗口中查看结果是否符合你的预期。
非常完美。
开个玩笑,它看起来非常糟糕!
没有任何东西控制这些Text的位置,因此它们都在彼此之上绘制,就好像它们位于FrameLayout中一样。幸运的是,JetpackCompose提供了大量布局可组合函数。
在上述情况中,你将使用Column可组合函数为上述的混淆添加秩序。
把Column想象成垂直的LinearLayout。它只是将其所有子可组合函数布局在一个垂直的列中。
更新Greeting()以将三个Text()包装在一个Column()中:
@Composable@PreviewfunGreeting(){Column{Text("Hello,World!",style=TextStyle(color=Color.Red))Text("Hello,SecondWorld!",style=TextStyle(color=Color.Red))Text("Hello,ThirdWorld!",style=TextStyle(color=Color.Red))}}Column有一个被@Composable标记的lambda块,在其中你可以定义垂直排列的子可组合函数。
在Greeting()中你添加了三个Text()来作为Column的子可组合函数。这种让可组合函数接受lambda以创建其他可组合函数的模式在JetpackCompose中很常见。甚至可以说这就是整个Compose的设计思想。
编译并运行,你将看到三个在垂直列中排列的Text。这看起来好多了。
除了Column()之外,你还可以使用Row()在水平行布局子可组合函数中。这看起来像一个水平方向的LinearLayout。
你将构建一个名为ComposableCookBook的菜谱App,而不是构建简单的红色文本,它会显示美味的食谱列表。该项目带有一个预定义的Recipe:
你的目标是使用JetpackCompose使ComposeCookbook应用看起来更好,通过创建一个UI,该UI在卡片中显示每个食谱,卡片顶部是食谱的图片,卡片下方是原料列表。
第一步,你需要创建一个显示单个食谱的可组合函数。
右键单击ComposableCookBook包并选择NewNewKotlinFile/Class。然后从列表中选择File并输入文件名RecipeCard。最后,在文件中添加如下所示的RecipeCard()可组合函数:
@ComposablefunRecipeCard(recipe:Recipe){Text(recipe.title)}现在,你只是使用Text()显示菜谱的名称。
由于RecipeCard()带有一个参数,所以你不能使用@Preview。然而,你可以创建另一个提供默认RecipeCard()的可组合函数。在RecipeCard()下面添加以下代码:
@Composable@PreviewfunDefaultRecipeCard(){RecipeCard(defaultRecipes[0])}现在你可以预览RecipeCard()了。
接下来,你将要在RecipeCard()名称的上方添加图片。
将RecipeCard()中的内容替换为以下代码:
//1valimage=imageResource(recipe.imageResource)//2Column(modifier=Modifier.fillMaxWidth()){//3Image(asset=image,contentScale=ContentScale.Crop,modifier=Modifier.fillMaxWidth().height(144.dp))Text(recipe.title)}确保导入所有可能标记为未解析引用的红色函数。
如此之小的代码块中有很多神奇之处!然后根据需要刷新预览。
以下是对上述代码的解释:
刷新预览,你会看到食谱卡片的雏形!
拉面看起来很好吃——但是你需要什么原料来烹饪它呢?下一个任务,你将创建一个原料列表。
要列出原料,你将使用Text()。由于在上一步你已经定义了一个Column,添加原料将会非常容易。
将以下代码添加到食谱名称的Text()下面:
for(ingredientinrecipe.ingredients){Text(ingredient)}JetpackCompose的一大优点是你可以使用普通的Kotlin代码来描述稍微复杂的UI细节。
在上述代码中,你使用for循环列出了所有包含原料的Text。如果你重建UI,你会在名称下方看到这道美味拉面餐的所有原料。太酷了,对不对?而且你不需要定义一个RecyclerView.Adapter和任意的ViewHolder。
拉面看起来确实很好吃,但食谱卡片本身看起来比较方正。接下来,你将为食谱卡片添加圆角,让它看起来更好看。
使用Surface()作为父级可以让你有选择的为子项添加圆角。使用下面的代码替换现有RecipeCard()的内容:
Surface(shape=RoundedCornerShape(8.dp),elevation=8.dp){valimage=imageResource(recipe.imageResource)Column(modifier=Modifier.fillMaxWidth()){Image(asset=image,contentScale=ContentScale.Crop,modifier=Modifier.fillMaxWidth().height(144.dp))Text(recipe.title)for(ingredientinrecipe.ingredients){Text("$ingredient")}}}Surface()处理绘制形状并为组件提供一个"海拔"高度。对于食谱卡片,你将使用带有RoundedCornerShape()和8dp"海拔"高度的Surface()。
刷新构建。预览现在应该显示一张圆形的卡片!
卡片已初具规模,但它缺少两件事:名称组件上的一些基本样式以及组件之间的一些间距。你将在下一步中处理上述问题。
首先在食谱名称Text()中添加文本样式:
Text(recipe.title,style=MaterialTheme.typography.h4)在这里,你使用style参数通过默认的MaterialTheme排版标题样式来设置文本样式。
要将间距添加到卡片,请将名称和原料Text()包装在另一个Column()中并添加以下修饰符:
Column(modifier=Modifier.padding(16.dp)){Text(recipe.title,style=MaterialTheme.typography.h4)for(ingredientinrecipe.ingredients){Text("$ingredient")}}除了使用修饰符来控制宽度和高度之外,你还可以使用它们为不同的@Composable添加间距。
刷新预览。你应该看到一张漂亮的食谱卡片:
你的食谱卡片现在看起来很棒。是时候让你的用户能够列出他们最喜欢的食谱了。
通常,你使用RecyclerView来创建一个列表。在JetpackCompose中,你可以使用LazyColumnFor@Composable来实现延迟实例化子项的滚动列表,就像使用RecyclerView一样,但只需少量的代码!
要实现食谱列表,再次右键单击根代码包,然后选择NewNewKotlinFile/Class。然后从列表中选择File并输入文件名称RecipeList。最后,将RecipeList()添加到文件中:
现在你有一个可组合函数来显示你的食谱列表,是时候将所有内容连接到你的MainActivity中并在设备上查看你的工作成果了!
打开到MainActivity并将setContent()的内容替换为以下内容:
RecipeList(defaultRecipes)编译并运行。你会看到令人垂涎欲滴的美味食谱列表。
App看起来还不错,但是你无法真正辨别每个食谱卡片。要使UI看起来清晰明了,你需要在子项之间添加一些间距。
再次打开RecipeCard文件,为RecipeCard函数添加一个Modifier参数,将此参数传递给Surface:
funRecipeCard(recipe:Recipe,modifier:Modifier){Surface(shape=RoundedCornerShape(8.dp),elevation=8.dp,modifier=modifier){...}}然后修改DefaultRecipeCard预览函数,增加一个修饰符:
@Composable@PreviewfunDefaultRecipeCard(){RecipeCard(defaultRecipes[0],Modifier.padding(16.dp))}最后,打开RecipeList文件,并为RecipeCard增加一个16dp的间距修饰符。RecipeList函数现在应该如下所示:
现在App唯一缺少的是工具栏!你将在最后一步添加一个工具栏。
拥有工具栏是Android应用程序的默认行为,所以我们就必须要添加它!使用下面的代码替换MainActivity文件中setContent()的内容:
//1Column(modifier=Modifier.fillMaxSize()){//2TopAppBar(title={Text("ComposableCookBook")})//3RecipeList(defaultRecipes)}以下是对上述代码的解释:
编译并运行,你会在顶部看到一个漂亮的带有ComposeableCookBook字样的新工具栏。
恭喜!你已经使用JetpackCompose构建了你的第一个App。
你现在已经体验了Android用户界面世界的一些最新和最伟大的变化。但是你只是了解了Compose提供的一些基本功能,并且在Compose稳定之前,其中许多API必然会发生重大变化。
在本教程中,你了解了:
guodongAndroid
普通Android开发者一枚,熟悉Java/Kotlin语言,仅此而已。