Android Jetpack Compose之底部导航栏的实现

这篇具有很好参考价值的文章主要介绍了Android Jetpack Compose之底部导航栏的实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1.概述

写过一段Android jetpack compose 界面的小伙伴应该都用过Compose的脚手架Scaffold,利用它我们可以很快的实现一个现代APP的主流界面架构,即一个带顶部导航栏和底部导航栏的界面架构,我们基于这个架构可以快速的搭建出我们想要的页面效果。而今天的文章就是要介绍如何实现一个有特点的底部导航栏。底部导航栏一般都是在界面的最底部有可供切换的几个按钮,点击对应的按钮可以切换到对应的页面,例如微信的底部导航栏,分为“微信、通讯录、发现、我”四个选项,这四个选项也比较中规中矩,使用Compose实现起来也很简单,只要配置好按钮和对应的文字就可以。但是如果设计的同学不按常理出牌,比如像咸鱼那样,搞5个按钮,其中有一个还特别大。如下图:
jetpackcompose的bottomnavigation底部导航栏,Android Jetpack Compose,Kotlin--将Android进行到底,android jetpack,Compose,底部导航栏那阁下该如何应对呢。本文就介绍下如何实现这样的底部导航栏。

2. 效果展示

实现其实也不难,只需要设计的小朋友给咱们切一张背景图,就是上图中的带弧形的背景图给我们,我们再绘制到底部导航栏的背后就行了,先看下效果:

jetpackcompose的bottomnavigation底部导航栏,Android Jetpack Compose,Kotlin--将Android进行到底,android jetpack,Compose,底部导航栏

3. 代码实现

3.1 定义底部导航栏的tab项

经过观察我们可以发现底部导航栏的显示有图标和文字,并且选中的时候颜色会变化,所以我们需要定义一个类来保存这些状态,代码如下:

sealed class ScreenPage(
    val route: String,
    @StringRes val resId: Int = 0, // 如果没有文字标题,就不需要使用这个属性
    val iconSelect: Int,
    val iconUnselect: Int,
    var isShowText: Boolean = true
) {
    object Home : ScreenPage(
        route = "home",
        resId = R.string.str_main_title_home,
        iconSelect = R.drawable.ic_home_selected,
        iconUnselect = R.drawable.ic_home_unselected
    )

    object Recommend : ScreenPage(
        route = "recommend",
        resId = R.string.str_main_title_recommend,
        iconSelect = R.drawable.ic_recom_selected,
        iconUnselect = R.drawable.ic_recom_unselected
    )

    object Capture : ScreenPage(
        route = "add",
        iconSelect = R.drawable.ic_add_selected,
        iconUnselect = R.drawable.ic_add_unselected,
        isShowText = false
    )

    object Find : ScreenPage(
        route = "find",
        resId = R.string.str_main_title_find,
        iconSelect = R.drawable.ic_find_selected,
        iconUnselect = R.drawable.ic_find_unselected
    )

    object Mine : ScreenPage(
        route = "mine",
        resId = R.string.str_main_title_mine,
        iconSelect = R.drawable.ic_mine_selected,
        iconUnselect = R.drawable.ic_mine_unselected
    )
}

如上面的代码所示,我们在对应的tab中添加上展示的文字的资源ID,选中和未选中的图片资源ID,以及路由,当我们需要切换到其他tab时改变这些属性就可以了,路由可以帮助我们跳转到其他页面。是否显示title的属性可以帮助我们自定义底部Tab的样式
注意:图中的图标资源可以去阿里的矢量图标库下载 阿里矢量图标库地址

3.2 整体页面架构搭建

使用Scaffold搭建页面的架构,这里的Scaffold需要特别注意,我们用到的是material中的Scafold,不是material3中的那个 代码如下:

    val items = listOf(
        ScreenPage.Home,
        ScreenPage.Recommend,
        ScreenPage.Capture,
        ScreenPage.Find,
        ScreenPage.Mine
    )

    val navController = rememberNavController()
    val context = LocalContext.current

    Scaffold(
        bottomBar = {.....省略底部导航栏的代码,这部分单独介绍......}
        },
        backgroundColor = Color.LightGray
    ) { paddingValues ->
        Log.d("walt-zhong", "paddingValues: $paddingValues")
        NavHost(
            navController,
            startDestination = ScreenPage.Home.route,
//            modifier = Modifier.padding(paddingValues) 
// 加了会导致底部多出一些padding导致影响透明背景的显示
        ) {
            composable(ScreenPage.Home.route) {
                HomePage()
            }

            composable(ScreenPage.Recommend.route) {
                RecPage()
            }

            composable(ScreenPage.Capture.route) {
                // CapturePage()
            }

            composable(ScreenPage.Find.route) {
                // FindPage()
            }

            composable(ScreenPage.Mine.route) {
                // MinePage()
            }
        }
    }

