Jetpack Compose Scaffold,TopAppBar,BottomAppBar,SnackBar等使用讲解

2,797 阅读6分钟

Compose 提供了大量基于 Material Design 的可组合项以及 androidx.compose.material:material 依赖项。Material 组件大量使用槽位 API,这是 Compose 引入的一种模式,它在可组合项之上带来一层自定义设置。而Scaffold是槽位的典型,这篇文章会介绍TopAppBar,BottomAppBar,SnackBar,以及Scaffold的使用。

一:TopAppBar

先来看看TopBar的代码

@Composable
fun TopAppBar(
    modifier: Modifier = Modifier,
    backgroundColor: Color = MaterialTheme.colors.primarySurface,
    contentColor: Color = contentColorFor(backgroundColor),
    elevation: Dp = AppBarDefaults.TopAppBarElevation,
    contentPadding: PaddingValues = AppBarDefaults.ContentPadding,
    content: @Composable RowScope.() -> Unit
){
    ...
}
  • modifier修饰符 Modifier用法详解
  • backgroundColor 背景颜色
  • contentColor 内容的颜色
  • elevation 阴影
  • contentPadding 内容边距 可通过PaddingValue设置,也可使用默认AppBarDefaults.ContentPadding
  • content 内容控件 比如我们下面举例一个,左边是menu的Icon,右边是标题
@Preview
@Composable
fun topBarView(){
    TopAppBar(
        modifier = Modifier
            .fillMaxWidth()
            .height(48.dp),
        backgroundColor = MaterialTheme.colors.primarySurface,
        elevation = 4.dp,
        contentPadding = AppBarDefaults.ContentPadding
    ) {
        Row(modifier = Modifier.fillMaxWidth(),verticalAlignment = Alignment.CenterVertically) {
            Icon(
                imageVector = Icons.Filled.Menu,
                contentDescription = "menu按钮",
                tint = Color.White,
                modifier = Modifier
                    .clickable {
                        //点击了按钮
                    }
                    .padding(start = 12.dp, end = 12.dp)
                    .fillMaxHeight()
            )
            Text(text = "页面标题",fontSize = 17.sp,color = Color.White)
        }
    }
}

二:BottomAppBar

先来看看BottomBar的代码

@Composable
fun BottomAppBar(
    modifier: Modifier = Modifier,
    backgroundColor: Color = MaterialTheme.colors.primarySurface,
    contentColor: Color = contentColorFor(backgroundColor),
    cutoutShape: Shape? = null,
    elevation: Dp = AppBarDefaults.BottomAppBarElevation,
    contentPadding: PaddingValues = AppBarDefaults.ContentPadding,
    content: @Composable RowScope.() -> Unit
){
    ...
}
  • modifier修饰符 Modifier用法详解
  • backgroundColor 背景颜色
  • contentColor 内容的颜色
  • cutoutShape 形状
  • elevation 阴影
  • contentPadding 内容边距 可通过PaddingValue设置,也可使用默认AppBarDefaults.ContentPadding
  • content 内容控件

比如我们举例,有5个tab

@Preview
@Composable
fun bottomBarView(){
    val posState = remember {
        mutableStateOf(0)
    }
    BottomAppBar(
        modifier = Modifier
            .fillMaxWidth()
            .height(50.dp),
        backgroundColor = Color.White,
        contentColor = Color.White,
        elevation = 4.dp,
        cutoutShape = RoundedCornerShape(4),
        contentPadding = AppBarDefaults.ContentPadding
    ) {
        Row() {
            // 比如说BottomBar是5个tab
            val modifier = Modifier
                .fillMaxHeight()
                .weight(1f, true)
            tabItemView(0,modifier,posState)
            tabItemView(1,modifier,posState)
            tabItemView(2,modifier,posState)
            tabItemView(3,modifier,posState)
            tabItemView(4,modifier,posState)
        }
    }
}

@Composable
fun tabItemView(pos:Int,modifier: Modifier,posState:MutableState<Int>){
    Column(
        modifier=modifier.clickable {
            posState.value = pos
        }
        ,horizontalAlignment = Alignment.CenterHorizontally
        ,verticalArrangement = Arrangement.Center
    ) {
        val imageVector = when(pos){
            0->Icons.Filled.Home
            1->Icons.Filled.Message
            2->Icons.Filled.Domain
            3->Icons.Filled.Favorite
            else->Icons.Filled.Person
        }

        val message = when(pos){
            0->"首页"
            1->"消息"
            2->"建筑"
            3->"喜欢"
            else->"我的"
        }

        Icon(imageVector = imageVector, contentDescription = message,tint = if(posState.value == pos) Color.Blue else Color.Black)
        Text(text = message,fontSize = 10.sp,color = if(posState.value == pos) Color.Blue else Color.Black)
    }
}

