• 注册
  • Android博客 Android博客 关注:0 内容:1410

    Jetpack Compose初体验–(导航、生命周期等)

  • 查看作者
  • 打赏作者
  • 当前位置: 职业司 > Android开发 > Android博客 > 正文
    • Android博客
    • 本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!

      上一篇Jetpack Compose初体验
      中练习了Compose中的布局、自定义布局、自定义view、动画、手势等操作,这些都是在一个页面中完成,一个应用不能只有一个页面,今天来练习一下Jetpack Compose中的导航。

      普通导航

      在Jetpack Compose中导航可以使用Jetpack中的Navigation组件,引入相关的扩展依赖就可以了 Navigation官方文档

      implementation "androidx.navigation:navigation-compose:2.4.0-alpha01"
      复制代码

      使用Navigation导航用到两个比较重要的对象NavHost和NavController。

      • NavHost用来承载页面,和管理导航图
      • NavController用来控制如何导航还有参数回退栈等

      导航的路径使用字符串来表示,当使用NavController导航到某个页面的时候,NavHost内部会自动进行页面重组。

      来个小栗子实践一下

      @Composable
      fun MainView(){
      val navController = rememberNavController()
      NavHost(navController = navController, startDestination = "first_screen"){
      composable("first_screen"){
      FirstScreen(navController = navController)
      }
      composable("second_screen"){
      SecondScreen(navController = navController)
      }
      composable("third_screen"){
      ThirdScreen(navController = navController)
      }
      }
      }
      复制代码
      • 通过rememberNavController()方法创建navController对象
      • 创建NavHost对象,传入navController并指定首页
      • 通过composable()方法来往NavHost中添加页面,构造方法中的字符串就代表该页面的路径,后面的第二个参数就是具体的页面。

      下面把这三个页面写出来,每个页面里面都有个按钮继续执行其他导航

      @Composable
      fun FirstScreen(navController: NavController){
      Column(modifier = Modifier.fillMaxSize().background(Color.Blue),
      verticalArrangement = Arrangement.Center,
      horizontalAlignment = Alignment.CenterHorizontally
      ) {
      Button(onClick = {
      navController.navigate("second_screen")
      }) {
      Text(text = "I am First 点击我去Second")
      }
      }
      }
      @Composable
      fun SecondScreen(navController: NavController){
      Column(modifier = Modifier.fillMaxSize(),
      verticalArrangement = Arrangement.Center,
      horizontalAlignment = Alignment.CenterHorizontally) {
      Button(onClick = {
      navController.navigate("third_screen")
      }) {
      Text(text = "I am Second 点击我去Third")
      }
      }
      }
      @Composable
      fun ThirdScreen(navController: NavController){
      Column(modifier = Modifier.fillMaxSize(),
      verticalArrangement = Arrangement.Center,
      horizontalAlignment = Alignment.CenterHorizontally) {
      Button(onClick = {
      navController.navigate("first_screen")
      }) {
      Text(text = "I am Third 点击我去first")
      }
      }
      }
      复制代码

      这样一个简单的导航效果就完成了,感觉用了这个之后,要跟activity和fragment说拜拜了~~ ,全场只需一个activity加一堆可组合项(@Composable),新建一个页面简单了太多太多。

      当然页面之间跳转传参是少不了的,Compose中如何传参呢?

      参数传递肯定有发送端和接收端,navController是发送端,NavHost是接收端。先在NavHost中配置参数占位符,和接收取参数的方法。

      @Composable
      fun MainView(){
      val navController = rememberNavController()
      NavHost(navController = navController, startDestination = "first_screen"){
      composable("first_screen"){
      FirstScreen(navController = navController)
      }
      composable("second_screen/{userId}/{isShow}",
      //默认情况下 所有参数都会被解析为字符串 如果不是字符串需要单独指定 type
      arguments = listOf(navArgument("isShow"){type = NavType.BoolType})
      ){ backStackEntry ->
      SecondScreen(navController = navController,
      backStackEntry.arguments?.getString("userId"),
      backStackEntry.arguments?.getBoolean("isShow")!!
      )
      }
      composable("third_screen?selectable={selectable}",
      arguments = listOf(navArgument("selectable"){defaultValue = "哈哈哈我是可选参数的默认值"})){
      ThirdScreen(navController = navController,it.arguments?.getString("selectable"))
      }
      composable("four_screen"){
      FourScreen(navController = navController)
      }
      }
      }
      复制代码

      如上代码,接收参数直接在在该页面地址后面添加参数占位符类似second_screen/{userId}/{isShow},然后通过arguments参数来接收arguments = listOf(navArgument("isShow"){type = NavType.BoolType})。还可以通过defaultValue来定义参数的默认值。

      默认情况下 所有参数都会被解析为字符串 如果不是字符串需要单独指定 type。

      参数发送端更简单,参数直接跟到页面路径后面就可以,类似navController.navigate("second_screen/12345/true")
      下面给前面的页面添加上参数

      @Composable
      fun FirstScreen(navController: NavController){
      Column(modifier = Modifier
      .fillMaxSize()
      .background(Color.Blue),
      verticalArrangement = Arrangement.Center,
      horizontalAlignment = Alignment.CenterHorizontally
      ) {
      Button(onClick = {
      navController.navigate("second_screen/12345/true"){
      }
      }) {
      Text(text = "I am First 点击我去Second")
      }
      Spacer(modifier = Modifier.size(30.dp))
      }
      }
      @Composable
      fun SecondScreen(navController: NavController,userId:String?,isShow:Boolean){
      Column(modifier = Modifier
      .fillMaxSize()
      .background(Color.Green),
      verticalArrangement = Arrangement.Center,
      horizontalAlignment = Alignment.CenterHorizontally) {
      Button(onClick = {
      navController.navigate("third_screen?selectable=测试可选参数"){
      popUpTo(navController.graph.startDestinationId){saveState = true}
      }
      }) {
      Text(text = "I am Second 点击我去Third")
      }
      Spacer(modifier = Modifier.size(30.dp))
      Text(text = "arguments ${userId}")
      if(isShow){
      Text(text = "测试boolean值")
      }
      }
      }
      @Composable
      fun ThirdScreen(navController: NavController,selectable:String?){
      Column(modifier = Modifier.fillMaxSize(),
      verticalArrangement = Arrangement.Center,
      horizontalAlignment = Alignment.CenterHorizontally) {
      Button(onClick = {
      navController.navigate("first_screen")
      }) {
      Text(text = "I am Third 点击我去first")
      }
      Spacer(modifier = Modifier.size(30.dp))
      Button(onClick = {
      navController.navigate("four_screen")
      }) {
      Text(text = "I am Third 点击我去four")
      }
      selectable?.let { Text(text = it) }
      }
      }
      复制代码

      效果如下

      Jetpack Compose初体验–(导航、生命周期等)

      生命周期

      既然新的界面不使用activity或者fragment了,但是activity和fragment中的生命周期是非常有用的比如创建和销毁某些对象。那么Jetpack Compose中的每个组合函数的生命周期是怎样的呢?

      可组合项的生命周期比视图比activity 和 fragment 的生命周期更简单,一般是进入组合、执行0次或者多次重组、退出组合。生命周期相关的函数主要有下面的几个,使用@Composable修饰的可组合函数中没有自带的生命周期函数,想要监听其生命周期,需要使用Effect API

      • LaunchedEffect:第一次调用Compose函数的时候调用
      • DisposableEffect:内部有一个 onDispose()函数,当页面退出时调用
      • SideEffect:compose函数每次执行都会调用该方法

      来个小例子体验一下

      @Composable
      fun LifecycleDemo() {
      val count = remember { mutableStateOf(0) }
      Column {
      Button(onClick = {
      count.value++
      }) {
      Text("Click me")
      }
      LaunchedEffect(Unit){
      Log.d("Compose", "onactive with value: " + count.value)
      }
      DisposableEffect(Unit) {
      onDispose {
      Log.d("Compose", "onDispose because value=" + count.value)
      }
      }
      SideEffect {
      Log.d("Compose", "onChange with value: " + count.value)
      }
      Text(text = "You have clicked the button: " + count.value.toString())
      }
      }
      复制代码

      效果如下:

      Jetpack Compose初体验–(导航、生命周期等)

      然后把前面的例子稍微改一下,我们把LaunchedEffect和DisposableEffect一起放到一个if语句里面

      @Composable
      fun LifecycleDemo() {
      val count = remember { mutableStateOf(0) }
      Column {
      Button(onClick = {
      count.value++
      }) {
      Text("Click me")
      }
      if (count.value < 3) {
      LaunchedEffect(Unit){
      Log.d("Compose", "onactive with value: " + count.value)
      }
      DisposableEffect(Unit) {
      onDispose {
      Log.d("Compose", "onDispose because value=" + count.value)
      }
      }
      }
      SideEffect {
      Log.d("Compose", "onChange with value: " + count.value)
      }
      Text(text = "You have clicked the button: " + count.value.toString())
      }
      }
      复制代码

      那么此时的生命周期就是:当首次进入if语句的时候执行LaunchedEffect函数,离开if语句的时候,就执行DisposableEffect方法。

      底部导航

      说到导航就不得不说底部导航和顶部导航,底部导航的实现非常简单,直接使用JetPack Compose提供的脚手架在结合navController和NavHost就能轻松实现

      @Composable
      fun BottomMainView(){
      val bottomItems = listOf(Screen.First,Screen.Second,Screen.Third)
      val navController = rememberNavController()
      Scaffold(
      bottomBar = {
      BottomNavigation {
      val navBackStackEntry by navController.currentBackStackEntryAsState()
      val currentRoute = navBackStackEntry?.destination?.route
      bottomItems.forEach{screen ->
      BottomNavigationItem(
      icon = { Icon(Icons.Filled.Favorite,"") },
      label = { Text(stringResource(screen.resourceId)) },
      selected = currentRoute == screen.route,
      onClick = {
      navController.navigate(screen.route){
      //当底部导航导航到在非首页的页面时,执行手机的返回键 回到首页
      popUpTo(navController.graph.startDestinationId){saveState = true}
      //从名字就能看出来 跟activity的启动模式中的SingleTop模式一样 避免在栈顶创建多个实例
      launchSingleTop = true
      //切换状态的时候保存页面状态
      restoreState = true
      }
      })
      }
      }
      }
      ){
      NavHost(navController = navController, startDestination = Screen.First.route ){
      composable(Screen.First.route){
      First(navController)
      }
      composable(Screen.Second.route){
      Second(navController)
      }
      composable(Screen.Third.route){
      Third(navController)
      }
      }
      }
      }
      @Composable
      fun First(navController: NavController){
      Column(modifier = Modifier.fillMaxSize(),
      verticalArrangement = Arrangement.Center,horizontalAlignment = Alignment.CenterHorizontally) {
      Text(text = "First",fontSize = 30.sp)
      }
      }
      @Composable
      fun Second(navController: NavController){
      Column(modifier = Modifier.fillMaxSize(),
      verticalArrangement = Arrangement.Center,horizontalAlignment = Alignment.CenterHorizontally) {
      Text(text = "Second",fontSize = 30.sp)
      }
      }
      @Composable
      fun Third(navController: NavController){
      Column(modifier = Modifier.fillMaxSize(),
      verticalArrangement = Arrangement.Center,horizontalAlignment = Alignment.CenterHorizontally) {
      Text(text = "Third",fontSize = 30.sp)
      }
      }
      复制代码

      效果如下

      Jetpack Compose初体验–(导航、生命周期等)

      顶部导航

      顶部导航使用TabRow和ScrollableTabRow这两个组件,其内部都是由一个一个的Tab组件组成。TabRow是平分整个屏幕的宽度,ScrollableTabRow可以超出屏幕宽度并且可以滑动,用法都是一样。

      @Composable
      fun TopTabRow(){
      var state by remember { mutableStateOf(0) }
      var titles = listOf("Java","Kotlin","Android","Flutter")
      Column {
      TabRow(selectedTabIndex = state) {
      titles.forEachIndexed{index,title ->
      run {
      Tab(
      selected = state == index,
      onClick = { state = index },
      text = {
      Text(text = title)
      })
      }
      }
      }
      Column(Modifier.weight(1f)) {
      when (state){
      0 -> TopTabFirst()
      1 -> TopTabSecond()
      2 -> TopTabThird()
      3 -> TopTabFour()
      }
      }
      }
      }
      @Composable
      fun TopTabFirst(){
      Column(modifier = Modifier.fillMaxSize(), verticalArrangement=Arrangement.Center,
      horizontalAlignment = Alignment.CenterHorizontally) {
      Text(text = "Java")
      }
      }
      @Composable
      fun TopTabSecond(){
      Column(modifier = Modifier.fillMaxSize(), verticalArrangement=Arrangement.Center,
      horizontalAlignment = Alignment.CenterHorizontally) {
      Text(text = "Kotlin")
      }
      }
      @Composable
      fun TopTabThird(){
      Column(modifier = Modifier.fillMaxSize(), verticalArrangement=Arrangement.Center,
      horizontalAlignment = Alignment.CenterHorizontally) {
      Text(text = "Android")
      }
      }
      @Composable
      fun TopTabFour(){
      Column(modifier = Modifier.fillMaxSize(), verticalArrangement=Arrangement.Center,
      horizontalAlignment = Alignment.CenterHorizontally) {
      Text(text = "Flutter")
      }
      }
      复制代码

      Jetpack Compose初体验–(导航、生命周期等)

      上面只能实现点击每个Tab 切换不同的页面,如果我们想要实现类似我们在xml布局中的ViewPage+TabLayout的效果呢

      在Jetpack中怎么实现ViewPage的效果呢,Google的github上提供了一个半官方的库名字叫pager:github.com/google/acco…

      implementation "com.google.accompanist:accompanist-pager:0.13.0"
      复制代码

      该库目前还是实验性的,以后API都可能会修改,目前使用的时候需要使用@ExperimentalPagerApi注解标记。

      @ExperimentalPagerApi
      @Composable
      fun TopScrollTabRow(){
      var titles = listOf("Java","Kotlin","Android","Flutter","scala","python")
      val scope = rememberCoroutineScope()
      var pagerState = rememberPagerState(
      pageCount = titles.size, //总页数
      initialOffscreenLimit = 2, //预加载的个数
      infiniteLoop = true, //无限循环
      initialPage = 0, //初始页面
      )
      Column {
      ScrollableTabRow(
      selectedTabIndex = pagerState.currentPage,
      modifier = Modifier.wrapContentSize(),
      edgePadding = 16.dp
      ) {
      titles.forEachIndexed{index,title ->
      run {
      Tab(
      selected = pagerState.currentPage == index,
      onClick = {
      scope.launch {
      pagerState.scrollToPage(index)
      }
      },
      text = {
      Text(text = title)
      })
      }
      }
      }
      HorizontalPager(
      state=pagerState,
      modifier = Modifier.weight(1f)
      ) {index ->
      Column(modifier = Modifier.fillMaxSize(),verticalArrangement = Arrangement.Center,horizontalAlignment = Alignment.CenterHorizontally) {
      Text(text = titles[index])
      }
      }
      }
      }
      复制代码

      pagerState.scrollToPage(index)方法可以控制pager滚动,不过它是一个suspend修饰的方法,需要运行在协程中,在jetpack compose中使用协程可以使用rememberCoroutineScope()方法来获取一个compose中的协程的作用域

      效果如下:

      Jetpack Compose初体验–(导航、生命周期等)

      Banner

      pager库都引入了那顺便吧Banner效果也练习一下,为了显示网络图片还得引入一个新的库,accompanist-coil。在JetPack Compose中官方提供了两个显示网络图片的库accompanist-coil和accompanist-glide,这里使用accompanist-coil。

      implementation 'com.google.accompanist:accompanist-coil:0.11.1'
      复制代码
      @ExperimentalPagerApi
      @Composable
      fun Third(navController: NavController){
      var pics = listOf("https://wanandroid.com/blogimgs/8a0131ac-05b7-4b6c-a8d0-f438678834ba.png",
      "https://www.wanandroid.com/blogimgs/62c1bd68-b5f3-4a3c-a649-7ca8c7dfabe6.png",
      "https://www.wanandroid.com/blogimgs/50c115c2-cf6c-4802-aa7b-a4334de444cd.png",
      "https://www.wanandroid.com/blogimgs/90c6cc12-742e-4c9f-b318-b912f163b8d0.png")
      Column(modifier = Modifier.fillMaxSize(),
      horizontalAlignment = Alignment.CenterHorizontally) {
      Text(text = "Third",fontSize = 30.sp)
      var pagerState = rememberPagerState(
      pageCount = 4, //总页数
      initialOffscreenLimit = 2, //预加载的个数
      infiniteLoop = true, //无限循环
      initialPage = 0, //初始页面
      )
      Box(modifier = Modifier
      .fillMaxWidth()
      .height(260.dp)
      .background(color = Color.Yellow)) {
      HorizontalPager(
      state=pagerState,
      modifier = Modifier.fillMaxSize()
      ) {index ->
      Image(modifier = Modifier.fillMaxSize(),
      painter = rememberCoilPainter(request = pics[index]),
      contentScale=ContentScale.Crop,
      contentDescription = "图片描述")
      }
      HorizontalPagerIndicator(
      pagerState = pagerState,
      modifier = Modifier
      .padding(16.dp).align(Alignment.BottomStart),
      )
      }
      }
      }
      复制代码

      Jetpack Compose初体验–(导航、生命周期等)

      使用Jetpack Compose写页面感觉比使用xml简单了很多,相信未来Android中的xml布局会像前端的jquary一样用的越来越少。

      请登录之后再进行评论

      登录

      手机阅读天地(APP)

      • 微信公众号
      • 微信小程序
      • 安卓APP
      手机浏览,惊喜多多
      匿名树洞,说我想说!
      问答悬赏,VIP可见!
      密码可见,回复可见!
      即时聊天、群聊互动!
      宠物孵化,赠送礼物!
      动态像框,专属头衔!
      挑战/抽奖,金币送不停!
      赶紧体会下,不会让你失望!
    • 实时动态
    • 签到
    • 做任务
    • 发表内容
    • 偏好设置
    • 到底部
    • 帖子间隔 侧栏位置:
    • 还没有账号?点这里立即注册