Compose-navigation: 问题汇总

本文探讨了如何在使用Compose时为Navigation添加动画效果,包括slide、fade和scale等,并解决首页回退问题。同时,针对ViewModel的声明周期管理,通过Hilt库实现与NavHostController的对应销毁,以优化内存利用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

Compose 非常适合构建单Activity应用, 本篇适合单Activity,多Composable,无Fragment的情况.


一、默认的navigation没有页面进出的动画

这里需要 accompanist-navigation-animation 库.
该部分出自: 动画实现更简单,Navigation Compose 帮您忙

1.导包 accompanist-navigation-animation

implementation "androidx.navigation:navigation-compose:2.4.2"
implementation "com.google.accompanist:accompanist-navigation-animation:0.24.10-beta"

2.自定义navigation-composable

这里默认 页面打开时从右侧进入; 注意这里 composable 导包, 是accompanist下的.

import androidx.compose.animation.*
import androidx.compose.runtime.Composable
import androidx.navigation.NamedNavArgument
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavDeepLink
import androidx.navigation.NavGraphBuilder
import com.google.accompanist.navigation.animation.composable

/**
 * accompanist-navigation-animation
 * navHostController.navigate()     时 右进左出
 * navHostController.popBackStack() 时 左进右出
 */
@ExperimentalAnimationApi
fun NavGraphBuilder.myComposable(
  route: String,
  arguments: List<NamedNavArgument> = emptyList(),
  deepLinks: List<NavDeepLink> = emptyList(),
  enterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?)? = {
    slideInHorizontally(initialOffsetX = { it })
  },
  exitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?)? = {
    slideOutHorizontally(targetOffsetX = { -it })
  },
  popEnterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?)? = {
    slideInHorizontally(initialOffsetX = { -it })
  },
  popExitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?)? = {
    slideOutHorizontally(targetOffsetX = { it })
  },
  content: @Composable AnimatedVisibilityScope.(NavBackStackEntry) -> Unit
) {
  composable(
    route, arguments, deepLinks,
    enterTransition = enterTransition,
    exitTransition = exitTransition,
    popEnterTransition = popEnterTransition,
    popExitTransition = popExitTransition,
    content = content
  )
}

3.使用

注意:

  • 需使用 AnimatedNavHost 替换 NavHost
  • 需使用 rememberAnimatedNavController() 替换 rememberNavController()
// Activity
var navC: NavHostController? = null
@OptIn(ExperimentalAnimationApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)
  setContent {
    MyTheme {
      navC = rememberAnimatedNavController()
      MainNavHost(navC!!)
    }
  }
}

// MainNavHost
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun MainNavHost(
  navC: NavHostController,
) {
  AnimatedNavHost(
    navController = navC,
    startDestination = MainScreen.Home.name,
    modifier = Modifier.fillMaxSize()
  ) {
    myComposable(MainScreen.Home.name){
      HomeMain(navC)
    }
    myComposable(MainScreen.Setting.name){
      SettingMain(navC)
    }
    myComposable(MainScreen.AboutUs.name){
      AboutUsMain(navC)
    }
  }
}

// MainScreen
enum class MainScreen {
  Home,       // 首页
  Setting,    // 设置
  AboutUs,    // 关于我们
}

OK, 默认 slide 动画的 navigation 已经实现;
除了: slide 还有 fade, scale, expand 等动画


二、首页回退问题

super.onBackPressed() 必须要调用, 否则 navigation 的回退就不生效了.

var navC: NavHostController? = null

// 1.5秒内的连击 退出应用
private var mBackTime by Delegates.observable(0L) { _, old, new ->
  if (new - old > 1500) {
    Toast.makeText(applicationContext, "再按一次退出", Toast.LENGTH_SHORT).show()
  } else {
    finish()
  }
}

// 回退时, 如果已在 Home首页, 则1.5秒内连击退出;
override fun onBackPressed() {
  if(navC?.currentDestination?.route == MainScreen.Home.name){
    mBackTime = System.currentTimeMillis()
  } else {
    super.onBackPressed()
  }
}

三、ViewModel 声明周期问题

在Compose中, 可以很方便的拿到 ViewModel 实例; 如下:

// @Composable 函数中
val vm = viewModel<MainVm>()

但在单 Activity, 无Fragment 的应用中, viewModel() 的声明周期默认与Activity相关
而我们希望 navC.popBackStack(), 被退出页面的相关的 ViewModel 销毁,释放内存;

这时候就需要借助 hilt 这个依赖注入库了.

1.配置 Hilt

// Project - build.gradle
buildscript {
    ext {
    	...
        hilt_version = "2.40.5"
    }
	// 1.加入依赖 hilt plugin
    dependencies {
        classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
    }
}

// app - build.gradle
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'	// 2.加入 dagger hilt plugin
}

// 3.引入库:
implementation "androidx.hilt:hilt-navigation-compose:1.0.0"
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"

2.加入注解

// Application;  1.加上 @HiltAndroidApp 注解
@HiltAndroidApp
class MyApplication : ComApplication()

// Activity;	2.加上 @AndroidEntryPoint 注解
@AndroidEntryPoint
class MyActivity: ComponentActivity() 

// ViewModel;	3.加上 @HiltViewModel 和 @Inject 注解
@HiltViewModel
class MainVm @Inject constructor() : ViewModel()

3.使用 hiltViewModel() 函数

@Composable
fun SettingMain(
  navC: NavHostController,
  vm: SettingVm = hiltViewModel()
) { ... }

总结

没有总结!

上一篇: Compose-自定义输入框(BasicTextField)
下一篇: Compose-时间选择器

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值