1.创建项目
导入版本
1.gradle/libs.versions.toml
[versions]
accompanistPermissions = "0.36.0"
agp = "8.5.0-beta01"
coilCompose = "2.7.0"
constraintlayoutComposeVersion = "1.0.1"
hiltAndroid = "2.51.1"
hiltNavigationCompose = "1.2.0"
kotlin = "1.9.25"
coreKtx = "1.10.1"
junit = "4.13.2"
junitVersion = "1.1.5"
espressoCore = "3.5.1"
activityCompose = "1.9.2"
composeBom = "2024.09.01"
kotlinxSerializationJson = "1.6.3"
lifecycleViewmodelKtx = "2.8.5"
composeRuntime = "1.7.1"
lingver = "1.3.0"
loggingInterceptorVersion = "4.12.0"
navigationCompose = "2.8.0"
retrofit = "2.11.0"
roomRuntime = "2.6.1"
rxandroid = "2.1.1"
rxandroidVersion = "3.0.1"
rxjava = "2.2.21"
rxjava3 = "3.1.9"
rxlifecycle = "3.1.0"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
#region viewmodel livedata compose
androidx-lifecycle-extensions = { module = "androidx.lifecycle:lifecycle-extensions", version.ref = "lifecycleViewmodelKtx" }
androidx-lifecycle-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycleViewmodelKtx" }
androidx-lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycleViewmodelKtx" }
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelKtx" }
androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleViewmodelKtx" }
androidx-lifecycle-viewmodel-savedstate = { module = "androidx.lifecycle:lifecycle-viewmodel-savedstate", version.ref = "lifecycleViewmodelKtx" }
constraintlayout-compose = { module = "androidx.constraintlayout:constraintlayout-compose", version.ref = "constraintlayoutComposeVersion" }
runtime-livedata = { module = "androidx.compose.runtime:runtime-livedata", version.ref = "composeRuntime" }
androidx-runtime = { module = "androidx.compose.runtime:runtime", version.ref = "composeRuntime" }
androidx-runtime-rxjava2 = { module = "androidx.compose.runtime:runtime-rxjava2" }
#endregion compose
#region kotlin serialization
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
#endregion
#region hilt
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hiltAndroid" }
hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hiltAndroid" }
androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
#endregion
#region navigation
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
#endregion
#region retrofit2 and okhttp3
okhttp3-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "loggingInterceptorVersion" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
converter-scalars = { module = "com.squareup.retrofit2:converter-scalars", version.ref = "retrofit" }
converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" }
#endregion okhttp3
#region rxjava 2
adapter-rxjava2 = { module = "com.squareup.retrofit2:adapter-rxjava2", version.ref = "retrofit" }
rxandroid = { module = "io.reactivex.rxjava2:rxandroid", version.ref = "rxandroid" }
rxjava = { module = "io.reactivex.rxjava2:rxjava", version.ref = "rxjava" }
#endregion
#region rxjava 3
rxjava3-rxandroid = { module = "io.reactivex.rxjava3:rxandroid", version.ref = "rxandroidVersion" }
rxjava3-rxjava = { module = "io.reactivex.rxjava3:rxjava", version.ref = "rxjava3" }
adapter-rxjava3 = { module = "com.squareup.retrofit2:adapter-rxjava3", version.ref = "retrofit" }
rxlifecycle = { module = "com.trello.rxlifecycle3:rxlifecycle", version.ref = "rxlifecycle" }
rxlifecycle-android-lifecycle-kotlin = { module = "com.trello.rxlifecycle3:rxlifecycle-android-lifecycle-kotlin", version.ref = "rxlifecycle" }
rxlifecycle-components = { module = "com.trello.rxlifecycle3:rxlifecycle-components", version.ref = "rxlifecycle" }
#endregion
#region room
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomRuntime" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomRuntime" }
#endregion
#region coil
coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coilCompose" }
#endregion
#region accompanist
accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanistPermissions" }
#endregion
#region 国际化
lingver = { module = "com.github.YarikSOffice:lingver", version.ref = "lingver" }
#endregion
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
我整理好的版本,都用过了适配
2.模块目录下的 build.gradle.kts
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
id("kotlin-kapt")
id("com.google.dagger.hilt.android")
kotlin("plugin.serialization")
id("androidx.room")
}
android {
namespace = "com.composeapp"
compileSdk = 34
defaultConfig {
applicationId = "com.composeapp"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
room {
schemaDirectory("$projectDir/schemas")
}
buildTypes {
release {
isMinifyEnabled = 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 {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.15"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
implementation(libs.kotlinx.serialization.json)
//region ViewModel Livedata compose runtime
implementation(libs.androidx.lifecycle.viewmodel.ktx)
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.lifecycle.livedata.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.lifecycle.lifecycle.runtime.compose)
implementation(libs.androidx.lifecycle.viewmodel.savedstate)
implementation(libs.androidx.runtime)
implementation(libs.runtime.livedata)
//endregion
// region hilt
implementation(libs.hilt.android)
kapt(libs.hilt.android.compiler)
implementation(libs.androidx.hilt.navigation.compose)
// endregion
//region navigation
implementation(libs.androidx.navigation.compose)
//endregion navigation
//region retrofit2 okhttp
//Retrofit 核心库
implementation(libs.retrofit)
//响应数据自动序列化
//JSON
implementation(libs.converter.gson)
//String类型
implementation(libs.converter.scalars)
//拦截器 logging
implementation(libs.okhttp3.logging.interceptor)
//endregion
//region rxjava2
// implementation(libs.androidx.runtime.rxjava2)
// implementation(libs.adapter.rxjava2)
// implementation(libs.rxjava)
// implementation(libs.rxandroid)
//endregion
//region rxjava3
implementation(libs.rxjava3.rxjava)
implementation(libs.adapter.rxjava3)
implementation(libs.rxlifecycle)
implementation(libs.rxlifecycle.android.lifecycle.kotlin)
implementation(libs.rxlifecycle.components)
implementation(libs.rxjava3.rxandroid)
//endregion
//region room
implementation(libs.androidx.room.runtime)
annotationProcessor(libs.androidx.room.compiler)
kapt(libs.androidx.room.compiler)
//endregion
//region coil 异步网络图片加载
implementation(libs.coil.compose)
//endregion
//region accompanist
implementation(libs.accompanist.permissions)
//endregion
//region 国际化
implementation(libs.lingver)
// endregion
//region compose 约束布局
implementation (libs.constraintlayout.compose)
//endregion
}
3.工程目录下的 build.gradle.kts
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.jetbrains.kotlin.android) apply false
id("com.google.dagger.hilt.android") version "2.51.1" apply false
kotlin("kapt") version "1.9.25" apply false
kotlin("jvm") version "1.9.25" apply false // or kotlin("multiplatform") or any other kotlin plugin
kotlin("plugin.serialization") version "1.9.25" apply false
val room_version = "2.6.1"
id("androidx.room") version room_version apply false
}
4.工程目录下setting.gradle.kts
pluginManagement {
repositories {
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven("https://jitpack.io")
}
}
rootProject.name = "ComposeApp"
include(":app")
2.搭建Hilt环境
1.创建Application
@HiltAndroidApp
class MyApplication : Application() {
companion object {
const val SharedPreferencesFileName = "MyApplication";
private lateinit var application: Application;
lateinit var sharedPreferences: SharedPreferences
fun getApplication(): Application {
return application;
}
}
@Override
override fun onCreate() {
super.onCreate()
// Log.i("测试","app启动了 child="+child)
application = this
sharedPreferences =
getSharedPreferences(SharedPreferencesFileName, Context.MODE_PRIVATE);
/**
* 国际化绑定 已经帮存入 sharedPreferences里了
*/
Lingver.init(this)
}
}
加上注解 @HiltAndroidApp
2.在用到注入的Activity上加上注解
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
/**
* 将依赖注入好的viewModel放入这里
*/
private val viewModel: XianPageViewModel by viewModels()
@Inject
lateinit var child: Child;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
val sharedPreferences = MyApplication.sharedPreferences
Log.i("share", "这是shared 全局 $sharedPreferences")
// sharedPreferences.edit().putString("token", "123456").apply()
//关闭 导航状态栏
// 隐藏状态栏
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.insetsController?.hide(WindowInsets.Type.statusBars())
} else {
@Suppress("DEPRECATION")
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN
}
this.lifecycleScope
val string = sharedPreferences.getString("token", "")
Log.i("share", "获取token 全局 $string")
ComposeAppTheme {
// ScaffoldExample()
// MyApp()
// NamePageRxjava3()
// NamePage()
// RoomTestPage()
// NfcPage()
// NfcMain()
// ImageIconPage()
// PermissionPage()
// LiveDataTest()
// LanguageSelector()
LanguageSelectorKuangjia()
}
}
// Log.i("测试","activity启动了 this="+this)
// Log.i("测试","activity启动了 child="+child.activity)
// Log.i("测试","activity启动了 child="+child.application)
Log.i("测试", "activity启动了 viewModel=" + viewModel)
}
}
3.测试注入
/**
* 第一种: 相当于spring @Component
*/
@ActivityScoped
class Child @Inject constructor(@ActivityContext val activity: Context,@ApplicationContext val application: Context){
val name = "你好"
// val applicationContext = application
}
3.注入网络模块
1.单例类
/**
* 全局唯一网络模块
*/
@Module
@InstallIn(SingletonComponent::class)
object NetModel {
private const val Tag = "Retrofit:";
private const val URL = "http://192.168.202.57:8080";
@Singleton
@Provides
fun provideOkHttpClient(tokenInterceptor: TokenInterceptor): OkHttpClient {
//构建日志拦截器
val httpLoggingInterceptor = HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
override fun log(message: String) {
Log.i(Tag, message)
}
}).setLevel(HttpLoggingInterceptor.Level.BODY)
//构建
return OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.addInterceptor(tokenInterceptor)
.addInterceptor(httpLoggingInterceptor)
.build()
}
@RxJava2Inject
@Singleton
@Provides
fun provideRetrofitRxJava2(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(URL)
.client(okHttpClient)
// .addConverterFactory(ScalarsConverterFactory.create())//添加Gson转换器
.addConverterFactory(GsonConverterFactory.create())//添加Gson转换器
//添加Rxjava适配
// .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
}
@RxJava3Inject
@Singleton
@Provides
fun provideRetrofitRxJava3(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(URL)
.client(okHttpClient)
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
// .addConverterFactory(ScalarsConverterFactory.create())//添加Gson转换器
.addConverterFactory(GsonConverterFactory.create())//添加Gson转换器
//添加Rxjava适配
.build()
}
@Singleton
@Provides
fun provideMainTestService(@RxJava3Inject retrofit: Retrofit): MainTestService {
return retrofit.create(MainTestService::class.java)
}
}
2.Token拦截器
@Singleton
class TokenInterceptor @Inject constructor() : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val token: String = MyApplication.sharedPreferences.getString("token", "2001").toString()
val url = request.url.newBuilder().addQueryParameter("token1", token).build()
val header = request.headers.newBuilder().add("token2", token).build()
val requestNew = request.newBuilder().url(url).headers(header).build()
return chain.proceed(requestNew);
}
}
3.要是注入两个相同类型的
创建自定义注解
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class RxJava2Inject
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class RxJava3Inject
在构造的时候声明
@RxJava2Inject
@Singleton
@Provides
fun provideRetrofitRxJava2(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(URL)
.client(okHttpClient)
// .addConverterFactory(ScalarsConverterFactory.create())//添加Gson转换器
.addConverterFactory(GsonConverterFactory.create())//添加Gson转换器
//添加Rxjava适配
// .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
}
@RxJava3Inject
@Singleton
@Provides
fun provideRetrofitRxJava3(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(URL)
.client(okHttpClient)
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
// .addConverterFactory(ScalarsConverterFactory.create())//添加Gson转换器
.addConverterFactory(GsonConverterFactory.create())//添加Gson转换器
//添加Rxjava适配
.build()
}
注入的时候声明注入哪个
@Singleton
@Provides
fun provideMainTestService(@RxJava3Inject retrofit: Retrofit): MainTestService {
return retrofit.create(MainTestService::class.java)
}
4.声明接口
interface MainTestService {
// @GET("/phone/gis/test")
// suspend fun getTestData(): Response<String>
@GET("/phone/gis/test")
suspend fun getTestDataCall(): Call<TestDto>
@GET("/phone/gis/test")
suspend fun getTestData(): Response<TestDto>
@GET("/phone/gis/test/{name}")
suspend fun getTestDataTruth(@Path("name") name: String, @Query("age") age: Int): TestDto
/**
* 使用Rxjava 不允许加 suspend 关键字
*/
@GET("/phone/gis/test/{name}")
fun getTestDataRx(@Path("name") name: String, @Query("age") age: Int): Observable<TestDto>
}
RxJava写 接口,千万不要声明suspend ,要不然序列化JSON 会报错,无法实例化 Observable
5.要是发送http请求还需要权限
network_security_config.xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
6.网络权限
<uses-permission android:name="android.permission.INTERNET"/>
4.ViewModel
/**
* viewmodel不能设置作用域,否则会报错
*
* ViewModel 部分中提到的 viewModel() 函数会自动使用 Hilt 通过 @HiltViewModel 注解构建的 ViewModel
*/
@HiltViewModel
class ScopePageViewModel @Inject constructor():ViewModel() {
init {
val job: Job = viewModelScope.launch { }//可以单独取消
}
}
然后注入就可以了
5.LiveData
@HiltViewModel
class LiveDataTestViewModel @Inject constructor() : ViewModel() {
val items:MutableLiveData<MutableList<Item>> = MutableLiveData(mutableListOf())
init {
val item1 = Item("测试模块1",true)
items.value?.add(item1)
val item2 = Item("测试模块2",true)
items.value?.add(item2)
}
fun updateItem(item:Item){
item.updateIsShow(!item.isShow.value!!)
}
}
用法
@Composable
fun LiveDataTest(viewModel: LiveDataTestViewModel = hiltViewModel()) {
val items = viewModel.items.observeAsState(mutableListOf())
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(30.dp)
) {
item {
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceAround) {
val width = Modifier
.width(150.dp)
.height(50.dp)
Button(onClick = {
viewModel.updateItem(items.value[0])
}, modifier = width) {
Text(text = stringResource(R.string._1))
}
Button(onClick = { viewModel.updateItem(items.value[1]) }, modifier = width) {
Text(text = "隐藏或打开测试模块2")
}
}
}
items(items.value) {
ItemComponent(item = it)
}
}
}
6.整合navigation
@Serializable
data class Profile(val name: String)
@Serializable
object FriendsList
@Serializable
object ProfileScreenChild2
@Serializable
data class ProfileScreenChild1(val param: String)
@Serializable
data class Aaa(val param: String)
// Define the ProfileScreen composable.
@Composable
fun ProfileScreen(
profile: Profile,
parentViewModel: XianPageViewModel?,
viewModel: XianPageViewModel = hiltViewModel(),
onNavigateToFriendsList: () -> Unit
) {
// ProfileScreen启动了com.composeapp.ui.view.XianPageViewModel@841f9de这是viewmodel
Log.i("测试", "ProfileScreen启动了" + viewModel.toString() + "这是viewmodel")
Log.i(
"测试",
"ProfileScreen启动了 这是父级的viewModel实例" + parentViewModel?.toString() + "这是父类viewmodel"
)
Column {
Text("导航参数: ${profile.name}")
ProfileScreenChild()
Button(onClick = { onNavigateToFriendsList() }) {
Text("Go to Friends List")
}
}
}
@Composable
fun ProfileScreenChild(viewModel: XianPageViewModel = hiltViewModel()) {
// ProfileScreenChild启动了com.composeapp.ui.view.XianPageViewModel@841f9de这是viewmodel
Log.i("测试", "ProfileScreenChild启动了" + viewModel.toString() + "这是viewmodel") //在同一个导航中是一致的
// Text("子组件的值: ${viewModel.data.value}")
val childNavController = rememberNavController()
NavHost(childNavController, startDestination = ProfileScreenChild1("你好测试子组件Pchild1")) {
composable<ProfileScreenChild2> { backStackEntry ->
//专门用来拿到当前导航的堆栈里的ViewModel
val parentEntry = remember(backStackEntry) {
//必须类型和值都一样才能找到,否则抛异常
childNavController.getBackStackEntry(ProfileScreenChild1("你好测试子组件Pchild1"))//必须值要一样才能找到,值不一致就找不到,跟序列化JSON比
}
// viewmodel 当组件移除组件树的时候,将会被销毁
val parentViewModel = hiltViewModel<XianPageViewModel>(parentEntry)
ProfileScreenChild2(parentViewModel)
}
composable<ProfileScreenChild1> { backStackEntry ->
val child1 = backStackEntry.toRoute<ProfileScreenChild1>()
ProfileScreenChild1(profileScreenChild1 = child1) {
childNavController.navigate(ProfileScreenChild2)
}
}
}
}
@Composable
fun ProfileScreenChild2(
parentViewModel: XianPageViewModel,
viewModel: XianPageViewModel = hiltViewModel()
) {
Log.i(
"测试",
"ProfileScreenChild2启动了" + parentViewModel.toString() + "这是parent viewmodel"
) //在同一个导航中是一致的
Log.i(
"测试",
"ProfileScreenChild2启动了" + viewModel.toString() + "这是viewmodel"
) //在同一个导航中是一致的
Log.i(
"测试",
"ProfileScreenChild2启动了" + (viewModel === parentViewModel) + "这是viewmodel"
) //在同一个导航中是一致的
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Button(onClick = { /*TODO*/ }) {
Text(text = "ProfileScreenChild2")
}
}
}
@Composable
fun ProfileScreenChild1(
viewModel: XianPageViewModel = hiltViewModel(),
profileScreenChild1: ProfileScreenChild1,
toProChild2: () -> Unit
) {
Log.i(
"测试",
"ProfileScreenChild1启动了" + viewModel.toString() + "这是viewmodel 这是param${profileScreenChild1.param}"
) //在同一个导航中是一致的
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Button(onClick = { toProChild2() }) {
Text(text = "ProfileScreenChild1")
}
}
}
// Define the FriendsListScreen composable.
@Composable
fun FriendsListScreen(
onNavigateToProfile: (String) -> Unit,
viewModel: XianPageViewModel = hiltViewModel()
) {
Log.i("测试", "FriendsListScreen启动了" + viewModel.toString() + "这是viewmodel")
val (text, setText) = remember { mutableStateOf("") }
Column {
Text("Friends List")
TextField(value = text, onValueChange = setText)
Button(onClick = { onNavigateToProfile(text) }) {
Text("Go to Profile")
}
}
}
// Define the MyApp composable, including the `NavController` and `NavHost`.
@Composable
fun MyApp(viewModel: XianPageViewModel = viewModel()) {
Log.i("测试", "MyApp启动了 viewmodel=$viewModel")//是Activity同一个
val navController = rememberNavController()
NavHost(navController, startDestination = FriendsList) {
composable<Profile> { backStackEntry ->
// 获取路由对象实例
val profile: Profile = backStackEntry.toRoute()
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
ProfileScreen(
profile = profile,
parentViewModel = null,
onNavigateToFriendsList = {
navController.navigate(route = FriendsList)
}
)
}
}
composable<FriendsList> {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
FriendsListScreen(
onNavigateToProfile = { it ->
navController.navigate(
route = Profile(name = "传入了新的参数:${it}")
)
}
)
}
}
}
}
7.整合room
1.实体类
@Entity
@Serializable
data class User(@PrimaryKey val uid:String,
@ColumnInfo(name = "name")
val name:String,
val time:Long,
val date:String?=null,
val age :Int
) {
}
2.dao
@Dao
interface UserDao {
@Query("SELECT * FROM user")
fun getAll(): List<User>
@Query("SELECT * FROM user WHERE uid = :userId")
fun loadAllByIds(userId: String): List<User>
@Query("SELECT * FROM user WHERE name LIKE :name")
fun findByName(name: String): List<User>
@Insert
fun insertAll(vararg users: User)
@Delete
fun delete(user: User)
}
3.数据库
@Database(entities = [User::class], version = 3, exportSchema = true)
abstract class AppDataBase: RoomDatabase() {
companion object{
private const val DB_NAME = "my_app.db"
fun getInstance(context: Context): AppDataBase{
val db = Room.databaseBuilder(
context,
AppDataBase::class.java, DB_NAME
)
.addMigrations(MIGRATION_1_2,MIGRATION_2_3)
.build()
return db;
}
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
// 执行 SQL 语句来迁移数据库
db.execSQL("ALTER TABLE user ADD COLUMN date TEXT")
}
}
val MIGRATION_2_3 = object : Migration(2, 3) {
override fun migrate(db: SupportSQLiteDatabase) {
// 执行 SQL 语句来迁移数据库
db.execSQL("ALTER TABLE user ADD COLUMN age INTEGER NOT NULL default 1")
}
}
}
/**
* 数据库当中的一张表
*/
abstract fun UserDao():UserDao
}
4.Dao实例注入
/**
* 全局唯一数据库模块
*/
@Module
@InstallIn(SingletonComponent::class)
object DataBaseModel {
@Singleton
@Provides
fun provideAppDataBase(@ApplicationContext context:Context): AppDataBase {
return AppDataBase.getInstance(context)
}
@Singleton
@Provides
fun provideUserDao(appDataBase: AppDataBase): UserDao {
return appDataBase.UserDao()
}
}
8.Permission
@SuppressLint("CheckResult")
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun PermissionPage(){
// Camera permission state
val cameraPermissionState = rememberPermissionState(
android.Manifest.permission.WRITE_EXTERNAL_STORAGE
)
Observable.fromCallable {
Log.i("执行","执行在"+Thread.currentThread().name)
"true"
}.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
Log.i("执行","执行成功$it "+Thread.currentThread().name)
},{
Log.i("执行","执行失败"+it.message)
})
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center){
if (cameraPermissionState.status.isGranted) {
Text("相机权限授予成功")
} else {
Column {
val textToShow = if (cameraPermissionState.status.shouldShowRationale) {
// If the user has denied the permission but the rationale can be shown,
// then gently explain why the app requires this permission
"相机权限是核心. Please grant the permission."
} else {
// If it's the first time the user lands on this feature, or the user
// doesn't want to be asked again for this permission, explain that the
// permission is required
"必须给予相机权限. " +
"Please grant the permission"
}
Text(textToShow)
Button(onClick = { cameraPermissionState.launchPermissionRequest() }) {
Text("Request permission")
}
}
}
}
}
9.Kotlin json 序列化
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
@Serializable
data class Project(val name: String, val language: String)
fun main() {
// Serializing objects
val data = Project("kotlinx.serialization", "Kotlin")
val string = Json.encodeToString(data)
println(string) // {"name":"kotlinx.serialization","language":"Kotlin"}
// Deserializing back into objects
val obj = Json.decodeFromString<Project>(string)
println(obj) // Project(name=kotlinx.serialization, language=Kotlin)
}
10.kotlin 冷流
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun main() {
runBlocking {
val coldFlow = flow {
for (i in 1..5){
delay(3000)
emit(i)
}
}
// 订阅者 1
launch {
coldFlow.collect { value ->
println("Subscriber 1 received: $value")
}
}
// 订阅者 2
launch {
delay(2000) // 延迟订阅,确保第二个订阅者在第一个订阅者之后
coldFlow.collect { value ->
println("Subscriber 2 received${Thread.currentThread().name}: $value")
}
}
println("执行")
}
}
11.kotlin 热流
import androidx.compose.runtime.collectAsState
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun main() {
runBlocking {
// 创建一个 StateFlow 实例,初始状态为 0
val stateFlow = MutableStateFlow(0)
// 启动一个协程来更新 StateFlow 的状态
launch {
repeat(5) {
delay(500) // 模拟数据生成
stateFlow.value += 1
}
}
// 订阅 StateFlow 并打印最新状态
launch {
stateFlow.collect { value ->
println("Subscriber received: $value")
}
}
}
}
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun main() {
runBlocking {
// 创建一个 SharedFlow
val sharedFlow = MutableSharedFlow<Int>()
// 启动一个协程来发射数据
launch {
repeat(10) {
delay(100) // 模拟异步操作
sharedFlow.emit(it) // 发射数据
}
}
// 订阅 SharedFlow
launch {
sharedFlow.collect { value ->
println("Subscriber 1 received: $value")
}
}
// 另一个订阅者
launch {
sharedFlow.collect { value ->
println("Subscriber 2 received: $value")
}
}
}
}
12.全局语言切换
在 Application created 方法
/**
* 国际化绑定 已经帮存入 sharedPreferences里了
*/
Lingver.init(this)
@Composable
fun LanguageSelectorKuangjia(localeViewmodel: LocaleViewmodel= hiltViewModel()) {
// val locale = MyApplication.sharedPreferences.getString("locale",null)
//
// val localeState = localeViewmodel.locale.observeAsState(
if (locale == null) Locale.getDefault() else Locale(locale)
// locale?.let { Locale(it) }?:Locale.getDefault()
// )
val current = LocalContext.current
Column(modifier = Modifier
.fillMaxSize()
.padding(30.dp)) {
// Example buttons to switch languages
Row {
Button(onClick = {
Lingver.getInstance().setLocale(current,Locale.SIMPLIFIED_CHINESE)
(current as Activity).recreate()
}) {
Text("中文")
}
Button(onClick = {
Lingver.getInstance().setLocale(current,Locale.ENGLISH)
(current as Activity).recreate()
}) {
Text("英文")
}
}
// Apply the selected language
// LanguageSwitcherKuangjia(localeState.value) {
// Your UI content here, which will reflect the selected language
Text(text = stringResource(id = R.string._1))
// }
}
}
自己写的扩展函数
/**
* 转变语言扩展函数 会保留在当前的Navigation 导航里
*/
fun Context.changeLocale(locale: Locale) {
Lingver.getInstance().setLocale(this,locale)
(this as Activity).recreate()
}
在 导航中 重创建,也会跟随导航目的地,不会重置
13.使用 阿里矢量图
iconfont-阿里巴巴矢量图标库
1.存一个地方
2.下载这个插件
3.右键一个空文件夹
4.操作
14.文件分享
1.文件提供者
<provider
android:authorities="${applicationId}.fileprovider"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
2. 外部存储空间权限文件
file_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="external_files" path="."/>
</paths>
3. MediaStore
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
public class ExportMedia {
public static Uri saveExcelFileToMediaStoreInExternalDownloadExport(Context context, String fileName) {
// 创建 ContentValues 对象
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
values.put(MediaStore.MediaColumns.MIME_TYPE, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS+"/EXPORT"); // 下载目录
// 插入文件到 MediaStore
return context.getContentResolver().insert(MediaStore.Files.getContentUri("external"), values);
}
public Uri queryExcelFiles(Context context, String fileName) {
String[] projection = {
MediaStore.Files.FileColumns._ID,
MediaStore.Files.FileColumns.DISPLAY_NAME,
MediaStore.Files.FileColumns.MIME_TYPE
};
String selection = MediaStore.Files.FileColumns.MIME_TYPE + "=?";
String[] selectionArgs = {"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}; // .xlsx 的 MIME 类型
Cursor cursor = context.getContentResolver().query(
MediaStore.Files.getContentUri("external"),
projection,
selection,
selectionArgs,
null
);
try {
while (cursor.moveToNext()) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID));
String displayName = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME));
if (displayName.equals(fileName)){
// 处理查询结果,例如分享文件
return ContentUris.withAppendedId(MediaStore.Files.getContentUri("external"), id);
}
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
}finally {
if (cursor != null){
cursor.close();
}
}
return null;
}
}
public class ShareUtils {
public static void shareExcel(Context context,String path){
// 文件路径,确保文件存在
File fileToShare = new File(path);
// 创建分享意图
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); // MIME 类型
// 使用 FileProvider 获取 content URI
Uri fileUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileprovider", fileToShare);
shareIntent.putExtra(Intent.EXTRA_STREAM, fileUri);
// 允许临时读取 URI 权限
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// 启动分享对话框
context.startActivity(Intent.createChooser(shareIntent, "分享一个Excel"));
}
public static void shareExcel(Context context,Uri uri){
// 创建分享意图
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); // MIME 类型
// 使用 FileProvider 获取 content URI
shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
// 允许临时读取 URI 权限
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// 启动分享对话框
context.startActivity(Intent.createChooser(shareIntent, "分享一个Excel"));
}
}
<!--在sdcard中创建/删除文件的权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
15. NFC
1.开启权限
<uses-permission android:name="android.permission.NFC" />
2.创建工具类
import android.nfc.tech.NfcA
import java.security.MessageDigest
object NfcHelper {
private fun readUid(nfcA: NfcA): ByteArray {
val command = byteArrayOf(xxxx.toByte(), xxxxx.toByte())
val uid = nfcA.transceive(command)
return byteArrayOf(uid[0], uid[1], uid[2], uid[3], uid[4], uid[5], uid[6], uid[7])
}
/**
* 处理密码
*
* @param nfcA
* @return
*/
private fun handlePwd(nfcA: NfcA): ByteArray {
val uidByte = readUid(nfcA).copyOf(7)
val secret = byteArrayOf(xxxxxxxxxxxxxxxxxx)
//两个数组重载运算符 拼接在一起
val byteUnique = uidByte + secret
//加密SHA -256
val digest = MessageDigest.getInstance("SHA-256")
// 更新 MessageDigest 实例,传入要哈希的数据
digest.update(byteUnique)
// 计算哈希值并返回 取前四位
val pwdBytes = digest.digest().copyOf(4)
return pwdBytes
}
/**
* 新nfc芯片验证密码
*
* @param nfcA
* @return
*/
fun authNew(nfcA: NfcA) {
val pwd = handlePwd(nfcA)
//拼接两个数组
val command = byteArrayOf(0x1B.toByte()) + pwd
nfcA.transceive(command)
}
/**
* 新nfc 芯片读方法
*
* @param nfcA
* @param startPage
* @param endPage
* @return
*/
fun readTag(nfcA: NfcA, startPage: Int, endPage: Int): List<Byte> {
val list = arrayListOf<Byte>()
for (i in startPage..endPage) {
val data = nfcA.transceive(byteArrayOf(0x30, i.toByte()))
list.addAll(data.asList().subList(0,4))
}
return list;
}
/**
* 新nfc 芯片写方法
*
* @param nfcA
* @param writeByte
* @param block 扇区,也就是页
*/
fun writeTag(nfcA: NfcA, writeByte: ByteArray, page: Int) {
val cmd = byteArrayOf(0xA2.toByte(), page.toByte()) + writeByte
nfcA.transceive(cmd)
}
}
3.Model
/**
* 全局唯一NFC模块
*/
@Module
@InstallIn(SingletonComponent::class)
object NfcModel {
@Singleton
@Provides
fun provideNfcAdapter(@ApplicationContext context: Context): NfcAdapter {
return NfcAdapter.getDefaultAdapter(context)
}
}
4.viewModel
import android.nfc.NfcAdapter
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.scopes.ActivityScoped
import javax.inject.Inject
/**
* viewmodel不能设置作用域,否则会报错
*/
@HiltViewModel
class NfcViewModel @Inject constructor(val nfcAdapter: NfcAdapter):ViewModel() {
// 使用 MutableLiveData 来持有数据
}
5.页面
@Composable
fun NfcMain(){
var open by remember { mutableStateOf(false) }
Column(Modifier.fillMaxSize()) {
Box(modifier = Modifier
.fillMaxWidth()
.height(100.dp), contentAlignment = Alignment.Center){
Button(onClick = { open = !open }) {
Text(text = if (open) "点击关闭NFC" else "点击开启NFC")
}
}
if (open){
NfcPage()
}else{
Box(modifier = Modifier
.fillMaxSize().background(Color.Blue)
, contentAlignment = Alignment.Center){
Text(text = "NFC未开启")
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NfcPage(modifier: Modifier = Modifier, nfcViewModel: NfcViewModel = hiltViewModel()) {
var presses by remember { mutableIntStateOf(0) }
val readOrWrite = remember {
mutableStateOf(false)
}
val (text, setText) = remember { mutableStateOf("") }
val nfcAdapter = nfcViewModel.nfcAdapter
val list = remember {
mutableStateListOf<String>()
}
val context = LocalContext.current
LaunchedEffect(key1 = Unit) {
val pendingIntent = PendingIntent.getActivity(
context, 0, Intent(
MyApplication.getApplication(), context::class.java
)
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), PendingIntent.FLAG_IMMUTABLE
)
val filters = arrayOf(IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED))
val arrayOf = arrayOf(arrayOf<String>(NfcA::class.java.name))
nfcAdapter.enableReaderMode(context as Activity?, { tag ->
val nfcA = NfcA.get(tag)
Log.i("NfcA", "NfcA: $nfcA")
nfcA.timeout = 3000
try {
nfcA.connect()
Log.i("测试","nfc超时时间 ${nfcA.timeout} ")
Log.i("测试","nfc最大长度 ${nfcA.maxTransceiveLength} ")
NfcHelper.authNew(nfcA)
// NewNfcHelper.authNew(nfcA)
if (readOrWrite.value){
val readTag = NfcHelper.writeTag(nfcA, byteArrayOf(2,3,4,1), 4)
Log.i("测试","写入成功 $readTag")
}else{
val readTag = NfcHelper.readTag(nfcA, 4, 4)
Log.i("测试","读取成功 $readTag")
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
nfcA.close()
}
}, NfcAdapter.FLAG_READER_NFC_A, null);
nfcAdapter.enableForegroundDispatch(context, pendingIntent, filters, arrayOf)
}
DisposableEffect(key1 = Unit) {
this.onDispose {
nfcAdapter.disableForegroundDispatch(context as Activity)
nfcAdapter.disableReaderMode(context as Activity)
}
}
Scaffold(
topBar = {
TopAppBar(
colors = topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.primary,
),
title = {
Box(
modifier = Modifier
.fillMaxWidth(),
contentAlignment = Alignment.Center // 设置 Box 的内容居中
) {
TextField(
value = text, onValueChange = setText,
modifier = Modifier
.width(200.dp)
.height(50.dp),// 调整宽度
// 调整高度
textStyle = MaterialTheme.typography.bodyLarge.copy(
fontSize = 16.sp,
lineHeight = TextUnit(200f, TextUnitType.Sp)
), // 调整字体大小
) // 调整内边距
}
}
)
},
bottomBar = {
BottomAppBar(
containerColor = MaterialTheme.colorScheme.primaryContainer,
contentColor = MaterialTheme.colorScheme.primary,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(space = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Button(modifier = Modifier.weight(1f), onClick = {
}) {
Text(text = "添加")
}
Button(modifier = Modifier.weight(1f), onClick = {
readOrWrite.value = !readOrWrite.value
}) {
Text(text = if (readOrWrite.value) "写入" else "读取")
}
Button(modifier = Modifier.weight(1f), onClick = {
list.clear()
}) {
Text(text = "清空")
}
}
}
},
floatingActionButton = {
FloatingActionButton(onClick = { presses++ }) {
Icon(Icons.Default.Build, contentDescription = "Add")
}
}
) { innerPadding ->
LazyColumn(modifier = Modifier.padding(innerPadding)) {
items(items = list, key = { it }) {
Text(
text = it,
modifier = Modifier
.fillMaxWidth()
.height(40.dp),
textAlign = TextAlign.Center
)
}
}
}
}