我们使用Compose的navigation做页面导航,这里就不介绍相关的知识了,有兴趣的自行百度。然后配置好需要跳转的页面
这里需要注意的是,不要将Scaffold提供的padding值设置给底部导航栏或者是NavHost,因为这样会导致我们的透明背景被遮挡,导致无法显示弧形的底部导航栏效果。

3.3 底部导航栏的实现

底部导航栏的实现主要有背景的绘制,选中tab的状态变更以及对应页面的切换,代码如下:

  BottomAppBar(
                elevation = 0.dp,
                backgroundColor = Color.Transparent,
                contentColor = Color.Transparent,
                modifier = Modifier
                    .wrapContentHeight()
                    .fillMaxWidth()
                    .drawWithCache {
                        val bgImg = ContextCompat.getDrawable(
                            context,
                            R.drawable.main_nav_bg
                        )
                        onDrawBehind {
                            bgImg!!.updateBounds(
                                0,
                                0, // 这里可以调整中间的大按钮的上下位置。
                                size.width.toInt(),
                                size.height.toInt()
                            )
                            bgImg.draw(drawContext.canvas.nativeCanvas)
                        }
                    }
            ) {
                val navBackStackEntry by navController.currentBackStackEntryAsState()
                val currentDestination = navBackStackEntry?.destination
                var isSelected: Boolean
                items.forEach { screenPage ->
                    isSelected =
                        currentDestination?.hierarchy?.any { it.route == screenPage.route } == true
                    CompositionLocalProvider(LocalRippleTheme provides NoRippleTheme) {
                        BottomNavigationItem(
                            selected = isSelected,
                            selectedContentColor = Color(0xFF037FF5),
                            unselectedContentColor = Color(0xFF31373D),
                            onClick = {
                                navController.navigate(screenPage.route) {
                                    //点击Item时,清空栈内到NavOptionsBuilder.popUpTo ID之间的所有Item
                                    // 避免栈内节点的持续增加,同时saveState用于界面状态的恢复
                                    popUpTo(navController.graph.findStartDestination().id) {
                                        saveState = true
                                    }

                                    // 避免多次点击Item时产生多个实列
                                    launchSingleTop = true
                                    // 当再次点击之前的Item时,恢复状态
                                    restoreState = true
                                }
                            },

                            icon = {
                                Image(
                                    painter = if (isSelected) {
                                        painterResource(screenPage.iconSelect)
                                    } else {
                                        painterResource(screenPage.iconUnselect)
                                    },
                                    null,
                                    modifier = if (!screenPage.isShowText) {
                                        Modifier.size(58.dp)
                                    } else {
                                        Modifier.size(25.dp)
                                    },
                                    contentScale = ContentScale.Crop
                                )
                            },
                            alwaysShowLabel = screenPage.isShowText,
                            label =
                            if (!screenPage.isShowText) {
                                null
                            } else {
                                {
                                    Text(
                                        text = stringResource(screenPage.resId),
                                        style = TextStyle(
                                            fontSize = 10.sp,
                                            fontWeight = FontWeight.Medium,
                                            color = if (isSelected) {
                                                Color.Yellow
                                            } else {
                                                Color.Black
                                            }
                                        )
                                    )
                                }
                            },
                            modifier = if (screenPage.isShowText) {
                                Modifier.padding(top = 10.dp)
                            } else {
                                Modifier.padding(top = 0.dp)
                            }
                        )
                    }
                }
            }

上面的代码应该都很好懂,所以我们就只讲下绘制背景部分,其他的读者可以自行阅读代码,绘制背景部分的代码是:

   Modifier.drawWithCache {
    val bgImg = ContextCompat.getDrawable(
        context,
        R.drawable.main_nav_bg
    )
    onDrawBehind {
        bgImg!!.updateBounds(
            0,
            0, // 这里可以调整中间的大按钮的上下位置。
            size.width.toInt(),
            size.height.toInt()
        )
        bgImg.draw(drawContext.canvas.nativeCanvas)
    }
}