三:SnackBar

我们来看看SnackBar的代码

@Composable
fun Snackbar(
    modifier: Modifier = Modifier,
    action: @Composable (() -> Unit)? = null,
    actionOnNewLine: Boolean = false,
    shape: Shape = MaterialTheme.shapes.small,
    backgroundColor: Color = SnackbarDefaults.backgroundColor,
    contentColor: Color = MaterialTheme.colors.surface,
    elevation: Dp = 6.dp,
    content: @Composable () -> Unit
){...}
  • modifier 修饰符
  • action 行为的内容是什么控件
  • actionOnNewLine 表示action内容是否在新的一行,为true为另为一行,否则会和content的内容叠在一起
  • shape形状 默认是MaterialTheme.shapes.small 也就是RoundedCornerShape(4.dp)
  • backgroundColor 设置背景颜色
  • contentColor 设置内容的颜色
  • elevation设置阴影
  • content 内容是什么控件
@Preview()
@Composable
fun snackbarTest(){
    Snackbar(
        modifier = Modifier
            .fillMaxWidth()
            .padding(10.dp),
        action = {
            TextButton(
                onClick = {
                    Log.e("ccm","点击了隐藏按钮")
                }) {
                Text(text = "隐藏",color = Color.White)
            }
        },
        actionOnNewLine = true,
        shape = MaterialTheme.shapes.small,
        backgroundColor = SnackbarDefaults.backgroundColor,
        contentColor =MaterialTheme.colors.surface,
        elevation = 6.dp
    ) {
        Text(text = "提示的信息",color=Color.White,fontSize = 12.sp)
    }
}

四:Scaffold

先来看看Scaffold的代码:我们依旧通过一个属性讲解举例,来掌握Scaffold组件

