Jetpack Compose入门详解

  • 前排提醒
  • 前言(Compose是什么)
    • 1.实战准备
  • 一、优势与缺点
  • 二、前四课
  • 三、标准布局组件
    • 1.Column
    • 2.Row
    • 3.Box
  • 四、xml和compose混合使用 + livedata数据绑定
    • 1.xml和compose混合使用
    • 2.livedata数据绑定
  • 五.compose结合navigation使用
    • 1.集成导航
    • 2.传递参数
    • 3.深层链接
  • 六.Compose 中的 ConstraintLayout
  • 七.Compose 手写一个分享二维码弹窗
  • 八.Compose 设置颜色的三种方式
  • 九.Compose事件与状态简略介绍
  • 十.Compose中的预览@Preview与@PreviewParameter的使用
  • 十一.Compose中的获取Context
  • 十二.Compose 动画api之我的电子木鱼青春版
  • 总结

前排提醒

我知道点进来的人都是想学习JC的,所以可能都不知道环境怎么弄,事实上如果只是学习的话,安装了最新版的Android studio后,创建项目时就可以构建一个Jetpack Compose,用于学习是再好不过了

前言(Compose是什么)

提示:需要对原生xml布局有一定了解,另外它最好是配合Kotlin 使用更佳

借用官方的解释:Jetpack Compose 是用于构建原生 Android 界面的新工具包。它使用更少的代码、强大的工具和直观的 Kotlin API,可以帮助您简化并加快 Android 界面开发。

1.实战准备

因为是新东西,所以配置上和平常有点不一样,可以对照着加下依赖 和配置,有些不用的可以酌情添加,例如:navigation

