美文网首页Compose for Android
jetpack compose实战——TopAppBar的使用和

jetpack compose实战——TopAppBar的使用和

作者: Peakmain | 来源:发表于2022-05-04 18:14 被阅读0次

    前言

    TopAppBar的使用

    源码

    有两个源码,我们看其中一个

    @Composable
    fun TopAppBar(
        title: @Composable () -> Unit,
        modifier: Modifier = Modifier,
        navigationIcon: @Composable (() -> Unit)? = null,
        actions: @Composable RowScope.() -> Unit = {},
        backgroundColor: Color = MaterialTheme.colors.primarySurface,
        contentColor: Color = contentColorFor(backgroundColor),
        elevation: Dp = AppBarDefaults.TopAppBarElevation
    ) 
    
    • title:标题
    • navigationIcon:左边图标
    • actions:右边图标
    • backgroundColor:背景颜色
    • contentColor:内容区域的颜色,包括Icon,Text等
    • elevation:阴影
    image.png
           Scaffold(topBar = {
               TopAppBar(title = {
                   Text(text = "我是系统的TopAppBar")
               },navigationIcon = {
                   Icon(imageVector = Icons.Default.ArrowBack, contentDescription = null)
               },actions = {
                   Icon(imageVector = Icons.Default.Add, contentDescription = null)
               })
           }) 
    

    上面我们看的是参数源码,我们看下方法体的源码

    fun TopAppBar(
    ...
    ) {
        AppBar(
            backgroundColor,
            contentColor,
            elevation,
            AppBarDefaults.ContentPadding,
            RectangleShape,
            modifier
        ) {
            if (navigationIcon == null) {//👈🏻①
                Spacer(TitleInsetWithoutIcon)
            } else {
                //👈🏻②
                Row(TitleIconModifier, verticalAlignment = Alignment.CenterVertically) {
                    CompositionLocalProvider(
                        LocalContentAlpha provides ContentAlpha.high,
                        content = navigationIcon
                    )
                }
            }
    
            Row(
           //注释③
                Modifier.fillMaxHeight().weight(1f),
                verticalAlignment = Alignment.CenterVertically
            ) {
                ProvideTextStyle(value = MaterialTheme.typography.h6) {
                    CompositionLocalProvider(
                        LocalContentAlpha provides ContentAlpha.high,
                        content = title
                    )
                }
            }
          //注释④
            CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
                Row(
                    Modifier.fillMaxHeight(),
                    horizontalArrangement = Arrangement.End,
                    verticalAlignment = Alignment.CenterVertically,
                    content = actions
                )
            }
        }
    }
    
    • 我们看到AppBar有个默认padding值是AppBarDefaults.ContentPadding=4.dp,AppBar的默认高度是56dp


      image.png
    • 我们看注释①

    private val AppBarHorizontalPadding = 4.dp
    private val TitleInsetWithoutIcon = Modifier.width(16.dp - AppBarHorizontalPadding)
    

    我们可以看到,如果左边没有图标,那么它会设置一个12dp的空白区域

    tips:Spacer空白区域,通过 modifier 设置空白区域的大小

    • 注释②
      • 如果左边返回按钮不为空,那么就会执行我们创建的navigationIcon,只是默认设置成了垂直居中
      • CompositionLocalProvider大家可以先理解成只是用来传递数据的,后面会介绍到
      • TitleIconModifier源码
    private val AppBarHorizontalPadding = 4.dp
    private val TitleIconModifier = Modifier.fillMaxHeight()
        .width(72.dp - AppBarHorizontalPadding)
    

    设置naviationIcon的大小为68.dp

    • 注释③:我们可以看到它实际设置了weight=1,那也就是说,如果有左图标或者右图标,那么文字其实并不会居中,而是填满剩余部分,然后垂直居中

    • 注释④,默认是居右,然后有4dp的距离

    所以代码其实等价于

         Row(
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(56.dp)
                        .background(Color_149EE7)
                        .padding(4.dp),
                    horizontalArrangement = Arrangement.SpaceBetween
                ) {
                    Row(modifier = Modifier
                        .fillMaxHeight()
                        .width(68.dp)
                        .background(Color.Red),
                        verticalAlignment = Alignment.CenterVertically) {
                        CompositionLocalProvider(
                            LocalContentAlpha provides ContentAlpha.high,
                            content = {
                                Icon(imageVector = Icons.Default.ArrowBack,
                                    contentDescription = null,
                                    tint = Color.White)
                            }
                        )
                    }
                    Row(
                        Modifier
                            .fillMaxHeight()
                            .weight(1f),
                        verticalAlignment = Alignment.CenterVertically
                    ) {
                        ProvideTextStyle(value = MaterialTheme.typography.h6) {
                            CompositionLocalProvider(
                                LocalContentAlpha provides ContentAlpha.high,
                                content = {
                                    Text(text = "我是自定义的TopAppBar", color = Color.White)
                                }
                            )
                        }
                    }
                    CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
                        Row(
                            Modifier.fillMaxHeight(),
                            horizontalArrangement = Arrangement.End,
                            verticalAlignment = Alignment.CenterVertically,
                            content = {
                                Icon(
                                    imageVector = Icons.Default.Add,
                                    contentDescription = null,
                                    modifier = Modifier.background(Color.Blue),
                                    tint = Color.White
                                )
                            }
                        )
                    }
              }
    
    image.png

    存在的问题

    • 想要的效果:首页的文字居中,如下图


      image.png
    • 上面我们分析知道title实际设置了weight=1,当左边有图标的时候,文字设置居中实际是模块居中,而不是标题文本内容居中


      image.png

      如上图,"封装首页"其实才是我们想要的效果,"系统首页"是现有的TopAppBar的样式。那么想达到我们想要的效果应该怎么做呢?在封装前我们还需要了解一个控件ConstraintLayout

    ConstraintLayout的使用

    ConstraintLayout 可以让组件相对屏幕或其他同级组件进行布局,减少 Row、Column、Box 布局的互相嵌套。
    想要在项目中使用ConstraintLayout,必须在build.gradle 中增加依赖,我的放在library中

      api "androidx.constraintlayout:constraintlayout-compose:1.0.0"
    

    用法:constraintlayout使用有两种方式,我这里主要介绍一种方式,大家可以自行查询你第二种方式

    • 给 ConstraintsLayout 设置 ContraintSet
    • 通过 createRefs 或 createRefFor 创建引用,每一个组件需要有一个引用
    • 通过设置 layoutId 来关联组件
    • contraintSet中constrain的body块使用linkto等设置约束
    • parent 是默认的父组件的引用,可以直接使用
     val constraintSet = ConstraintSet {
         val ceshi1 = createRefFor("ceshi1")
         val ceshi2 = createRefFor("ceshi2")
         constrain(ceshi1) {
             start.linkTo(parent.start)
             end.linkTo(parent.end)
             top.linkTo(parent.top)
             bottom.linkTo(parent.bottom)
         }
         constrain(ceshi2) {
             top.linkTo(parent.top)
             bottom.linkTo(parent.bottom)
         }
     }
     ConstraintLayout(constraintSet,
         Modifier
             .height(56.dp)
             .background(Color.Red)
             .fillMaxWidth()) {
         Text(
             text = "测试11",
             modifier = Modifier.layoutId("ceshi1"),
             color = Color.White
         )
         Text(
             text = "测试12",
             modifier = Modifier.layoutId("ceshi2"),
             color = Color.White
         )
    }
    

    效果如图


    image.png

    TopAppBar的封装

    结合上面分析,我们的需要注意几点:
    1.TopAppBar的高度是56dp
    2.TopAppBar的两边的间距是4dp

    直接贴代码

    @Composable
    fun TopAppBarCenter(
        title: @Composable () -> Unit,
        modifier: Modifier = Modifier,
        navigationIcon: @Composable (() -> Unit)? = null,
        backgroundColor: Color = MaterialTheme.colors.primarySurface,
        actions: @Composable RowScope.() -> Unit = {},
        content: @Composable (PaddingValues) -> Unit
    ) {
       Scaffold(topBar = {
           val constraintSet = ConstraintSet {
               val titleRef = createRefFor("title")
               val navigationIconRef = createRefFor("navigationIcon")
               val actionsRef = createRefFor("actions")
               constrain(titleRef) {
                   start.linkTo(parent.start)
                   end.linkTo(parent.end)
                   top.linkTo(parent.top)
                   bottom.linkTo(parent.bottom)
               }
               constrain(navigationIconRef) {
                   top.linkTo(parent.top)
                   bottom.linkTo(parent.bottom)
               }
               constrain(actionsRef){
                   top.linkTo(parent.top)
                   bottom.linkTo(parent.bottom)
                   end.linkTo(parent.end)
               }
           }
           ConstraintLayout(constraintSet,
               modifier = Modifier
                   .fillMaxWidth()
                   .background(backgroundColor)
                   .height(TopAppBarHeight)
                   .then(modifier)) {
               Box(
                   Modifier
                       .layoutId("title")
                       .padding(horizontal = 4.dp)
               ) {
                   ProvideTextStyle(value = MaterialTheme.typography.h6) {
                       CompositionLocalProvider(
                           LocalContentAlpha provides ContentAlpha.high,
                           content = title
                       )
                   }
               }
               if (navigationIcon != null) {
                   Box(modifier = Modifier
                       .layoutId("navigationIcon")
                       .padding(start = 4.dp)) {
                       CompositionLocalProvider(
                           LocalContentAlpha provides ContentAlpha.high,
                           content = navigationIcon
                       )
                   }
               }
               Row(
                   Modifier.layoutId("actions").padding(end = 4.dp),
                   content = actions
               )
    
           }
       }) {
            content(it)
       }
    
    }
    

    使用就非常简单了

        TopAppBarCenter(title = {
            Text(text = "封装首页", color = Color.White)
        }, navigationIcon = {
            Icon(imageVector = Icons.Default.ArrowBack,
                contentDescription = null,
                tint = Color.White)
        }, actions = {
            Icon(imageVector = Icons.Default.Add, contentDescription = null, tint = Color.White)
        }) {
            //绘制内容区
            Text(text = "我是首页")
        }
    
    沉浸式状态栏
    • 首先开启沉浸式状态栏
    //MainActivity
    //设置沉浸式状态栏
    WindowCompat.setDecorFitsSystemWindows(window, false)
    
        def accompanist_version = "0.24.7-alpha"
        api "com.google.accompanist:accompanist-systemuicontroller:${accompanist_version}"
    
    • 使用
            val systemUiController = rememberSystemUiController()
            SideEffect {
                systemUiController.setSystemBarsColor(
                    color = Color.Transparent,
                    darkIcons = darkIcons
                )
            }
    
    • 2、获取状态栏的高度,原本TopAppStatus的高度+状态栏的高度
    //获取状态栏的高度
     with(LocalContext.current) {
         statusBarHeight =
             resources.getDimensionPixelSize(resources.getIdentifier("status_bar_height",
                 "dimen",
                 "android"))
     }
     with(LocalDensity.current) {
         statusBarHeightDp = statusBarHeight.toDp()
     }
    
    • 3、设置padding(top=状态栏的高度)
          ConstraintLayout(constraintSet, modifier = Modifier
                .fillMaxWidth()
                .background(backgroundColor)
                .height(topAppBarHeight + statusBarHeightDp)
                .padding(top = statusBarHeightDp) //设置padding(top=状态栏的高度)
                .then(modifier))
    

    效果如下


    image.png

    但是当我们把虚拟导航栏打开,我们再看看效果

    image.png
    我们我们发现下方的BottomNavigation被挡住了,怎么解决呢?这时候我们需要用到另一个Google的库, Insets for Jetpack Compose
    • 添加依赖
        api "com.google.accompanist:accompanist-insets:$accompanist_version"
    
    • 修改BottomNavigation,添加modifier
      BottomNavigation(backgroundColor = MaterialTheme.colors.surface
                    , modifier = Modifier.navigationBarsPadding()
    

    运行起来看下效果


    image.png

    总结

    我们首先对TopAppBar的源码进行分析,我们发现原有的TopAppBar并不能满足我们的需求,于是我们自己重写了一个TopAppBar,使用的技术其实也很简单,主要就是使用到了ConstraintLayout。关于沉浸式呢,我们用的都是Google自带的库,用起来也是非常简单。
    至此,我们的TopAppBar的封装已经结束了,下篇将介绍Banner图的使用和封装。
    最后贴下TopAppBar的源码地址TopAppBar.kt

    相关文章

      网友评论

        本文标题:jetpack compose实战——TopAppBar的使用和

        本文链接:https://www.haomeiwen.com/subject/kldwyrtx.html