jetpack compose实战——TopAppBar的使用和封装

前言

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容