当前位置: 首页 > news >正文

Android compose 的基本环境搭建

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" }
#endregionjunit = { 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 = 34defaultConfig {applicationId = "com.composeapp"minSdk = 24targetSdk = 34versionCode = 1versionName = "1.0"testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"vectorDrawables {useSupportLibrary = true}}room {schemaDirectory("$projectDir/schemas")}buildTypes {release {isMinifyEnabled = falseproguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"),"proguard-rules.pro")}}compileOptions {sourceCompatibility = JavaVersion.VERSION_1_8targetCompatibility = 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 runtimeimplementation(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 hiltimplementation(libs.hilt.android)kapt(libs.hilt.android.compiler)implementation(libs.androidx.hilt.navigation.compose)// endregion//region  navigationimplementation(libs.androidx.navigation.compose)//endregion navigation//region retrofit2 okhttp//Retrofit 核心库implementation(libs.retrofit)//响应数据自动序列化//JSONimplementation(libs.converter.gson)//String类型implementation(libs.converter.scalars)//拦截器 loggingimplementation(libs.okhttp3.logging.interceptor)//endregion//region rxjava2
//    implementation(libs.androidx.runtime.rxjava2)
//    implementation(libs.adapter.rxjava2)
//    implementation(libs.rxjava)
//    implementation(libs.rxandroid)//endregion//region  rxjava3implementation(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 roomimplementation(libs.androidx.room.runtime)annotationProcessor(libs.androidx.room.compiler)kapt(libs.androidx.room.compiler)//endregion//region coil 异步网络图片加载implementation(libs.coil.compose)//endregion//region accompanistimplementation(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 falsealias(libs.plugins.jetbrains.kotlin.android) apply falseid("com.google.dagger.hilt.android") version "2.51.1" apply falsekotlin("kapt") version "1.9.25" apply falsekotlin("jvm") version "1.9.25" apply false // or kotlin("multiplatform") or any other kotlin pluginkotlin("plugin.serialization") version "1.9.25" apply falseval 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: SharedPreferencesfun getApplication(): Application {return application;}}@Overrideoverride fun onCreate() {super.onCreate()
//        Log.i("测试","app启动了 child="+child)application = thissharedPreferences =getSharedPreferences(SharedPreferencesFileName, Context.MODE_PRIVATE);/*** 国际化绑定 已经帮存入 sharedPreferences里了*/Lingver.init(this)}}

加上注解  @HiltAndroidApp

2.在用到注入的Activity上加上注解

@AndroidEntryPoint
class MainActivity : ComponentActivity() {/*** 将依赖注入好的viewModel放入这里*/private val viewModel: XianPageViewModel by viewModels()@Injectlateinit var child: Child;override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)enableEdgeToEdge()setContent {val sharedPreferences = MyApplication.sharedPreferencesLog.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.lifecycleScopeval 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@Providesfun 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@Providesfun 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@Providesfun 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@Providesfun 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@Providesfun 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@Providesfun 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@Providesfun 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这是viewmodelLog.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这是viewmodelLog.i("测试", "ProfileScreenChild启动了" + viewModel.toString() + "这是viewmodel") //在同一个导航中是一致的
//    Text("子组件的值: ${viewModel.data.value}")val childNavController = rememberNavController()NavHost(childNavController, startDestination = ProfileScreenChild1("你好测试子组件Pchild1")) {composable<ProfileScreenChild2> { backStackEntry ->//专门用来拿到当前导航的堆栈里的ViewModelval 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>@Insertfun insertAll(vararg users: User)@Deletefun 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@Providesfun provideAppDataBase(@ApplicationContext context:Context): AppDataBase {return AppDataBase.getInstance(context)}@Singleton@Providesfun provideUserDao(appDataBase: AppDataBase): UserDao {return appDataBase.UserDao()}}

8.Permission

@SuppressLint("CheckResult")
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun PermissionPage(){// Camera permission stateval 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 objectsval data = Project("kotlinx.serialization", "Kotlin")val string = Json.encodeToString(data)println(string) // {"name":"kotlinx.serialization","language":"Kotlin"}// Deserializing back into objectsval 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.runBlockingfun main() {runBlocking {val coldFlow =  flow {for (i in 1..5){delay(3000)emit(i)}}// 订阅者 1launch {coldFlow.collect { value ->println("Subscriber 1 received: $value")}}// 订阅者 2launch {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.runBlockingfun main() {runBlocking {// 创建一个 StateFlow 实例,初始状态为 0val 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.runBlockingfun main() {runBlocking {// 创建一个 SharedFlowval sharedFlow = MutableSharedFlow<Int>()// 启动一个协程来发射数据launch {repeat(10) {delay(100) // 模拟异步操作sharedFlow.emit(it) // 发射数据}}// 订阅 SharedFlowlaunch {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.currentColumn(modifier = Modifier.fillMaxSize().padding(30.dp)) {// Example buttons to switch languagesRow {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 languageText(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.文件提供者

     <providerandroid:authorities="${applicationId}.fileprovider"android:name="androidx.core.content.FileProvider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid: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"); // 下载目录// 插入文件到 MediaStorereturn 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 URIUri 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 URIshareIntent.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.MessageDigestobject 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 -256val 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()) + pwdnfcA.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()) + writeBytenfcA.transceive(cmd)}}

3.Model

/*** 全局唯一NFC模块*/
@Module
@InstallIn(SingletonComponent::class)
object NfcModel {@Singleton@Providesfun 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.nfcAdapterval list = remember {mutableStateListOf<String>()}val context = LocalContext.currentLaunchedEffect(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 = 3000try {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)}}}
}


http://www.mrgr.cn/news/36902.html

相关文章:

  • el-table+el-form实现表单校验和解决不垂直居中导致的问题
  • OccLLaMA:首个结合3D占用预测、语言、行为构建的生成式世界模型
  • 物联网网络中集中式与分布式SDN环境的比较分析
  • ps快速更换电商图片背景,轻松变成白底图
  • VulnStack-红日靶机(二)
  • web安全攻防渗透测试实战指南_web安全攻防渗透测试实战指南,零基础入门到精通,收藏这一篇就够了
  • 0基础学前端 day4
  • 微调(Fine-tuning)
  • 2024年【烟花爆竹经营单位主要负责人】免费试题及烟花爆竹经营单位主要负责人考试技巧
  • 【vue3】登录功能怎么实现?
  • 离散化 ---( 求区间和)
  • 国产化框架PaddleYOLO结合Swanlab进行作物检测
  • (22)activeMQ部署
  • 1小时极限速通MC局域网联机:PCL2 + Zerotier局域网联机方案
  • 一招搞定苹果安卓跨系统传输,文件大小再也不是问题
  • SQL 查询语句的顺序详解
  • 玩转springboot之springboot定制化tomcat
  • 【线程】线程池
  • RestSharp简介
  • SDL录制音频并播放