@Composable
fun Scaffold(
    modifier: Modifier = Modifier,
    scaffoldState: ScaffoldState = rememberScaffoldState(),
    topBar: @Composable () -> Unit = {},
    bottomBar: @Composable () -> Unit = {},
    snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
    floatingActionButton: @Composable () -> Unit = {},
    floatingActionButtonPosition: FabPosition = FabPosition.End,
    isFloatingActionButtonDocked: Boolean = false,
    drawerContent: @Composable (ColumnScope.() -> Unit)? = null,
    drawerGesturesEnabled: Boolean = true,
    drawerShape: Shape = MaterialTheme.shapes.large,
    drawerElevation: Dp = DrawerDefaults.Elevation,
    drawerBackgroundColor: Color = MaterialTheme.colors.surface,
    drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
    drawerScrimColor: Color = DrawerDefaults.scrimColor,
    backgroundColor: Color = MaterialTheme.colors.background,
    contentColor: Color = contentColorFor(backgroundColor),
    content: @Composable (PaddingValues) -> Unit
){
   ...
}
  • modifier修饰符 Modifier用法详解
  • topBar 顶部的组件。一般是标题栏,比如使用上面讲的TopAppBar。
  • bottomBar 底部的组件。比如使用上面讲的BottomAppBar。比如可以是5个Tab
  • snackbarHost 其实是设置了一个Snackbar控件。我们来看看具体实现。
    @Composable
    fun SnackbarHost(
      hostState: SnackbarHostState,
      modifier: Modifier = Modifier,
      snackbar: @Composable (SnackbarData) -> Unit = { Snackbar(it) }
    )
    
    • hostState这个是SnackbarHost状态
    • modifier 修饰符
    • snackbar Snackbar控件。所以主要还是看Snackbar控件 在具体看看Snackbar(it)点进去的代码
    @Composable
    fun Snackbar(
      snackbarData: SnackbarData,
      modifier: Modifier = Modifier,
      actionOnNewLine: Boolean = false,
      shape: Shape = MaterialTheme.shapes.small,
      backgroundColor: Color = SnackbarDefaults.backgroundColor,
      contentColor: Color = MaterialTheme.colors.surface,
      actionColor: Color = SnackbarDefaults.primaryActionColor,
      elevation: Dp = 6.dp
    ) {
      val actionLabel = snackbarData.actionLabel
      val actionComposable: (@Composable () -> Unit)? = if (actionLabel != null) {
          @Composable {
              TextButton(
                  colors = ButtonDefaults.textButtonColors(contentColor = actionColor),
                  onClick = { snackbarData.performAction() },
                  content = { Text(actionLabel) }
              )
          }
      } else {
          null
      }
      Snackbar(
          modifier = modifier.padding(12.dp),
          content = { Text(snackbarData.message) },
          action = actionComposable,
          actionOnNewLine = actionOnNewLine,
          shape = shape,
          backgroundColor = backgroundColor,
          contentColor = contentColor,
          elevation = elevation
      )
    }
    
    
    // 具体看看SnackbarData是什么
     interface SnackbarData {
      val message: String
      val actionLabel: String?
      val duration: SnackbarDuration
      fun performAction()
      fun dismiss()
    }
    
    所以其实snackbarHost这个属性就是设置了一个Snackbar控件。只不过把Snackbar的content属性是一个Text文本控件,并且该文本控件的内容是snackbarData的message属性,Snackbar的action是一个TextButton,并且TextButton的行为刚好是执行了snackbarData的performAction,而且该TextButton显示的文本刚好是snackbarData.actionLable属性。再讲一个SnackbarDuration
    • duration是设置显示的时长。有三种SnackbarDuration.Long(显示10000L后自动消失),SnackbarDuration.Short(显示4000L后自动消失),SnackbarDuration.Indefinite(显示后不消失,除非点了action按钮隐藏)
  • floatingActionButton 是浮动的按钮
  • floatingActionButtonPosition 浮动按钮的显示位置。FabPosition取值可以是FabPosition.End 底部居右,一个是FabPosition.Center居中
  • isFloatingActionButtonDocked 为true的时候,会有一半的高度遮盖在bottomBar上面
  • drawerContent 是左边弹出的drawer组件。类似以前的DrawerLayout
  • drawerGesturesEnabled 弹出的左边的drawer组件能否通过手指拖动的形式打开和关闭
  • drawerShape drawer组件的形状
  • drawerElevation drawer组件的阴影
  • drawerBackgroundColor drawer组件的背景颜色
  • drawerContentColor drawer组件的内容的颜色
  • drawerScrimColor drawer组件打开的时候,右边余留的空间的颜色值
  • backgroundColor scaffold的背景颜色
  • contentColor scaffold的内容的颜色
  • content scaffold的包含的内容
  • scaffoldState 设置Scaffold的状态,主要是设置drawerState和snackbarHostState的状态。scaffoldState是一个ScaffoldState,我们具体来看看ScaffoldState的代码
    @Stable
    class ScaffoldState(
      val drawerState: DrawerState,
      val snackbarHostState: SnackbarHostState
    )
    
    • drawerState 设置drawer的控件的状态,比如open,close
    • snackbarHostState 这个是设置snackbarHost的状态,比如显示snackbarHost,隐藏snackbarHost 比如我们举例,点击了floatingActionButton按钮显示出snackbarHost控件,点击了topBar的menuIcon。显示出来drawer控件。