这里我们可以使用Modiofier.drawBehind { }方法,但是这个方法会在每次重组的时候重新走一遍,所以我们使用Modifier.drawWithCache来优化它。这里我们将弧形背景绘制到底部导航栏的后面。就呈现出来一个弧形的底部导航栏,这时候我们还需要绘制tab,我们可以根据配置去改变TAB的图标大小和状态。添加动画等。
在这里我们还需要注意的是我们需将底部导航栏BottomAppBar的背景设置成透明的,否则他会影响我们的弧形背景的显示

还有设置文字的时候需要特别注意,如下面的代码所示:

BottomNavigationItem(
    ...省略掉部分不相干代码....
    alwaysShowLabel = screenPage.isShowText,
    label =
    if (!screenPage.isShowText) {
        null
    } else {
        {
            Text(
                text = stringResource(screenPage.resId),
                style = TextStyle(
                    fontSize = 10.sp,
                    fontWeight = FontWeight.Medium,
                    color = if (isSelected) {
                        Color.Yellow
                    } else {
                        Color.Black
                    }
                )
            )
        }
    },
    modifier = if (screenPage.isShowText) {
        Modifier.padding(top = 10.dp)
    } else {
        Modifier.padding(top = 0.dp)
    }
)

如上面的代码所示,我们想要底部的部分Tab显示的时候不展示文字,这时就需要将alwaysShowLabel设置成false,但是这时候设置 label的时候,需要设置成null,否则我们的Tab显示会不正常,因为文字部分虽然不显示,但是内容还是占据着UI中的位置,导致不显示文字的TAB位置不正确。

3.4 所有代码

class BottomNavAct : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyComposeTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    MainContainerPage()
                }
            }
        }
    }
@Composable
fun MainContainerPage() {
    val items = listOf(
        ScreenPage.Home,
        ScreenPage.Recommend,
        ScreenPage.Capture,
        ScreenPage.Find,
        ScreenPage.Mine
    )

    val navController = rememberNavController()
    val context = LocalContext.current

    Scaffold(
        bottomBar = {
            BottomAppBar(
                elevation = 0.dp,
                backgroundColor = Color.Transparent,
                contentColor = Color.Transparent,
                modifier = Modifier
                    .wrapContentHeight()
                    .fillMaxWidth()
                    .drawWithCache {
                        val bgImg = ContextCompat.getDrawable(
                            context,
                            R.drawable.main_nav_bg
                        )
                        onDrawBehind {
                            bgImg!!.updateBounds(
                                0,
                                0, // 这里可以调整中间的大按钮的上下位置。
                                size.width.toInt(),
                                size.height.toInt()
                            )
                            bgImg.draw(drawContext.canvas.nativeCanvas)
                        }
                    }
            ) {
                val navBackStackEntry by navController.currentBackStackEntryAsState()
                val currentDestination = navBackStackEntry?.destination
                var isSelected: Boolean
                items.forEach { screenPage ->
                    isSelected =
                        currentDestination?.hierarchy?.any { it.route == screenPage.route } == true
                    CompositionLocalProvider(LocalRippleTheme provides NoRippleTheme) {
                        BottomNavigationItem(
                            selected = isSelected,
                            selectedContentColor = Color(0xFF037FF5),
                            unselectedContentColor = Color(0xFF31373D),
                            onClick = {
                                navController.navigate(screenPage.route) {
                                    //点击Item时,清空栈内到NavOptionsBuilder.popUpTo ID之间的所有Item
                                   
                                    // 避免栈内节点的持续增加,同时saveState用于界面状态的恢复
                                    popUpTo(navController.graph.findStartDestination().id) {
                                        saveState = true
                                    }

                                    // 避免多次点击Item时产生多个实列
                                    launchSingleTop = true
                                    // 当再次点击之前的Item时,恢复状态
                                    restoreState = true
                                }
                            },

                            icon = {
                                Image(
                                    painter = if (isSelected) {
                                        painterResource(screenPage.iconSelect)
                                    } else {
                                        painterResource(screenPage.iconUnselect)
                                    },
                                    null,
                                    modifier = if (!screenPage.isShowText) {
                                        Modifier.size(58.dp)
                                    } else {
                                        Modifier.size(25.dp)
                                    },
                                    contentScale = ContentScale.Crop
                                )
                            },
                            alwaysShowLabel = screenPage.isShowText,
                            label =
                            if (!screenPage.isShowText) {
                                null
                            } else {
                                {
                                    Text(
                                        text = stringResource(screenPage.resId),
                                        style = TextStyle(
                                            fontSize = 10.sp,
                                            fontWeight = FontWeight.Medium,
                                            color = if (isSelected) {
                                                Color.Yellow
                                            } else {
                                                Color.Black
                                            }
                                        )
                                    )
                                }
                            },
                            modifier = if (screenPage.isShowText) {
                                Modifier.padding(top = 10.dp)
                            } else {
                                Modifier.padding(top = 0.dp)
                            }
                        )
                    }
                }
            }
        },
        backgroundColor = Color.LightGray
    ) { paddingValues ->
        Log.d("walt-zhong", "paddingValues: $paddingValues")
        NavHost(
            navController,
            startDestination = ScreenPage.Home.route,
           
// modifier = Modifier.padding(paddingValues) // 加了会导致底部多出一些padding导致影响透明背景的示
        ) {
            composable(ScreenPage.Home.route) {
                HomePage()
            }

            composable(ScreenPage.Recommend.route) {
                RecPage()
            }

            composable(ScreenPage.Capture.route) {
                // CapturePage()
            }

            composable(ScreenPage.Find.route) {
                // FindPage()
            }

            composable(ScreenPage.Mine.route) {
                // MinePage()
            }
        }
    }
}

