Jetpack Compose入门详解
- 前排提醒🥯
- 前言(Compose是什么)🧇
- 优势与缺点🍞
- 前四课🥐
- 标准布局组件🥖
- xml和compose混合使用 + livedata数据绑定🥨
- compose结合navigation使用🥞
- Compose 中的 ConstraintLayout🧀
- Compose 手写一个分享二维码弹窗🍕
- Compose 设置颜色的三种方式🥪
- Compose事件与状态简略介绍🧈
- Compose中的预览@Preview与@PreviewParameter的使用🥧
- Compose中的获取Context🥠
- Compose 动画api之我的电子木鱼青春版🍩
- Compose布局之Image初步使用到了解🍪
- Compose屏幕适配🎂
- Compose中的附带效应🍰
- Jetpack Compose 实战 宝可梦图鉴🧁
- Compose 实战 宝可梦图鉴 桌面版🍮
- Android Studio Hedgehog (2023.1 版)对Compose的支持🥛
- Compose pager分页器入门使用 HorizontalPager与VerticalPager(2023/8)🥛
- Compose眼珠跟随手势移动的笑脸
- 后续更新策略调整
- Compose Canvas基础(1) drawxxx()绘制方法
- Compose Canvas基础(2) 图形转换
- 总结
前排提醒🥯
我知道点进来的人都是想学习JC的,所以可能都不知道环境怎么弄,事实上如果只是学习的话,安装了最新版的Android studio后,创建项目时就可以构建一个Jetpack Compose,用于学习是再好不过了
前言(Compose是什么)🧇
提示:需要对原生xml布局有一定了解,另外它最好是配合Kotlin 使用更佳
借用官方的解释:Jetpack Compose 是用于构建原生 Android 界面的新工具包。它使用更少的代码、强大的工具和直观的 Kotlin API,可以帮助您简化并加快 Android 界面开发。
阅读提示:
- 本文非常的多和长,截止目前CSDN统计已有4w5的字数,建议收藏慢慢观看 ❗❗❗
- 本文知识点顺序按照博主自己学习的路线顺序,如有需要自行按照目录学习
1.实战准备
因为是新东西,所以配置上和平常有点不一样,可以对照着加下依赖 和配置,有些不用的可以酌情添加,例如:navigation
app的build.gradle
plugins {
id 'com.android.application'
id 'kotlin-android'
}
android {
compileSdk 31
defaultConfig {
applicationId "com.zyf.myjetpack"
minSdk 22
targetSdk 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
viewBinding true
// Enables Jetpack Compose for this module
compose 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 activities
implementation 'androidx.activity:activity-compose:1.4.0'
// Compose Material Design
implementation 'androidx.compose.material:material:1.1.1'
// Animations
implementation 'androidx.compose.animation:animation:1.1.1'
// Tooling support (Previews, etc.)
implementation 'androidx.compose.ui:ui-tooling:1.1.1'
// Integration with ViewModels
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.4.1'
// UI Tests
androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.1.1'
// When using a AppCompat theme
implementation "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
}
2.使用物料清单
值得注意的是Compose Kotlin 编译器扩展 (androidx.compose.compiler) 未关联到 Compose 库版本。相反,它会关联到 Kotlin 编译器插件的版本,并与 Compose 的其余部分分开发布,因此请务必使用与您的 Kotlin 版本兼容的版本。点击这个查看版本对应关系
上面是2022年五月第一次体验时使用的依赖了,在新接触的情况下,官方推荐我们使用BOM(物料清单)来管理所有的compose依赖版本,以便于我们使用,它为我们整理了一个版本中一些相互关联的依赖,使我们在开发过程中不用担心依赖迭代和一些新特性。我们这里用官方的例子来解释物料库的使用
dependencies {
// 导入compose bom
implementation platform('androidx.compose:compose-bom:2023.01.00')
// 加上了版本号以覆盖material3的bom设置,使用alpha01版本
implementation 'androidx.compose.material3:material3:1.1.0-alpha01'
// 导入其他的没有版本号的compose依赖,让bom管理版本号
implementation 'androidx.compose.foundation:foundation'
}
上例介绍了使用bom的不同的两种场景,第一种就是在依赖后加上版本号从而不让bom管理依赖版本,适用于体验新特性的时候,而第二种就是我们常用的用法,不加版本号,让bom替我们管理依赖。
对于androidx.compose:compose-bom:2023.01.00需要说的是 2023.01.00是compose的一个重要正式版本,官方用时间点来标志,如需了解哪些 Compose 库版本已映射到特定 BoM 版本,请查看 BoM 到库的版本映射。
2023.9.27日更新 ,评论区提到 compose compiler 与 kotlin, gradle, gradle plugin 等版本都有匹配关系。之前因为compose 其实不太相关只是环境问题所以没有详细写(真的有点劝退新人),既然都提到了那么就补充一下吧,以下是一些补充的匹配关系连接
Android Gradle 插件和 Android Studio 兼容性
再强调一次,初学者最好下一个最新版的Android Studio ,新建项目默认现在就是compose构建了
优势与缺点🍞
它和安卓传统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混合使用
a.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">
<TextView
android: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.ComposeView
android: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
//这里绑定了ComposeView
val view = binding.composeHome
view.setContent {
AppCompatTheme{
HomePge()
}
}
return root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
@Preview
@Composable
private fun HomePge(){
Column() {
Text(text = "我是 Jetpack Compose")
}
}
}
效果如下
b.compose中使用view
作为之前的补充,所以阅读起来可能会感到断层
通过跟评论区的博友沟通,我留意了在compose中使用view的相关方面,在补充的今天发现了AndroidView这一compose控件,它设计的初衷在于在compose中嵌套使用一些compose暂未支持的view控件(例如WebView, SurfaceView, AdView),当然也可以嵌入xml布局
我们先来看看源码
- factory 是我们需要嵌套的view
- modifier 修饰符
- update 在布局膨胀后调用的回调,每当在该回调中读取的 State 发生变化时,AndroidView 都会重组。
使用代码如下
@Composable
fun XmlView(){
var selectedItem by remember { mutableStateOf(0) }
AndroidView(factory = { context ->
android.widget.Button(context).apply{
setOnClickListener{
selectedItem += 1
}
}
},
modifier = Modifier.fillMaxSize(),
update = {view ->
view.text = selectedItem.toString()
})
}
效果如下
(安卓官方提醒)最好在 AndroidView factory lambda 中构建一个 View,而不是使用 remember 在 AndroidView 之外保存对 View 的直接引用。
如需嵌入 XML 布局,请使用 androidx.compose.ui:ui-viewbinding 库提供的 AndroidViewBinding API。为此,您的项目必须启用视图绑定。
这边直接引入官方代码
@Composable
fun AndroidViewBindingExample() {
AndroidViewBinding(ExampleLayoutBinding::inflate) {
exampleView.setBackgroundColor(Color.GRAY)
}
}
可以看到代码中的ExampleLayoutBinding,这里的布局应该就是 exampl_layout,而exampleView则是xml布局中一个view的id
2.livedata数据绑定
创建 一个ViewModel如下:
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class HomeViewModel : ViewModel() {
private val _count = MutableLiveData(0)
val count: LiveData<Int> = _count
fun 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()方法让其值+1
viewModel.add()
Toast.makeText(context,count.value.toString(), Toast.LENGTH_SHORT).show()
}
)
}
}
效果图
compose结合navigation使用🥞
1.集成导航
先创建三个页面,分别为HomePage,DashboardPage和NotificationPage
@Composable
fun HomePage(){
Text(
"This is HomePage",
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = dimensionResource(R.dimen.dp20))
.wrapContentWidth(Alignment.CenterHorizontally)
)
}
@Composable
fun DashboardPage(){
Text(
"This is DashboardPage",
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = dimensionResource(R.dimen.dp20))
.wrapContentWidth(Alignment.CenterHorizontally)
)
}
@Composable
fun 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对象
@Composable
fun MainNavHost(){
NavHost(navController = ,startDestination = ,
){
}
}
NavHost对象需要两个必传参数,一个是NavController,一个是起始路由地址,NavController 对象是 Navigation 组件的中心 API,我们可以通过 rememberNavController创建,代码如下所示:
import androidx.navigation.compose.rememberNavController
val navController = rememberNavController()
我们接着往下写,我们先将navController作为参数传递进来,然后startDestination 设置HomePage为我们的启始路由,每一个composable中为页面添加路由(route)
@Composable
fun 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依旧是传递进来的
@Composable
fun 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)
}
}
}
@Composable
fun 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)
)
}
)
}
}
@Composable
fun 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()
}
}
}
@Composable
fun HomePage(){
Text(
"This is HomePage",
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = dimensionResource(R.dimen.dp20))
.wrapContentWidth(Alignment.CenterHorizontally)
)
}
@Composable
fun DashboardPage(){
Text(
"This is DashboardPage",
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = dimensionResource(R.dimen.dp20))
.wrapContentWidth(Alignment.CenterHorizontally)
)
}
@Composable
fun 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
//参数类型,不写默认为String
type = 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 = 999999
type = 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 = 3
type = 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)方法拿到参数传递给页面,在页面中我们用占位符展示拿到的参数
@Composable
fun 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)
}
}
}
@Composable
fun MyBottomNavigation(navController:NavHostController){
BottomNavigation(
Modifier
.fillMaxWidth()
.height(64.dp)
) {
//这里通过currentBackStackEntryAsState方法拿到navController的状态
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
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)
)
}
)
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)
)
}
)
}
}
@Composable
fun 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
//参数类型,不写默认为String
type = 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 = 999999
type = 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 = 3
type = NavType.IntType
}
)
){
val argument = requireNotNull(it.arguments)
val come= argument.getString(ParamsConfig.PARAMS_COME)
val from = argument.getInt(ParamsConfig.PARAMS_FROM)
NotificationPage(come,from)
}
}
}
@Composable
fun 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 个页面")
}
}
@Composable
fun 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 个页面")
}
}
@Composable
fun 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">
<application
android: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">
<activity
android: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_TASK
navController.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)
}
}
}
@Composable
fun MyBottomNavigation(navController:NavHostController){
BottomNavigation(
Modifier
.fillMaxWidth()
.height(64.dp)
) {
//这里通过currentBackStackEntryAsState方法拿到navController的状态
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
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)
)
}
)
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_TASK
navController.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)
)
}
)
}
}
@Composable
fun 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
//参数类型,不写默认为String
type = 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 = 999999
type = 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"
})
){
}
}
}
@Composable
fun 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 个页面")
}
}
@Composable
fun 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 个页面")
}
}
@Composable
fun 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 constrain
val (button, text) = createRefs()
Button(
onClick = { /* Do something */ },
// Assign reference "button" to the Button composable
// and constrain it to the top of the ConstraintLayout
modifier = 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 composable
Text("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.
*/
@Stable
val start = ConstraintLayoutBaseScope.VerticalAnchor(id, -2)
/**
* The left anchor of this layout.
*/
@Stable
val absoluteLeft = ConstraintLayoutBaseScope.VerticalAnchor(id, 0)
/**
* The top anchor of this layout.
*/
@Stable
val top = ConstraintLayoutBaseScope.HorizontalAnchor(id, 0)
/**
* The end anchor of this layout. Represents right in LTR layout direction, or left in RTL.
*/
@Stable
val end = ConstraintLayoutBaseScope.VerticalAnchor(id, -1)
/**
* The right anchor of this layout.
*/
@Stable
val absoluteRight = ConstraintLayoutBaseScope.VerticalAnchor(id, 1)
/**
* The bottom anchor of this layout.
*/
@Stable
val bottom = ConstraintLayoutBaseScope.HorizontalAnchor(id, 1)
/**
* The baseline anchor of this layout.
*/
@Stable
val 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中的预览@Preview与@PreviewParameter的使用🥧
Compose中的预览@Preview与@PreviewParameter的使用
Compose中的获取Context🥠
import androidx.compose.ui.platform.LocalContext
Toast.makeText(LocalContext.current, "哈哈哈哈", Toast.LENGTH_SHORT).show()
出自Compose中的获取Context评论区
Compose 动画api之我的电子木鱼青春版🍩
空闲时间码的电子木鱼
阅读本文你可以学习到
Compose沉浸式样式
Compose一些动画API例如 animateSizeAsState, infiniteTransition,AnimatedVisibility
Compose沉底样式的Dialog
Compose LazyRow 中的ListItem
Compose的手势监听
Compose布局之Image初步使用到了解🍪
Compose屏幕适配🎂
摘录自github上一个仿网易云项目NCMUSIC
const val APP_DESIGN_WIDTH = 750
/**
* compose屏幕适配单位
*/
val Number.cdp
get() = Dp(
toFloat() *
Resources.getSystem().displayMetrics.widthPixels
/ APP_DESIGN_WIDTH
/ Resources.getSystem().displayMetrics.density
)
val Dp.toPx
get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, Resources.getSystem().displayMetrics)
/**
* compose屏幕适配单位(字体专用)
*/
val Number.csp
get() = (toFloat() *
Resources.getSystem().displayMetrics.widthPixels
/ APP_DESIGN_WIDTH
/ Resources.getSystem().displayMetrics.scaledDensity).sp
/**
* 将数字转换成compose中的DP
*/
val Number.transformDp
get() = Dp(toFloat() / Resources.getSystem().displayMetrics.density)
使用就和dp和sp一样
Compose中的附带效应🍰
(一)DisposableEffect
Compose中的附带效应(一)DisposableEffect
(二)LaunchedEffect
Compose中的附带效应(二)LaunchedEffect
(三)rememberCoroutineScope
Compose中的附带效应(三)rememberCoroutineScope
(四)rememberUpdatedState
Compose中的附带效应(四)rememberUpdatedStatet
(五)derivedStateOf
Compose中的附带效应(五)derivedStateOf
Jetpack Compose 实战 宝可梦图鉴🧁
学Compose学了有小半年的时间了,一直都是看官方的一些基础的教程并总结学习。最近终于实战了一个宝可梦图鉴的小项目,麻雀虽小五脏俱全,很适合用来系统学习Compose。
Compose 实战 宝可梦图鉴 桌面版🍮
Android Studio Hedgehog (2023.1 版)对Compose的支持🥛
调试程序中的 Compose 状态信息
若 Compose 界面的部分内容意外重组,开发者有时很难弄清原因所在。现在,只要在可组合函数上设置断点,调试程序便会列出可组合函数的参数及其状态,这样一来您就可以更轻松地识别可能导致重组的更改。例如,当您在可组合项上暂停时,调试程序将准确告诉您哪些参数 “已更改” 或保持 “未更改” 状态,以便您更有效地调查重组的原因。
Compose 动画预览支持
Android Studio Hedgehog 中的 Compose 动画预览现支持许多额外的 Compose API,包括 animate*AsState、CrossFade、rememberInfiniteTransition 以及 AnimatedContent,但不包含 updateTransition 和 AnimatedVisibility。Compose 动画预览还具有新的选择器,可让您设置非枚举或布尔值状态,以使用精确输入调试 Compose 动画。您可以播放、暂停、拖动、控制速度和统筹所有受支持的 Compose Animation API。
参考链接
Compose pager分页器入门使用 HorizontalPager与VerticalPager(2023/8)🥛
Compose pager分页器入门使用 HorizontalPager与VerticalPager(2023/8)
Compose眼珠跟随手势移动的笑脸
你可以学习到:
1.Canvas画图
2.手势监听
3.平移动画
后续更新策略调整
学习Jetpack Compose的反思,总结及新的开始(无干货,纯叙事)
Compose Canvas基础(1) drawxxx()绘制方法
Compose Canvas基础(1) drawxxx()绘制方法
Compose Canvas基础(2) 图形转换
总结
本篇文章只介绍了Compose最基础的使用,这只是它的冰山一角,后续博主自己学习后会更新Compose的其他相关文章。有错误的地方欢迎指正,Compose真正的太新啦,好多组件连官方自己都没有示例,只能看源码肝爆,大家多多支持一下
可以关注下[我的公众号]:微信搜索我怀里的猫
文章中的源码及自己刷力扣的一些杂七杂八的东西
ssh clone
git@gitcode.net:shop_and_sleep/mycompose.git
https clone
https://gitcode.net/shop_and_sleep/mycompose.git
参考资料
安卓Compose官方文档
JetPack Compose 底部导航栏实现
Jetpack Compose之 在Compose中使用Navigation导航