@Preview
@Composable
fun scaffoldTest(){
    val scaffoldState = rememberScaffoldState()
    val scope = rememberCoroutineScope()
    val posState = remember {
        mutableStateOf(0)
    }
    Scaffold(
        scaffoldState = scaffoldState,
        topBar = {
            topBarView(scaffoldState)
        },
        bottomBar = {
            bottomBarView(posState)
        },
        // 这个是SnackbarHost组件
//        snackbarHost,
        floatingActionButton = {
            FloatingActionButton(
                onClick = {
                    scope.launch {
                        scaffoldState.snackbarHostState.showSnackbar(
                            "点击了FloatingActionButton",
                            "隐藏",
                            duration = SnackbarDuration.Short
                        )
                    }
                }
            ) {
                Text(text = "Open SnackbarHost",fontSize = 10.sp,color=Color.White,modifier = Modifier.padding(start=10.dp,end = 10.dp))
            }
        },
        floatingActionButtonPosition = FabPosition.End,
        // 为true的时候,会有一半的高度遮盖在bottomBar上面
        isFloatingActionButtonDocked = false,
        drawerContent = {
            val modifier = Modifier
                .fillMaxWidth()
                .padding(start = 20.dp, top = 15.dp, bottom = 15.dp)
            Text(text = "drawer title",fontSize = 14.sp,modifier = modifier,textAlign = TextAlign.Left)
            Text(text = "drawer 第一个内容",fontSize = 14.sp,modifier=modifier,textAlign = TextAlign.Left)
            Text(text = "drawer 第二个内容",fontSize = 14.sp,modifier=modifier,textAlign = TextAlign.Left)
            Text(text = "drawer 第三个内容",fontSize = 14.sp,modifier=modifier,textAlign = TextAlign.Left)
        },
        // 表示能够通过手指拖动的形式打开和关闭drawer控件
        drawerGesturesEnabled = true,
        drawerShape = RoundedCornerShape(4.dp),
        drawerElevation = 20.dp,
        drawerBackgroundColor = MaterialTheme.colors.primarySurface,
        drawerContentColor = Color.White,
        // drawer控件打开的时候,右边剩余的部分的颜色
        drawerScrimColor = DrawerDefaults.scrimColor,
        backgroundColor = Color.White
        // contentColor 内容的颜色
    ) {
        Column(modifier = Modifier.fillMaxSize(),horizontalAlignment = Alignment.CenterHorizontally,verticalArrangement = Arrangement.Center) {
            val text = when(posState.value){
                0->"第一个Tab的内容"
                1->"第二个Tab的内容"
                2->"第三个Tab的内容"
                3->"第四个Tab的内容"
                else-> "第五个Tab的内容"
            }
            Text(text = text)
        }
    }
}

// TopBar
@Composable
fun topBarView(scaffoldState:ScaffoldState){
    val scope = rememberCoroutineScope()
    TopAppBar(
        modifier = Modifier
            .fillMaxWidth()
            .height(48.dp),
        backgroundColor = MaterialTheme.colors.primarySurface,
        elevation = 4.dp,
        contentPadding = AppBarDefaults.ContentPadding
    ) {
        Row(modifier = Modifier.fillMaxWidth(),verticalAlignment = Alignment.CenterVertically) {
            Icon(
                imageVector = Icons.Filled.Menu,
                contentDescription = "返回按钮",
                tint = Color.White,
                modifier = Modifier
                    .clickable {
                        // 打开drawer
                        scope.launch {
                            scaffoldState.drawerState.open()
                        }
                    }
                    .padding(start = 12.dp, end = 12.dp)
                    .fillMaxHeight()
            )
            Text(text = "页面标题",fontSize = 17.sp,color = Color.White)
        }
    }
}

// bottomBar
@Composable
fun bottomBarView(posState:MutableState<Int>){

    BottomAppBar(
        modifier = Modifier
            .fillMaxWidth()
            .height(50.dp),
        backgroundColor = Color.White,
        contentColor = Color.White,
        elevation = 4.dp,
        cutoutShape = RoundedCornerShape(4),
        contentPadding = AppBarDefaults.ContentPadding
    ) {
        Row() {
            // 比如说BottomBar是5个tab
            val modifier = Modifier
                .fillMaxHeight()
                .weight(1f, true)
            tabItemView(0,modifier,posState)
            tabItemView(1,modifier,posState)
            tabItemView(2,modifier,posState)
            tabItemView(3,modifier,posState)
            tabItemView(4,modifier,posState)
        }
    }
}

@Composable
fun tabItemView(pos:Int,modifier: Modifier,posState:MutableState<Int>){
    Column(
        modifier=modifier.clickable {
            posState.value = pos
        }
        ,horizontalAlignment = Alignment.CenterHorizontally
        ,verticalArrangement = Arrangement.Center
    ) {
        val imageVector = when(pos){
            0->Icons.Filled.Home
            1->Icons.Filled.Message
            2->Icons.Filled.Domain
            3->Icons.Filled.Favorite
            else->Icons.Filled.Person
        }

        val message = when(pos){
            0->"首页"
            1->"消息"
            2->"建筑"
            3->"喜欢"
            else->"我的"
        }

        Icon(imageVector = imageVector, contentDescription = message,tint = if(posState.value == pos) Color.Blue else Color.Black)
        Text(text = message,fontSize = 10.sp,color = if(posState.value == pos) Color.Blue else Color.Black)
    }
}