object NoRippleTheme : RippleTheme {
    @Composable
    override fun defaultColor(): Color = Color.Unspecified

    @Composable
    override fun rippleAlpha(): RippleAlpha =
        RippleAlpha(0.0f, 0.0f, 0.0f, 0.0f)
}

sealed class ScreenPage(
    val route: String,
    @StringRes val resId: Int = 0, // 如果没有文字标题,就不需要使用这个属性
    val iconSelect: Int,
    val iconUnselect: Int,
    var isShowText: Boolean = true
) {
    object Home : ScreenPage(
        route = "home",
        resId = R.string.str_main_title_home,
        iconSelect = R.drawable.ic_home_selected,
        iconUnselect = R.drawable.ic_home_unselected
    )

    object Recommend : ScreenPage(
        route = "recommend",
        resId = R.string.str_main_title_recommend,
        iconSelect = R.drawable.ic_recom_selected,
        iconUnselect = R.drawable.ic_recom_unselected
    )

    object Capture : ScreenPage(
        route = "add",
        iconSelect = R.drawable.ic_add_selected,
        iconUnselect = R.drawable.ic_add_unselected,
        isShowText = false
    )

    object Find : ScreenPage(
        route = "find",
        resId = R.string.str_main_title_find,
        iconSelect = R.drawable.ic_find_selected,
        iconUnselect = R.drawable.ic_find_unselected
    )

    object Mine : ScreenPage(
        route = "mine",
        resId = R.string.str_main_title_mine,
        iconSelect = R.drawable.ic_mine_selected,
        iconUnselect = R.drawable.ic_mine_unselected
    )
}

4.总结

本文主要介绍了一个特殊有趣的底部导航栏的实现方法,在大型项目的开发中,底部导航栏会被当成一个单独的模块维护,这就需要将底部导航栏抽取出来,本文只做一个抛砖引玉的作用,读者感兴趣可以试着抽取一下,我在项目中是抽取出来作为单独的模块的,发现的问题是抽取出来后 BottomNavigationItem的selectedContentColor 和unselectedContentColor 对于文字不生效了。最后我的解决方法是通过selected属性去动态修改对应的字体颜色和图片,在使用过程中读者有问题的话可以评论区一起交流文章来源地址https://www.toymoban.com/news/detail-842610.html

