我知道点进来的人都是想学习JC的,所以可能都不知道环境怎么弄,事实上如果只是学习的话,安装了最新版的Android studio后,创建项目时就可以构建一个Jetpack Compose,用于学习是再好不过了
提示:需要对原生xml布局有一定了解,另外它最好是配合Kotlin 使用更佳
借用官方的解释:Jetpack Compose 是用于构建原生 Android 界面的新工具包。它使用更少的代码、强大的工具和直观的 Kotlin API,可以帮助您简化并加快 Android 界面开发。
因为是新东西,所以配置上和平常有点不一样,可以对照着加下依赖 和配置,有些不用的可以酌情添加,例如: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
}
它和安卓传统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 的标准布局元素即可。
使用 Column 可将多个项垂直地放置在屏幕上。
@Composable
fun ArtistCard() {
Column {
Text("Alfred Sisley")
Text("3 minutes ago")
}
}
同样,使用 Row 可将多个项水平地放置在屏幕上。Column 和 Row 都支持配置它们所含元素的对齐方式。
@Composable
fun ArtistCard(artist: Artist) {
Row(verticalAlignment = Alignment.CenterVertically) {
Image(/*这里是你的图片*/)
Column {
Text(artist.name)
Text(artist.lastSeenOnline)
}
}
}
得到如下布局
使用 Box 可将元素放在其他元素上。Box 还支持为其包含的元素配置特定的对齐方式。
@Composable
fun ArtistAvatar(artist: Artist) {
Box {
Image(/*这里是头像*/)
Icon(/*这里是角标*/)
}
}
得到如下布局
通常,您只需要这些构建块。您可以自行编写可组合函数,将这些布局组合成更精美的布局,让其适合您的应用。
在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")
}
}
}
作为之前的补充,所以阅读起来可能会感到断层
通过跟评论区的博友沟通,我留意了在compose中使用view的相关方面,在补充的今天发现了AndroidView这一compose控件,它设计的初衷在于在compose中嵌套使用一些compose暂未支持的view控件(例如WebView, SurfaceView, AdView),当然也可以嵌入xml布局
使用代码如下
@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
创建 一个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()
}
)
}
}
先创建三个页面,分别为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()
}
}
}
学习过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)
)
}
}
在集成导航的时候,我们只能进行简单的页面跳转,下面就在页面跳转时带上我们需要的参数,传递参数是要在路由上添加的,为了便于理解,我们新增一个参数的配置
/**
* @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 个页面")
}
}
}
我想在导航中跳转到网页(我的博客)
在清单文件中进行配置
<?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 个页面")
}
}
}
依赖
implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"
官网上说是否将 ConstraintLayout 用于 Compose 中的特定界面取决于开发者的偏好,这是什么意思呢?个人理解就是使用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)
})
}
}
//这里的 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,他还包括这些
/**
* 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)
越写到后面文章就显得有点臃肿了,后续复杂更新会开单章
Jetpack Compose之手写分享页面
你可以学习到
Compose 设置颜色的三种方式
Compose事件与状态简略介绍
Compose中的预览@Preview与@PreviewParameter的使用
import androidx.compose.ui.platform.LocalContext
Toast.makeText(LocalContext.current, "哈哈哈哈", Toast.LENGTH_SHORT).show()
出自Compose中的获取Context评论区
空闲时间码的电子木鱼
阅读本文你可以学习到
Compose沉浸式样式
Compose一些动画API例如 animateSizeAsState, infiniteTransition,AnimatedVisibility
Compose沉底样式的Dialog
Compose LazyRow 中的ListItem
Compose的手势监听
Compose 动画api之我的电子木鱼青春版
Compose布局之Image初步使用到了解
本篇文章只介绍了Compose最基础的使用,这只是它的冰山一角,后续博主自己学习后会更新Compose的其他相关文章。有错误的地方欢迎指正,Compose真正的太新啦,好多组件连官方自己都没有示例,只能看源码肝爆,大家多多支持一下
文章中的源码及自己刷力扣的一些杂七杂八的东西
ssh clone
[email protected]:shop_and_sleep/mycompose.git
https clone
https://gitcode.net/shop_and_sleep/mycompose.git
参考资料
安卓Compose官方文档
JetPack Compose 底部导航栏实现
Jetpack Compose之 在Compose中使用Navigation导航