app的build.gradle

  plugins {id 'com.android.application'id 'kotlin-android'
}android {compileSdk 31defaultConfig {applicationId "com.zyf.myjetpack"minSdk 22targetSdk 30versionCode 1versionName "1.0"testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}compileOptions {sourceCompatibility JavaVersion.VERSION_1_8targetCompatibility JavaVersion.VERSION_1_8}kotlinOptions {jvmTarget = '1.8'}buildFeatures {viewBinding true// Enables Jetpack Compose for this modulecompose true}composeOptions {kotlinCompilerExtensionVersion '1.1.1'}
}dependencies {implementation 'androidx.core:core-ktx:1.3.2'implementation 'androidx.appcompat:appcompat:1.2.0'implementation 'com.google.android.material:material:1.3.0'implementation 'androidx.constraintlayout:constraintlayout:2.0.4'implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'// Integration with activitiesimplementation 'androidx.activity:activity-compose:1.4.0'// Compose Material Designimplementation 'androidx.compose.material:material:1.1.1'// Animationsimplementation 'androidx.compose.animation:animation:1.1.1'// Tooling support (Previews, etc.)implementation 'androidx.compose.ui:ui-tooling:1.1.1'// Integration with ViewModelsimplementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.4.1'// UI TestsandroidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.1.1'// When using a AppCompat themeimplementation "com.google.accompanist:accompanist-appcompat-theme:0.16.0"// Lifecycles only (without ViewModel or LiveData)implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.1"implementation  "androidx.compose.runtime:runtime-livedata:1.1.1"implementation   "androidx.compose.ui:ui:1.1.1"implementation   "androidx.navigation:navigation-compose:2.4.1"testImplementation 'junit:junit:4.+'androidTestImplementation 'androidx.test.ext:junit:1.1.2'androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'}

project的build.gradle

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {repositories {google()jcenter()maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }}dependencies {classpath "com.android.tools.build:gradle:7.0.3"//1.6.10版本classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10"// NOTE: Do not place your application dependencies here; they belong// in the individual module build.gradle files}
}task clean(type: Delete) {delete rootProject.buildDir
}

一、优势与缺点

它和安卓传统xml布局相比,又拥有以下几点优势

  • 更少的代码

    编写更少的代码会影响到所有开发阶段:作为代码撰写者,需要测试和调试的代码会更少,出现 bug 的可能性也更小,您就可以专注于解决手头的问题;作为审核人员或维护人员,您需要阅读、理解、审核和维护的代码就更少。
    与使用 Android View 系统(按钮、列表或动画)相比,Compose 可让您使用更少的代码实现更多的功能。无论您需要构建什么内容,现在需要编写的代码都更少了。

  • 直观
    Compose 使用声明性 API,这意味着您只需描述界面,Compose 会负责完成其余工作。这类 API 十分直观 - 易于探索和使用:“我们的主题层更加直观,也更加清晰。我们能够在单个 Kotlin 文件中完成之前需要在多个 XML 文件中完成的任务,这些 XML 文件负责通过多个分层主题叠加层定义和分配属性。”(Twitter)

  • 加速开发
    Compose 与您所有的现有代码兼容:您可以从 View 调用 Compose 代码,也可以从 Compose 调用 View。大多数常用库(如 Navigation、ViewModel 和 Kotlin 协程)都适用于 Compose,因此您可以随时随地开始采用。“我们集成 Compose 的初衷是实现互操作性,我们发现这件事情已经‘水到渠成’。我们不必考虑浅色模式和深色模式等问题,整个体验无比顺畅。”

  • 功能强大
    利用 Compose,您可以凭借对 Android 平台 API 的直接访问和对于 Material Design、深色主题、动画等的内置支持,创建精美的应用:“Compose 不仅解决了声明性界面的问题,还改进了无障碍功能 API、布局等各种内容。将设想变为现实所需的步骤更少了”;您可以轻松快速地通过动画让应用变得生动有趣:“在 Compose 中添加动画效果非常简单,没有理由不去为颜色/大小/高度变化添加动画效果”(Monzo),“不需要任何特殊的工具就能制作动画,这与显示静态屏幕没有什么不同”(Square)。

上面都是官方文档的官话,下面是我自己的归纳,上面提到的优点我就不赘述

优点

他是一套全新的声明式UI,完全不同于传统所有组件继承于臃肿庞大的view,而是基于更底层的canvas,简单来说,就是它的性能要比安卓原生的xml布局要好,比如xml的多重布局嵌套导致的一些问题,相信安卓开发对复杂页面嵌套优化都头疼过,只要你使用Compose,就不会遇到这样的问题

缺点

目前还是一个新的东西,大部分公司都还没有将Compose 纳入到项目当中,一些将Compose 融入到项目中的细节还没有敲定,例如:如何优雅的将viewmodel与Compose 绑定用于显示UI;一些技术点还待开发人员熟悉

说的好听支持java,但是大家也就图一乐,现在安卓官方主推的是什么语言大家心里都有数

二、前四课

安卓官方文档推出了Jetpack Compose的四课内容,带我们从开始到构建一个简单的聊天屏幕,如果你拥有安卓xml布局和Kotlin 的基础,那么这将非常的简单
安卓官方Jetpack Compose 教程

成果类似下图:

该屏幕显示包含图片和文字的可展开的动画消息列表,使用 Material Design 原则设计,添加了深色主题,具有预览功能,所有内容只需不到 100 行代码!

以下是您目前为止所学的内容:

  • 定义可组合函数
  • 在可组合项中添加不同的元素
  • 使用布局可组合项构建界面组件
  • 使用修饰符扩展可组合项
  • 创建高效列表
  • 跟踪状态以及修改状态
  • 在可组合项上添加用户互动
  • 在展开消息时显示动画效果

三、标准布局组件

在许多情况下,我们只需使用 Compose 的标准布局元素即可。

1.Column

使用 Column 可将多个项垂直地放置在屏幕上。

@Composable
fun ArtistCard() {Column {Text("Alfred Sisley")Text("3 minutes ago")}
}

我们会得到如下布局

2.Row

同样,使用 Row 可将多个项水平地放置在屏幕上。Column 和 Row 都支持配置它们所含元素的对齐方式。

@Composable
fun ArtistCard(artist: Artist) {Row(verticalAlignment = Alignment.CenterVertically) {Image(/*这里是你的图片*/)Column {Text(artist.name)Text(artist.lastSeenOnline)}}
}

得到如下布局

3.Box

使用 Box 可将元素放在其他元素上。Box 还支持为其包含的元素配置特定的对齐方式。

@Composable
fun ArtistAvatar(artist: Artist) {Box {Image(/*这里是头像*/)Icon(/*这里是角标*/)}
}

得到如下布局

通常,您只需要这些构建块。您可以自行编写可组合函数,将这些布局组合成更精美的布局,让其适合您的应用。

四、xml和compose混合使用 + livedata数据绑定

1.xml和compose混合使用

在xml中嵌入composeView,通过id: compose_home 绑定布局 ,这个时候就可以和原生xml布局混合使用

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".ui.home.HomeFragment"><TextViewandroid:id="@+id/xml_home"android:text="我是原生xml"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginEnd="8dp"android:layout_marginStart="8dp"android:layout_marginTop="8dp"android:textAlignment="center"android:textSize="20sp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><androidx.compose.ui.platform.ComposeViewandroid:id="@+id/compose_home"android:layout_width="match_parent"android:layout_height="wrap_content"tools:ignore="MissingConstraints" />
</androidx.constraintlayout.widget.ConstraintLayout>

kt代码(这里博主用的Fragment示例)

class HomeFragment : Fragment() {private var _binding: FragmentHomeBinding? = null// This property is only valid between onCreateView and// onDestroyView.private val binding get() = _binding!!override fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?): View? {_binding = FragmentHomeBinding.inflate(inflater, container, false)val root: View = binding.root//这里绑定了ComposeViewval view = binding.composeHomeview.setContent {AppCompatTheme{HomePge()}}return root}override fun onDestroyView() {super.onDestroyView()_binding = null}@Preview@Composableprivate fun HomePge(){Column() {Text(text = "我是 Jetpack Compose")}}}

效果如下

2.livedata数据绑定

创建 一个ViewModel如下:

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModelclass HomeViewModel : ViewModel() {private val _count = MutableLiveData(0)val count: LiveData<Int> = _countfun add() {_count.value = _count.value?.plus(1)}}

在HomePge()页面中稍作改动

    @Preview@Composable/*    括号里的HomeViewModel 是初始化viewModel,实际并不需要在传参中带进来viewModel()为androidx.lifecycle.viewmodel.compose中方法,等同于ViewModelProvider(this).get(HomeViewModel::class.java)*/private fun HomePge(viewModel: HomeViewModel = viewModel()){Column() {Text(text = "我是 Jetpack Compose")//引用包import androidx.compose.runtime.livedata.observeAsState//.observeAsState()绑定 viewModel中的count值,当count发生改变Text组件将会重绘val count = viewModel.count.observeAsState()Text(text =count.value.toString(),style = MaterialTheme.typography.h5,fontSize = TextUnit.Unspecified,textAlign = TextAlign.Center,modifier = Modifier.fillMaxWidth().padding(horizontal = dimensionResource(R.dimen.dp20)).wrapContentWidth(Alignment.CenterHorizontally).clickable {//调用  viewModel的add()方法让其值+1viewModel.add()Toast.makeText(context,count.value.toString(), Toast.LENGTH_SHORT).show()})}}

效果图

五.compose结合navigation使用

1.集成导航

先创建三个页面,分别为HomePage,DashboardPage和NotificationPage

@Composablefun HomePage(){Text("This is HomePage",textAlign = TextAlign.Center,modifier = Modifier.fillMaxWidth().padding(horizontal = dimensionResource(R.dimen.dp20)).wrapContentWidth(Alignment.CenterHorizontally))}@Composablefun DashboardPage(){Text("This is DashboardPage",textAlign = TextAlign.Center,modifier = Modifier.fillMaxWidth().padding(horizontal = dimensionResource(R.dimen.dp20)).wrapContentWidth(Alignment.CenterHorizontally))}@Composablefun NotificationPage(){Text("This is NotificationPage",textAlign = TextAlign.Center,modifier = Modifier.fillMaxWidth().padding(horizontal = dimensionResource(R.dimen.dp20)).wrapContentWidth(Alignment.CenterHorizontally))}

为了让代码看起来稍微规范些,我们创建了一个RouteConfig

object  RouteConfig {/*** homePage路由*/const val ROUTE_HomePage = "Home"/*** dashboardPage路由*/const val ROUTE_DashboardPage = "Dashboard"/*** dashboardPage路由*/const val ROUTE_NotificationPage = "Notifications"
}

然后创建一个NavHost对象

@Composablefun MainNavHost(){NavHost(navController = ,startDestination = ,){}}

NavHost对象需要两个必传参数,一个是NavController,一个是起始路由地址,NavController 对象是 Navigation 组件的中心 API,我们可以通过 rememberNavController创建,代码如下所示:

import androidx.navigation.compose.rememberNavControllerval navController = rememberNavController()

我们接着往下写,我们先将navController作为参数传递进来,然后startDestination 设置HomePage为我们的启始路由,每一个composable中为页面添加路由(route)

    @Composablefun MainNavHost(navController:NavHostController){NavHost(navController = navController,startDestination = RouteConfig.ROUTE_HomePage,){composable(route = RouteConfig.ROUTE_HomePage,){HomePage()}composable(route = RouteConfig.ROUTE_DashboardPage,){DashboardPage()}composable(route = RouteConfig.ROUTE_NotificationPage,){NotificationPage()}}}
  • RouteConfig.ROUTE_HomePage 对应 HomePage()页面
  • RouteConfig.ROUTE_DashboardPage 对应 DashboardPage()页面
  • RouteConfig.ROUTE_NotificationPage 对应 NotificationPage()

学习过navigation的小伙伴们应该知道BottomNavigationView这个组件,compose中也有相对应的组件名为BottomNavigation,我们也这里使用到了,并通过navController.navigate()方法实现了页面之间的跳转,值得一提的是,navController依旧是传递进来的

 @Composablefun MyBottomNavigation(navController:NavHostController){BottomNavigation(Modifier.fillMaxWidth().height(64.dp)) {BottomNavigationItem(true,onClick = {navController.navigate(RouteConfig.ROUTE_HomePage)},modifier = Modifier.padding(5.dp),icon = {Image(painter = painterResource(R.drawable.ic_home_black_24dp),contentDescription = RouteConfig.ROUTE_HomePage,)},label = {Text(text = RouteConfig.ROUTE_HomePage,color = Color.Black,modifier = Modifier.wrapContentWidth(Alignment.CenterHorizontally))})BottomNavigationItem(false,onClick = {navController.navigate(RouteConfig.ROUTE_DashboardPage)},modifier = Modifier.padding(5.dp),icon = {Image(painter = painterResource(R.drawable.ic_dashboard_black_24dp),contentDescription = RouteConfig.ROUTE_DashboardPage,)},label = {Text(text = RouteConfig.ROUTE_DashboardPage,color = Color.Black,modifier = Modifier.wrapContentWidth(Alignment.CenterHorizontally))})BottomNavigationItem(false,onClick = {navController.navigate(RouteConfig.ROUTE_NotificationPage)},modifier = Modifier.padding(5.dp),icon = {Image(painter = painterResource(R.drawable.ic_notifications_black_24dp),contentDescription = RouteConfig.ROUTE_NotificationPage,)},label = {Text(text = RouteConfig.ROUTE_NotificationPage,color = Color.Black,modifier = Modifier.wrapContentWidth(Alignment.CenterHorizontally))})}}

最后我们在oncreate处将MainNavHost和MyBottomNavigation放在activity视图中,这里我使用了Scaffold脚手架,它的bottomBar方法可以将MyBottomNavigation置于底部

override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {//这里进行了navController的初始化val navController = rememberNavController()Scaffold(//设置底部导航栏bottomBar = {MyBottomNavigation(navController)}) {MainNavHost(navController)}}}

我们可以ctrl+右键看看Scaffold的源码,除了bottomBar 还有很多其他的参数可配置,感觉是比较方便的一个脚手架呢

完整代码,稍作改良

import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.zyf.myjetpack.R/*** @ProjectName : My jetpack* @Author : yifeng_zeng* @Time : 2022/6/28 19:47* @Description : Navigation+Compose*/
class NavigationActivity : AppCompatActivity(){override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {//这里进行了navController的初始化val navController = rememberNavController()Scaffold(//设置底部导航栏bottomBar = {MyBottomNavigation(navController)}) {MainNavHost(navController)}}}@Composablefun MyBottomNavigation(navController:NavHostController){BottomNavigation(Modifier.fillMaxWidth().height(64.dp)) {BottomNavigationItem(true,onClick = {navController.navigate(RouteConfig.ROUTE_HomePage)},modifier = Modifier.padding(5.dp),icon = {Image(painter = painterResource(R.drawable.ic_home_black_24dp),contentDescription = RouteConfig.ROUTE_HomePage,)},label = {Text(text = RouteConfig.ROUTE_HomePage,color = Color.Black,modifier = Modifier.wrapContentWidth(Alignment.CenterHorizontally))})BottomNavigationItem(false,onClick = {navController.navigate(RouteConfig.ROUTE_DashboardPage)},modifier = Modifier.padding(5.dp),icon = {Image(painter = painterResource(R.drawable.ic_dashboard_black_24dp),contentDescription = RouteConfig.ROUTE_DashboardPage,)},label = {Text(text = RouteConfig.ROUTE_DashboardPage,color = Color.Black,modifier = Modifier.wrapContentWidth(Alignment.CenterHorizontally))})BottomNavigationItem(false,onClick = {navController.navigate(RouteConfig.ROUTE_NotificationPage)},modifier = Modifier.padding(5.dp),icon = {Image(painter = painterResource(R.drawable.ic_notifications_black_24dp),contentDescription = RouteConfig.ROUTE_NotificationPage,)},label = {Text(text = RouteConfig.ROUTE_NotificationPage,color = Color.Black,modifier = Modifier.wrapContentWidth(Alignment.CenterHorizontally))})}}@Composablefun MainNavHost(navController:NavHostController){NavHost(navController = navController,startDestination = RouteConfig.ROUTE_HomePage,){composable(route = RouteConfig.ROUTE_HomePage,){HomePage()}composable(route = RouteConfig.ROUTE_DashboardPage,){DashboardPage()}composable(route = RouteConfig.ROUTE_NotificationPage,){NotificationPage()}}}@Composablefun HomePage(){Text("This is HomePage",textAlign = TextAlign.Center,modifier = Modifier.fillMaxWidth().padding(horizontal = dimensionResource(R.dimen.dp20)).wrapContentWidth(Alignment.CenterHorizontally))}@Composablefun DashboardPage(){Text("This is DashboardPage",textAlign = TextAlign.Center,modifier = Modifier.fillMaxWidth().padding(horizontal = dimensionResource(R.dimen.dp20)).wrapContentWidth(Alignment.CenterHorizontally))}@Composablefun NotificationPage(){Text("This is NotificationPage",textAlign = TextAlign.Center,modifier = Modifier.fillMaxWidth().padding(horizontal = dimensionResource(R.dimen.dp20)).wrapContentWidth(Alignment.CenterHorizontally))}}

效果图

2.传递参数

在集成导航的时候,我们只能进行简单的页面跳转,下面就在页面跳转时带上我们需要的参数,传递参数是要在路由上添加的,为了便于理解,我们新增一个参数的配置


/*** @ProjectName : My jetpack* @Author : yifeng_zeng* @Time : 2022/6/30 9:01* @Description : Navigation参数配置*/
object ParamsConfig {/*** 参数-name*/const val PARAMS_COME = "come"/*** 参数-age*/const val PARAMS_FROM = "from"
}

本来我们的home页面的路由如下

      NavHost(navController = navController,startDestination = RouteConfig.ROUTE_HomePage,){composable(route = RouteConfig.ROUTE_HomePage,){HomePage()}composable(route = RouteConfig.ROUTE_DashboardPage,){DashboardPage()}composable(route = RouteConfig.ROUTE_NotificationPage,){NotificationPage()}}

现在我们做一点点修改,为路由加上可选参数和不可选参数

  /** startDestination : 起始路由* */NavHost(navController = navController,startDestination = RouteConfig.ROUTE_HomePage +"?${ParamsConfig.PARAMS_COME}={${ParamsConfig.PARAMS_COME}}"+"?${ParamsConfig.PARAMS_FROM}={${ParamsConfig.PARAMS_FROM}}",){//ParamsConfig.PARAMS_COME与ParamsConfig.PARAMS_FROM都为可选参数composable(route = RouteConfig.ROUTE_HomePage +"?${ParamsConfig.PARAMS_COME}={${ParamsConfig.PARAMS_COME}}"+"?${ParamsConfig.PARAMS_FROM}={${ParamsConfig.PARAMS_FROM}}",arguments = listOf(navArgument(ParamsConfig.PARAMS_COME) {//如果不传的默认值defaultValue = "FIRST HOME"},navArgument(ParamsConfig.PARAMS_FROM) {defaultValue = 0//参数类型,不写默认为Stringtype = NavType.IntType })){val argument = requireNotNull(it.arguments)val come= argument.getString(ParamsConfig.PARAMS_COME)val from = argument.getInt(ParamsConfig.PARAMS_FROM)HomePage(come,from)}composable(//ParamsConfig.PARAMS_COME为必填参数,ParamsConfig.PARAMS_FROM为可选参数route = "${RouteConfig.ROUTE_DashboardPage}/{${ParamsConfig.PARAMS_COME}}" +"?${ParamsConfig.PARAMS_FROM}={${ParamsConfig.PARAMS_FROM}}",arguments = listOf(navArgument(ParamsConfig.PARAMS_COME) {},navArgument(ParamsConfig.PARAMS_FROM) {defaultValue = 999999type = NavType.IntType })){val argument = requireNotNull(it.arguments)val come= argument.getString(ParamsConfig.PARAMS_COME)val from = argument.getInt(ParamsConfig.PARAMS_FROM)DashboardPage(come,from)}composable(//ParamsConfig.PARAMS_COME为必填参数,ParamsConfig.PARAMS_FROM为可选参数route = "${RouteConfig.ROUTE_NotificationPage}/{${ParamsConfig.PARAMS_COME}}" +"?${ParamsConfig.PARAMS_FROM}={${ParamsConfig.PARAMS_FROM}}",arguments = listOf(navArgument(ParamsConfig.PARAMS_COME) {},navArgument(ParamsConfig.PARAMS_FROM) {defaultValue = 3type = NavType.IntType})){val argument = requireNotNull(it.arguments)val come= argument.getString(ParamsConfig.PARAMS_COME)val from = argument.getInt(ParamsConfig.PARAMS_FROM)NotificationPage(come,from)}}

此时home页面的路由由 /Home 变更为 /Home?come={come}?from = {from},所以启始路由startDestination 要与home页面路由保持一致,也 变更为 /Home?come={come}?from = {from}。这里的come和from都是可选参数,可以不传,如果不传就会取navArgument中的defaultValue ,navArgument还可以定义参数的类型,默认为String,而必传参数的写法示例为Dashboard页面的路由 由 /Dashboard 变更为 /Dashboard/come?from = {from} 这里come为必传参数,不传就会报错,from为可选参数,可传可不传,通过requireNotNull(it.arguments)方法拿到参数传递给页面,在页面中我们用占位符展示拿到的参数

@Composablefun HomePage(come: String?, from: Int) {Column() {Text("This is HomePage",textAlign = TextAlign.Center,modifier = Modifier.fillMaxWidth().padding(horizontal = dimensionResource(R.dimen.dp20)).wrapContentWidth(Alignment.CenterHorizontally))Spacer(modifier = Modifier.height(20.dp))Text(text = "我是$come 页面,我来自第$from 个页面")}}

接下来我们通过BottomNavigationItem的点击来传递参数,我么将原来的navController.navigate(RouteConfig.ROUTE_HomePage)修改为如下

navController.navigate("${RouteConfig.ROUTE_HomePage}?come=HOME?from=1")

这里我们的路由如果为可选参数,那么传递时写法为 ?参数 = 值 与路由保持一致,如果是必选参数,那么写法为 /参数 ,示例如下:

//传递可选参数
navController.navigate("${RouteConfig.ROUTE_DashboardPage}/Dashboard?from=2")

/Dashboard为必填参数,错误的写法

navController.navigate("${RouteConfig.ROUTE_DashboardPage}/Dashboard/2")

因为在NavHost中配置的可选参数,但传递参数时使用了必传参数的写法,这是程序就会抛出找不到路由的错误

值得一提的是,我在官文中发现可以将NavHost的选中状态与BottomNavigationItem进行绑定

//这里通过currentBackStackEntryAsState方法拿到navController的状态val navBackStackEntry by navController.currentBackStackEntryAsState()val currentDestination = navBackStackEntry?.destination

然后在BottomNavigationItem中设置他的选中状态

 BottomNavigationItem(/*这里将navController的状态与BottomNavigationItem的选中进行绑定,这里的it.route与NavHost中第一个composable的route比较,如果为true则为选中*/selected =currentDestination?.hierarchy?.any { it.route ==RouteConfig.ROUTE_HomePage +"?${ParamsConfig.PARAMS_COME}={${ParamsConfig.PARAMS_COME}}"+"?${ParamsConfig.PARAMS_FROM}={${ParamsConfig.PARAMS_FROM}}"} == true,onClick = {navController.navigate("${RouteConfig.ROUTE_HomePage}?come=HOME?from=1")},modifier = Modifier.padding(5.dp),icon = {Image(painter = painterResource(R.drawable.ic_home_black_24dp),contentDescription = RouteConfig.ROUTE_HomePage,)},label = {Text(text = RouteConfig.ROUTE_HomePage,color = Color.Black,modifier = Modifier.wrapContentWidth(Alignment.CenterHorizontally))})

效果图

完整代码


import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.zyf.myjetpack.R/*** @ProjectName : My jetpack* @Author : yifeng_zeng* @Time : 2022/6/28 19:47* @Description : Navigation+Compose*/
class NavigationActivity : AppCompatActivity(){override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {//这里进行了navController的初始化val navController = rememberNavController()Scaffold(//设置底部导航栏bottomBar = {MyBottomNavigation(navController)}) {MainNavHost(navController)}}}@Composablefun MyBottomNavigation(navController:NavHostController){BottomNavigation(Modifier.fillMaxWidth().height(64.dp)) {//这里通过currentBackStackEntryAsState方法拿到navController的状态val navBackStackEntry by navController.currentBackStackEntryAsState()val currentDestination = navBackStackEntry?.destinationBottomNavigationItem(/*这里将navController的状态与BottomNavigationItem的选中进行绑定,这里的it.route与NavHost中第一个composable的route比较,如果为true则为选中*/selected =currentDestination?.hierarchy?.any { it.route ==RouteConfig.ROUTE_HomePage +"?${ParamsConfig.PARAMS_COME}={${ParamsConfig.PARAMS_COME}}"+"?${ParamsConfig.PARAMS_FROM}={${ParamsConfig.PARAMS_FROM}}"} == true,onClick = {navController.navigate("${RouteConfig.ROUTE_HomePage}?come=HOME?from=1")},modifier = Modifier.padding(5.dp),icon = {Image(painter = painterResource(R.drawable.ic_home_black_24dp),contentDescription = RouteConfig.ROUTE_HomePage,)},label = {Text(text = RouteConfig.ROUTE_HomePage,color = Color.Black,modifier = Modifier.wrapContentWidth(Alignment.CenterHorizontally))})BottomNavigationItem(selected =currentDestination?.hierarchy?.any { it.route =="${RouteConfig.ROUTE_DashboardPage}/{${ParamsConfig.PARAMS_COME}}" +"?${ParamsConfig.PARAMS_FROM}={${ParamsConfig.PARAMS_FROM}}"} == true,onClick = {//传递可选参数navController.navigate("${RouteConfig.ROUTE_DashboardPage}/Dashboard?from=2")},modifier = Modifier.padding(5.dp),icon = {Image(painter = painterResource(R.drawable.ic_dashboard_black_24dp),contentDescription = RouteConfig.ROUTE_DashboardPage,)},label = {Text(text = RouteConfig.ROUTE_DashboardPage,color = Color.Black,modifier = Modifier.wrapContentWidth(Alignment.CenterHorizontally))})BottomNavigationItem(selected = currentDestination?.hierarchy?.any { it.route =="${RouteConfig.ROUTE_NotificationPage}/{${ParamsConfig.PARAMS_COME}}" +"?${ParamsConfig.PARAMS_FROM}={${ParamsConfig.PARAMS_FROM}}"} == true,onClick = {//不传递可选参数navController.navigate("${RouteConfig.ROUTE_NotificationPage}/Notifications")},modifier = Modifier.padding(5.dp),icon = {Image(painter = painterResource(R.drawable.ic_notifications_black_24dp),contentDescription = RouteConfig.ROUTE_NotificationPage,)},label = {Text(text = RouteConfig.ROUTE_NotificationPage,color = Color.Black,modifier = Modifier.wrapContentWidth(Alignment.CenterHorizontally))})}}@Composablefun MainNavHost(navController:NavHostController){/** startDestination : 起始路由* */NavHost(navController = navController,startDestination = RouteConfig.ROUTE_HomePage +"?${ParamsConfig.PARAMS_COME}={${ParamsConfig.PARAMS_COME}}"+"?${ParamsConfig.PARAMS_FROM}={${ParamsConfig.PARAMS_FROM}}",){//ParamsConfig.PARAMS_COME与ParamsConfig.PARAMS_FROM都为可选参数composable(route = RouteConfig.ROUTE_HomePage +"?${ParamsConfig.PARAMS_COME}={${ParamsConfig.PARAMS_COME}}"+"?${ParamsConfig.PARAMS_FROM}={${ParamsConfig.PARAMS_FROM}}",arguments = listOf(navArgument(ParamsConfig.PARAMS_COME) {//如果不传的默认值defaultValue = "FIRST HOME"},navArgument(ParamsConfig.PARAMS_FROM) {defaultValue = 0//参数类型,不写默认为Stringtype = NavType.IntType })){val argument = requireNotNull(it.arguments)val come= argument.getString(ParamsConfig.PARAMS_COME)val from = argument.getInt(ParamsConfig.PARAMS_FROM)HomePage(come,from)}composable(//ParamsConfig.PARAMS_COME为必填参数,ParamsConfig.PARAMS_FROM为可选参数route = "${RouteConfig.ROUTE_DashboardPage}/{${ParamsConfig.PARAMS_COME}}" +"?${ParamsConfig.PARAMS_FROM}={${ParamsConfig.PARAMS_FROM}}",arguments = listOf(navArgument(ParamsConfig.PARAMS_COME) {},navArgument(ParamsConfig.PARAMS_FROM) {defaultValue = 999999type = NavType.IntType })){val argument = requireNotNull(it.arguments)val come= argument.getString(ParamsConfig.PARAMS_COME)val from = argument.getInt(ParamsConfig.PARAMS_FROM)DashboardPage(come,from)}composable(//ParamsConfig.PARAMS_COME为必填参数,ParamsConfig.PARAMS_FROM为可选参数route = "${RouteConfig.ROUTE_NotificationPage}/{${ParamsConfig.PARAMS_COME}}" +"?${ParamsConfig.PARAMS_FROM}={${ParamsConfig.PARAMS_FROM}}",arguments = listOf(navArgument(ParamsConfig.PARAMS_COME) {},navArgument(ParamsConfig.PARAMS_FROM) {defaultValue = 3type = NavType.IntType})){val argument = requireNotNull(it.arguments)val come= argument.getString(ParamsConfig.PARAMS_COME)val from = argument.getInt(ParamsConfig.PARAMS_FROM)NotificationPage(come,from)}}}@Composablefun HomePage(come: String?, from: Int) {Column() {Text("This is HomePage",textAlign = TextAlign.Center,modifier = Modifier.fillMaxWidth().padding(horizontal = dimensionResource(R.dimen.dp20)).wrapContentWidth(Alignment.CenterHorizontally))Spacer(modifier = Modifier.height(20.dp))Text(text = "我是$come 页面,我来自第$from 个页面")}}@Composablefun DashboardPage(come: String?, from: Int){Column() {Text("This is DashboardPage",textAlign = TextAlign.Center,modifier = Modifier.fillMaxWidth().padding(horizontal = dimensionResource(R.dimen.dp20)).wrapContentWidth(Alignment.CenterHorizontally))Spacer(modifier = Modifier.height(20.dp))Text(text = "我是$come 页面,我来自第$from 个页面")}}@Composablefun NotificationPage(come: String?, from: Int){Column() {Text("This is NotificationPage",textAlign = TextAlign.Center,modifier = Modifier.fillMaxWidth().padding(horizontal = dimensionResource(R.dimen.dp20)).wrapContentWidth(Alignment.CenterHorizontally))Spacer(modifier = Modifier.height(20.dp))Text(text = "我是$come 页面,我来自第$from 个页面")}}
}

3.深层链接

我想在导航中跳转到网页(我的博客)
在清单文件中进行配置

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.zyf.myjetpack"><applicationandroid:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/Theme.MyJetpack"><activityandroid:name=".navigation.NavigationActivity"android:exported="true"android:label="@string/app_name"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /><data android:scheme="https" android:host="blog.csdn.net" />//这里配置网址</intent-filter></activity></application></manifest>

在NavHost中添加deepLinks(深层链接,可添加多个)

 composable(route = "shop_and_sleep",//我的博客域名//深层链接格式可以存在多个deepLinks = listOf(navDeepLink {uriPattern = "https://blog.csdn.net/shop_and_sleep"})){}

然后响应它

 BottomNavigationItem(selected = currentDestination?.hierarchy?.any { it.route =="shop_and_sleep"} == true,onClick = {//不生效//navController.navigate("https://blog.csdn.net/shop_and_sleep".toUri())val deepLinkIntent = Intent()deepLinkIntent.data="https://blog.csdn.net/shop_and_sleep".toUri()deepLinkIntent.flags= Intent.FLAG_ACTIVITY_NEW_TASKnavController.handleDeepLink(deepLinkIntent)},modifier = Modifier.padding(5.dp),icon = {Image(painter = painterResource(R.drawable.ic_notifications_black_24dp),contentDescription = RouteConfig.ROUTE_NotificationPage,)},label = {Text(text = RouteConfig.ROUTE_NotificationPage,color = Color.Black,modifier = Modifier.wrapContentWidth(Alignment.CenterHorizontally))})

点击时的效果图
完整代码


import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import androidx.navigation.navDeepLink
import com.zyf.myjetpack.R/*** @ProjectName : My jetpack* @Author : yifeng_zeng* @Time : 2022/6/28 19:47* @Description : Navigation+Compose*/
class NavigationActivity : AppCompatActivity(){override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {//这里进行了navController的初始化val navController = rememberNavController()Scaffold(//设置底部导航栏bottomBar = {MyBottomNavigation(navController)}) {MainNavHost(navController)}}}@Composablefun MyBottomNavigation(navController:NavHostController){BottomNavigation(Modifier.fillMaxWidth().height(64.dp)) {//这里通过currentBackStackEntryAsState方法拿到navController的状态val navBackStackEntry by navController.currentBackStackEntryAsState()val currentDestination = navBackStackEntry?.destinationBottomNavigationItem(/*这里将navController的状态与BottomNavigationItem的选中进行绑定,这里的it.route与NavHost中第一个composable的route比较,如果为true则为选中*/selected =currentDestination?.hierarchy?.any { it.route ==RouteConfig.ROUTE_HomePage +"?${ParamsConfig.PARAMS_COME}={${ParamsConfig.PARAMS_COME}}"+"?${ParamsConfig.PARAMS_FROM}={${ParamsConfig.PARAMS_FROM}}"} == true,onClick = {navController.navigate("${RouteConfig.ROUTE_HomePage}?come=HOME?from=1")},modifier = Modifier.padding(5.dp),icon = {Image(painter = painterResource(R.drawable.ic_home_black_24dp),contentDescription = RouteConfig.ROUTE_HomePage,)},label = {Text(text = RouteConfig.ROUTE_HomePage,color = Color.Black,modifier = Modifier.wrapContentWidth(Alignment.CenterHorizontally))})BottomNavigationItem(selected =currentDestination?.hierarchy?.any { it.route =="${RouteConfig.ROUTE_DashboardPage}/{${ParamsConfig.PARAMS_COME}}" +"?${ParamsConfig.PARAMS_FROM}={${ParamsConfig.PARAMS_FROM}}"} == true,onClick = {//传递可选参数navController.navigate("${RouteConfig.ROUTE_DashboardPage}/Dashboard?from=2")},modifier = Modifier.padding(5.dp),icon = {Image(painter = painterResource(R.drawable.ic_dashboard_black_24dp),contentDescription = RouteConfig.ROUTE_DashboardPage,)},label = {Text(text = RouteConfig.ROUTE_DashboardPage,color = Color.Black,modifier = Modifier.wrapContentWidth(Alignment.CenterHorizontally))})BottomNavigationItem(selected = currentDestination?.hierarchy?.any { it.route =="shop_and_sleep"} == true,onClick = {//不生效//navController.navigate("https://blog.csdn.net/shop_and_sleep".toUri())val deepLinkIntent = Intent()deepLinkIntent.data="https://blog.csdn.net/shop_and_sleep".toUri()deepLinkIntent.flags= Intent.FLAG_ACTIVITY_NEW_TASKnavController.handleDeepLink(deepLinkIntent)},modifier = Modifier.padding(5.dp),icon = {Image(painter = painterResource(R.drawable.ic_notifications_black_24dp),contentDescription = RouteConfig.ROUTE_NotificationPage,)},label = {Text(text = RouteConfig.ROUTE_NotificationPage,color = Color.Black,modifier = Modifier.wrapContentWidth(Alignment.CenterHorizontally))})}}@Composablefun MainNavHost(navController:NavHostController){/** startDestination : 起始路由* */NavHost(navController = navController,startDestination = RouteConfig.ROUTE_HomePage +"?${ParamsConfig.PARAMS_COME}={${ParamsConfig.PARAMS_COME}}"+"?${ParamsConfig.PARAMS_FROM}={${ParamsConfig.PARAMS_FROM}}",){//ParamsConfig.PARAMS_COME与ParamsConfig.PARAMS_FROM都为可选参数composable(route = RouteConfig.ROUTE_HomePage +"?${ParamsConfig.PARAMS_COME}={${ParamsConfig.PARAMS_COME}}"+"?${ParamsConfig.PARAMS_FROM}={${ParamsConfig.PARAMS_FROM}}",arguments = listOf(navArgument(ParamsConfig.PARAMS_COME) {//如果不传的默认值defaultValue = "FIRST HOME"},navArgument(ParamsConfig.PARAMS_FROM) {defaultValue = 0//参数类型,不写默认为Stringtype = NavType.IntType })){val argument = requireNotNull(it.arguments)val come= argument.getString(ParamsConfig.PARAMS_COME)val from = argument.getInt(ParamsConfig.PARAMS_FROM)HomePage(come,from)}composable(//ParamsConfig.PARAMS_COME为必填参数,ParamsConfig.PARAMS_FROM为可选参数route = "${RouteConfig.ROUTE_DashboardPage}/{${ParamsConfig.PARAMS_COME}}" +"?${ParamsConfig.PARAMS_FROM}={${ParamsConfig.PARAMS_FROM}}",arguments = listOf(navArgument(ParamsConfig.PARAMS_COME) {},navArgument(ParamsConfig.PARAMS_FROM) {defaultValue = 999999type = NavType.IntType })){val argument = requireNotNull(it.arguments)val come= argument.getString(ParamsConfig.PARAMS_COME)val from = argument.getInt(ParamsConfig.PARAMS_FROM)DashboardPage(come,from)}composable(route = "shop_and_sleep",//我的博客域名//深层链接格式可以存在多个deepLinks = listOf(navDeepLink {uriPattern = "https://blog.csdn.net/shop_and_sleep"})){}}}@Composablefun HomePage(come: String?, from: Int) {Column() {Text("This is HomePage",textAlign = TextAlign.Center,modifier = Modifier.fillMaxWidth().padding(horizontal = dimensionResource(R.dimen.dp20)).wrapContentWidth(Alignment.CenterHorizontally))Spacer(modifier = Modifier.height(20.dp))Text(text = "我是$come 页面,我来自第$from 个页面")}}@Composablefun DashboardPage(come: String?, from: Int){Column() {Text("This is DashboardPage",textAlign = TextAlign.Center,modifier = Modifier.fillMaxWidth().padding(horizontal = dimensionResource(R.dimen.dp20)).wrapContentWidth(Alignment.CenterHorizontally))Spacer(modifier = Modifier.height(20.dp))Text(text = "我是$come 页面,我来自第$from 个页面")}}@Composablefun NotificationPage(come: String?, from: Int){Column() {Text("This is NotificationPage",textAlign = TextAlign.Center,modifier = Modifier.fillMaxWidth().padding(horizontal = dimensionResource(R.dimen.dp20)).wrapContentWidth(Alignment.CenterHorizontally))Spacer(modifier = Modifier.height(20.dp))Text(text = "我是$come 页面,我来自第$from 个页面")}}
}

六.Compose 中的 ConstraintLayout

依赖

   implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"

官网上说是否将 ConstraintLayout 用于 Compose 中的特定界面取决于开发者的偏好,这是什么意思呢?个人理解就是使用ConstraintLayout 能绘制出的布局是能通过其他组件绘制出同样的布局得,并且没有性能差别,这是与之前不同的,再通俗易懂一点呢,就是爱用不用,反正我有…

  • 引用是使用 createRefs()createRefFor() 创建的,ConstraintLayout 中的每个可组合项都需要有与之关联的引用。
  • 约束条件是使用 constrainAs() 修饰符提供的,该修饰符将引用作为参数,可让您在主体 lambda 中指定其约束条件。
  • 约束条件是使用 linkTo() 或其他有用的方法指定的。
  • parent 是一个现有的引用,可用于指定对 ConstraintLayout 可组合项本身的约束条件。

官网的话总是拗口,先直接把官方示例copy过来瞧瞧

@Preview(showBackground = true)
@Composable
fun ConstraintLayoutContent() {ConstraintLayout {// Create references for the composables to constrainval (button, text) = createRefs()Button(onClick = { /* Do something */ },// Assign reference "button" to the Button composable// and constrain it to the top of the ConstraintLayoutmodifier = Modifier.constrainAs(button) {top.linkTo(parent.top, margin = 16.dp,goneMargin = 8.dp)}) {Text("Button")}// Assign reference "text" to the Text composable// and constrain it to the bottom of the Button composableText("Text", Modifier.constrainAs(text) {top.linkTo(button.bottom, margin = 16.dp)})}
}

Preview过后是这个样子

这里我们以Text来做讲解

 //这里的 text 就是通过 createRefs() 来创建的val (button, text) = createRefs()//然后通过constrainAs()方法将Text控件与text进行绑定Text("Text", Modifier.constrainAs(text) {//这里的top就是Text控件的上方,button.bottom就是Button控件的下方,通过linkTo连接起来top.linkTo(button.bottom, margin = 16.dp)})}

翻译翻译什么TMD叫…串台了,翻译过来就是Text控件的上方在Button控件的下方,外边距为16dp,我们完全可以用其他的控件把这个效果实现,并且没有性能优劣之分,其实这个翻译过来吧有点相对布局那味,算了不管了(狗头保命)

@Preview(showBackground = true)
@Composable
fun NotConstraintLayoutContent() {Column {Button(onClick = { /* Do something */ },Modifier.padding(0.dp,16.dp,0.dp,0.dp)) {Text("Button")}Text("Text",Modifier.padding(0.dp,16.dp,0.dp,0.dp))}
}

你看,他两是不是一模一样

当然ConstraintLayout 不止top和bottom,他还包括这些

  • top 和end就是上下
  • start 就是布局的开始,如果你的布局是从左边开始start 就是左边,从右边开始start 就是右边,end也是一样的
  • absoluteLeft 和 absoluteRight 不管布局方向,就是左右
  • baseline 就是在一排
 /*** The start anchor of this layout. Represents left in LTR layout direction, or right in RTL.*/@Stableval start = ConstraintLayoutBaseScope.VerticalAnchor(id, -2)/*** The left anchor of this layout.*/@Stableval absoluteLeft = ConstraintLayoutBaseScope.VerticalAnchor(id, 0)/*** The top anchor of this layout.*/@Stableval top = ConstraintLayoutBaseScope.HorizontalAnchor(id, 0)/*** The end anchor of this layout. Represents right in LTR layout direction, or left in RTL.*/@Stableval end = ConstraintLayoutBaseScope.VerticalAnchor(id, -1)/*** The right anchor of this layout.*/@Stableval absoluteRight = ConstraintLayoutBaseScope.VerticalAnchor(id, 1)/*** The bottom anchor of this layout.*/@Stableval bottom = ConstraintLayoutBaseScope.HorizontalAnchor(id, 1)/*** The baseline anchor of this layout.*/@Stableval baseline = ConstraintLayoutBaseScope.BaselineAnchor(id)

这里值得一提的是当开发者这样写时会报错

这个报错信息告诉我们在start中需要的是一个横着的属性,但是我们给了一个竖着的属性bottom

Type mismatch.
Required:
ConstraintLayoutBaseScope.VerticalAnchor
Found:
ConstraintLayoutBaseScope.HorizontalAnchor

所以linkTo方法链接的两端是有标准的

VerticalAnchor是一组可以一起使用的,HorizontalAnchor是一组可以一起使用的,BaselineAnchor是单独使用的,只有baseline 能用

   Text("Text", Modifier.constrainAs(text) {start.linkTo(button.end)baseline.linkTo(button.baseline)})


那我们如何让一个控件在约束布局中居中呢?

             top.linkTo(parent.top)start.linkTo(parent.start)end.linkTo(parent.end)bottom.linkTo(parent.bottom)

上面的代码可以实现

七.Compose 手写一个分享二维码弹窗

越写到后面文章就显得有点臃肿了,后续复杂更新会开单章
Jetpack Compose之手写分享页面

你可以学习到

  • 上一节Compose中约束布局使用实战
  • Compose中权重的使用
  • Compose中圆角的设置
  • Compose中padding的使用

八.Compose 设置颜色的三种方式

Compose 设置颜色的三种方式

九.Compose事件与状态简略介绍

Compose事件与状态简略介绍

十.Compose中的预览@Preview与@PreviewParameter的使用

Compose中的预览@Preview与@PreviewParameter的使用

十一.Compose中的获取Context

import androidx.compose.ui.platform.LocalContextToast.makeText(LocalContext.current, "哈哈哈哈", Toast.LENGTH_SHORT).show()

出自Compose中的获取Context评论区

十二.Compose 动画api之我的电子木鱼青春版

空闲时间码的电子木鱼
阅读本文你可以学习到

Compose沉浸式样式
Compose一些动画API例如 animateSizeAsState, infiniteTransition,AnimatedVisibility
Compose沉底样式的Dialog
Compose LazyRow 中的ListItem
Compose的手势监听

Compose 动画api之我的电子木鱼青春版


总结

本篇文章只介绍了Compose最基础的使用,这只是它的冰山一角,后续博主自己学习后会更新Compose的其他相关文章。有错误的地方欢迎指正,Compose真正的太新啦,好多组件连官方自己都没有示例,只能看源码肝爆,大家多多支持一下

参考资料
安卓Compose官方文档
JetPack Compose 底部导航栏实现
Jetpack Compose之 在Compose中使用Navigation导航

Jetpack Compose入门详解(实时更新)相关推荐

  1. Android原生UI开发框架 《Jetpack Compose入门到精通》最全上手指南

    前言 在去年的Google/IO大会上,亮相了一个全新的 Android 原生 UI 开发框架-Jetpack Compose, 与苹果的SwiftIUI一样,Jetpack Compose是一个声明 ...

  2. FFmpeg入门详解之111:RTSP协议2

    rtsp消息详解 1.RTSP的消息有两大类,一是请求消息(request),一是回应消息(response),两种消息的格式不同. 请求消息格式: 方法 URI RTSP版本 CR LF 消息头 C ...

  3. FFmpeg入门详解之87:HLS直播协议详解

    引言与效果演示 ----------------------------------------- FFmpeg431的官方地址已经无法打开, 我将ffmepg4.3.1的开发包和源码上传到了百度云: ...

  4. FFmpeg入门详解之102:HLS直播协议详解

    引言与效果演示 ----------------------------------------- FFmpeg431的官方地址已经无法打开, 我将ffmepg4.3.1的开发包和源码上传到了百度云: ...

  5. FFmpeg入门详解之85:RTSP协议2

    rtsp消息详解 1.RTSP的消息有两大类,一是请求消息(request),一是回应消息(response),两种消息的格式不同. 请求消息格式: 方法 URI RTSP版本 CR LF 消息头 C ...

  6. FFmpeg入门详解之82:FFmpeg转码器Java版之ava编码

    创建数据库:db_webavtc 创建数据表:avcategory(素材类别) id int primary key, pid int , cname varchar(255), cmemo varc ...

  7. FFmpeg入门详解之105:m3u8文件格式详解

    简介 M3U8 是 Unicode 版本的 M3U,用 UTF-8 编码."M3U" 和 "M3U8" 文件都是苹果公司使用的 HTTP Live Stream ...

  8. linux 日志按大小切割_nginx入门详解(六)- 日志切割

    上一章讲解了nginx的目录加密功能,本章重点介绍nginx的日志切割. 笨办法学linux:nginx入门详解(五)- 目录加密​zhuanlan.zhihu.com 在第二章,我们探讨了nginx ...

  9. 【GCN】图卷积网络(GCN)入门详解

    机器学习算法与自然语言处理出品 @公众号原创专栏作者 Don.hub 单位 | 京东算法工程师 学校 | 帝国理工大学 图卷积网络(GCN)入门详解 什么是GCN GCN 概述 模型定义 数学推导 G ...

最新文章

  1. FuseSeg:用于自动驾驶领域的RGB和热成像数据融合网络
  2. 2018-2019-1 20165325 20165320 20165337 实验二 固件程序设计
  3. android操作系统优势,Android操作系统平台最大的优势
  4. 上币至iamToken
  5. asp.net mvc处理css和js版本问题
  6. 使用 SAP Business Application Studio 开发 Vue 应用
  7. java常考设计模式_java笔试常考的几种设计模式
  8. c++ linux 线程等待与唤醒_C++并发编程 等待与唤醒
  9. 以下哪个不是有效的java变量名,Java程序设计-中国大学mooc-题库零氪
  10. VC启动窗口画面制作方法研究
  11. IT项目管理-敏捷和传统
  12. python从云端服务器读数据_audio 读取服务器文件
  13. outlook邮箱备份方法:
  14. POJ 3415 后缀数组+单调栈
  15. 每日算法系列【LeetCode 658】找到 K 个最接近的元素
  16. PhpSpreadsheet使用
  17. Gartner数据:RPA以75.6%增长率成2019年Q1增速最快的企业级软件(附全球十大RPA市场数据)
  18. 26岁,2020 - 观《人生七年》
  19. 第七章第八章思维导图
  20. 如何成为高效的学习高手

热门文章

  1. 北京老家具修复服务器,涨知识:图解古旧家具修复的六个步骤
  2. 2021-2027全球与中国可待因药品市场现状及未来发展趋势
  3. 腾讯如何打造一款实时对战手游
  4. 2021第十三届中国最佳酒店大奖榜单揭晓:年度最佳酒店、最佳顶级奢华酒店、最佳城市地标酒店...
  5. mysql安装以及安装navicat并且连接
  6. 开源的跨平台AI模型部署总有一款是你的菜
  7. 招商信诺完成15例新冠病毒感染肺炎赔付;东呈减免湖北加盟酒店重大费用 | 美通企业日报...
  8. android 内部存储 其他,小米MIUI系统怎么清除内部储存空间中“其他”数据?
  9. maven 中配置多个mirror的问题
  10. linux查看pcie网卡命令,kudzu命令查看及设置网卡等硬件信息