到了这里,关于Android Jetpack Compose之底部导航栏的实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 利用Jetpack Compose进行导航(Navigation)

    Jetpack Compose是一个现代化的,声明式的UI工具包,它让我们可以更快、更简便地构建Android的界面。今天,我们要讨论如何使用Jetpack Compose和它的导航库(Navigation Compose)来进行应用导航。 Navigation Compose是一个用于管理Compose界面中的导航的库,它不仅提供了丰富的API以支持不

    2024年02月12日
    浏览(31)
  • Android实现底部导航栏方法(Navigation篇)

    底部导航栏一直是大部分App不可缺失的一部分 最近注意到Jetpack中的Navigation支持Fragment的切换操作 特此浅研究一下 选择性跳过 此处使用Google开发者文档中介绍 使用nav文件配合 FragmentContainerView组件 实现Fragment的切换操作 创建nav文件 导入后,在项目的res文件夹下,右键选择

    2024年02月06日
    浏览(77)
  • 『Android基础入门』ViewPager+Fragment+BottomNavigationView实现底部导航

    👨‍🎓作者简介:一位喜欢写作,计科专业大三菜鸟 🏡个人主页:starry陆离 如果文章有帮到你的话记得点赞👍+收藏💗支持一下哦 在ViewPager与Fragment结合实现多页面滑动的学习上再进一步,记录一下ViewPager+Fragment+BottomNavigationView实现底部导航 1.复习ViewPager的用法 2.复习F

    2023年04月08日
    浏览(31)
  • android 关于TabLayout联动ViewPager2 实现底部导航栏

    最近在心血来潮想写在app 不过我关于android可以说是0基础 在写底部导航栏的时候去问了大佬才知道TabLayout和ViewPager 花了两天才看懂... 这里只是简单介绍因为我不准备专门做安卓软件所以在学的途中很多地方没有认真记 本篇文章使用的代码是Java 这里官方是有将两个进行联动

    2024年01月25日
    浏览(32)
  • 【Android入门到项目实战-- 11.2】—— 实现底部导航栏(RadioGroup+Fragment)

            效果如下,使用RadioGroup实现,不能左右滑动切换页面,适用于导航页里还有需要切换页面的场景,如果需要滑动效果,使用ViewPager实现。         以下示例按照图上实现,具体多少个页面,按需修改。         由于需要用到icon,提前下载好图标到drawable文件

    2024年02月10日
    浏览(64)
  • 安卓学习笔记之五:Android Studio_骰子案例3(Kotlin搭配 Jetpack Compose实现)

    使用 Compose 创建一款交互式  Dice Roller  Android 应用。 完成: 定义可组合函数。 使用组合创建布局。 使用  Button  可组合项创建按钮。 导入  drawable  资源。 使用  Image  可组合项显示图片。 使用可组合项构建交互式界面。 使用  remember  可组合项将组合中的对象存储到内

    2024年02月20日
    浏览(40)
  • Android隐藏导航栏和状态栏的方法

    一。去除状态栏 以下是Android去除状态栏的代码示例: 1. 在Activity的onCreate()方法中添加以下代码: getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); 2. 在AndroidManifest.xml文件中的Activity节点中添加以下属性: android:theme=\\\"@android:style/Theme.NoTitleB

    2024年02月16日
    浏览(24)
  • Android studio中使用ViewPager和BottomNavigationView实现底部导航栏和碎片的同步切换

    通过几次的踩雷和摸索,完成了以上的操作,本教程写的详细全面,包教包会,对新手有好,看了不会的联系我,我倒立洗头给你看。 所需控件: fragment 作为Android中最常用的控件,它有自己的声明周期,可以粗略地等比为能够分屏的activity,但是和activity有区别,fragment有自

    2024年02月08日
    浏览(36)
  • Android笔记(十):结合Navigation组件实现Compose界面的导航

    在Android笔记(七)搭建Android JetPack Compose组件中Scaffold脚手架 一文中通过定义一个导航的函数来实现不同界面的切换。如果没有传递任何参数,这样的导航处理也是可以接受的,处理方式也非常简单。但是,如果考虑到不同Compose界面的切换且传递参数,或者有更复杂地处理情

    2024年01月22日
    浏览(42)
  • Android Jetpack Compose 别裁

    一、简介 二、compose优缺点 三、compose好学吗 四、Android Jetpack Compose 跟 fluter 哪个更好 五、Android Jetpack Compose 跟 fluter 技能学习选择 之所以叫Android Jetpack Compose别裁是希望能取舍网上的对compose的资料,推出别出心裁的文章,文章结束都会有一个案例,通过实践学习,让学习的

    2024年02月03日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包