Jetpack Compose Modifier用法详解

11,474 阅读7分钟

前面我们介绍了Jetpack Compose的环境的配置,以及Column , Row ,Box 的用法,这篇文章开始介绍Modifier。 Modifier 是Composable的修饰符,他是一个标准的Kotlin对象。

一:Modifie的作用

以前,我们在布局中去设置一个控件的大小,间距,点击事件,宽高,背景等属性值。而在Compose中我们是通过Modifie去设置,Modifie相当于一个控件的属性配置的工具类。修饰符大概有如下几种作用

  • 第一:可以去更新可组合项的大小,布局,行为和外观。
  • 第二:添加互动。例如点击,滚动,可拖拽,缩放等。
  • 处理用户输入
  • 添加信息,如无障碍标签

例如:

@Composable
fun ArtistCard(
    artist: Artist,
    onClick: () -> Unit
) {
    Column(
        modifier = Modifier
            .clickable(onClick = onClick)
            .padding(16.dp)
            .fillMaxWidth()
    ) {
        Row(verticalAlignment = Alignment.CenterVertically) { /*...*/ }
        Spacer(Modifier.size(20.dp))
        Card(elevation = 4.dp) { /*...*/  }
    }
}

上面的代码中,我们通过Modifier类调用的方式,去调用不同的修饰符函数。

  • clickable 使可组合项响应用户输入,并显示涟漪。
  • padding 在元素周围留出空间。
  • fillMaxWidth 使可组合项填充其父项为它提供的最大宽度。
  • size() 指定元素的首选宽度和高度

注意:修饰符的函数调用顺序非常重要。由于每个函数都会对上一个函数返回的 Modifier 进行更改,因此顺序会影响最终结果。比如上面是对Column整体控件都可以点击,但如果我们把,padding跟clickable的修饰符的顺序换一下。那么Column的点击范围就需要扣除padding的部分。

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        modifier = Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

二:常用的Modifier的函数方法介绍

Modifier涉及到的方法很多,我们可以从官网的地址去查看Modifier方法,下面我们会进可能的去涉及解释到常用方法的使用。

  • background 设置背景
    @Composable
    fun PaddedComposable() {
      Box(modifier = Modifier.size(300.dp,300.dp).background(Color.Blue)){}
    }
    
  • padding 设置边距(可以设置全部,也可以设置左start,上top,右end,底bottom分别设置,也可以设置horizontal横向,竖向vertical)
    @Composable
    fun PaddedComposable() {
      Text("Hello World", modifier = Modifier.padding(20.dp))
    }
    
    @Preview()
    @Composable
    fun paddingTest(){
      Column(modifier = Modifier.size(200.dp).background(Color.Gray).padding(20.dp)) {
          Box(modifier = Modifier.size(30.dp).background(Color.Red).padding(top = 4.dp)) {}
          Box(modifier = Modifier.size(30.dp).padding(start = 4.dp,top = 4.dp).background(Color.Green)) {}
          Box(modifier = Modifier.size(30.dp).padding(vertical = 4.dp).background(Color.Red)) {}
          Box(modifier = Modifier.size(30.dp).padding(horizontal = 4.dp).background(Color.Green)) {}
      }
    }
    
  • size 指定元素的宽高
     @Preview()
     @Composable
     fun modiferTest(){
      Box(modifier = Modifier.size(300.dp,300.dp){}
     }
    
  • requiredSize:请注意,如果指定的尺寸不符合来自布局父项的约束条件,则可能不会采用该尺寸。如果您希望可组合项的尺寸固定不变,而不考虑传入的约束条件,请使用 requiredSize 修饰符
     @Composable
     fun FixedSizeComposable() {
      Box(Modifier.size(90.dp, 150.dp).background(Color.Green)) {
          Box(Modifier.requiredSize(100.dp, 100.dp).background(Color.Red))
      }
     }
    
    比如外面的布局大小是90,150,而里面需要100dp的宽度超出了父项的约束,这时候希望子项有作用,那么可以使用requiredSize
  • fillMaxHeight 填充父项为它提供的最大的高度
  • fillMaxWidth 填充父项为它提供的最大的宽度
  • fillMaxSize 填充父项的宽高
  • width 宽度
  • height 高度
     @Preview()
     @Composable
     fun modiferTest(){
      Box(modifier = Modifier.size(300.dp,300.dp).background(Color.Blue)){
          Box(modifier = Modifier.fillMaxHeight().fillMaxWidth().background(Color.Green))
          Box(modifier = Modifier.fillMaxWidth().height(50.dp).background(Color.Red))
          Box(modifier = Modifier.width(50.dp).fillMaxHeight().background(Color.Black))
          Box(modifier = Modifier.fillMaxSize().background(Color.Gray))
      }
     }
    
  • wrapContentSize 根据子级元素的宽高来确定自身的宽度和高度,如果自身设置了最小宽高的话则会被忽略。当unbounded参数为true的时候,自身设置了最大宽度的话也会被忽略
  • wrapContentWidth 根据子级元素的宽度来确定自身的宽度,如果自身设置了最小宽度的话则会被忽略。当unbounded参数为true的时候,自身设置了最大宽度的话也会被忽略
  • wrapContentHeight 根据子级元素的高度来确定自身的高度,如果自身设置了最小高度的话则会被忽略。当unbounded参数为true的时候,自身设置了最大高度的话也会被忽略
    @Preview()
    @Composable
    fun modiferTest(){
      Column(modifier = Modifier.wrapContentSize().background(Color.Blue).padding(10.dp){
          Box(modifier = Modifier.size(30.dp, 30.dp).background(Color.Red)) {}
          Box(modifier = Modifier.padding(top = 4.dp).wrapContentWidth().height(60.dp).background(Color.Green).padding(60.dp))
          Box(modifier = Modifier.padding(top = 4.dp).width(60.dp).wrapContentHeight().background(Color.Gray).padding(30.dp)){}
      }
    }
    
  • preferredWidth 设置初始宽度
  • preferredHeight 设置初始高度
  • preferedSize 设置初始的宽高 preferedXXX 等是用来设置初始的size,这个值可能被其他约束覆盖。
    @Preview()
    @Composable
    fun preferredTest(){
      Column(modifier = Modifier
              .size(150.dp)
              .background(Color.Red)){
          Box(modifier = Modifier.preferredSize(40.dp).background(Color.Green))
          Box(modifier = Modifier.padding(top = 4.dp).preferredSize(40.dp,20.dp).background(Color.Green))
          Box(modifier = Modifier.padding(top = 4.dp).preferredWidth(40.dp).height(20.dp).background(Color.Green))
          Box(modifier = Modifier.padding(top = 4.dp).width(20.dp).preferredHeight(40.dp).background(Color.Green))
      }
    }
    
  • widthIn(最小宽度,最大宽度) 设置自身的最小,最大宽度
  • heightIn(最小高度,最大高度) 设置自身的最小,最大高度
  • sizeIn(最小宽度,最小高度,最大宽度,最大高度) 设置自身的最小,最大宽高
    @Preview()
    @Composable
    fun preferredTest(){
      Column(modifier = Modifier
              .size(300.dp)
              .background(Color.Red)){
          Text(text = "测试测试测试", modifier = Modifier.sizeIn(40.dp,40.dp,100.dp,100.dp))
          Text(text = "测试测试,测试测试,测试测试", modifier = Modifier.sizeIn(40.dp,40.dp,100.dp,100.dp))
          Text(text = "测试", modifier = Modifier.widthIn(20.dp,100.dp))
          Text(text = "测试,测试,测试", modifier = Modifier.widthIn(20.dp,100.dp))
          Text(text = "测试", modifier = Modifier.heightIn(20.dp,100.dp))
          Text(text = "测试,测试,测试,测试,测试,测试,测试,测试,测试,测试,测试", modifier = Modifier.heightIn(20.dp,100.dp))
      }
    }
    
  • preferredWidthIn(最小宽度,最大宽度) 设置初始最小和最大宽度
  • preferredHeightIn(最小高度,最大高度) 设置初始最小和最大高度
  • preferedSizeIn(最小宽度,最小高度,最大宽度,最大高度) 设置初始最小和最大宽高
    @Preview()
    @Composable
    fun preferredTest(){
      Column(modifier = Modifier
              .size(300.dp)
              .background(Color.Red)){
          Text(text = "测试测试", modifier = Modifier.preferredSizeIn(40.dp,40.dp,100.dp,100.dp))
          Text(text = "测试测试,测试测试,测试测试", modifier = Modifier.preferredSizeIn(40.dp,40.dp,100.dp,100.dp))
          Text(text = "测试", modifier = Modifier.preferredWidthIn(20.dp,100.dp))
          Text(text = "测试,测试,测试", modifier = Modifier.preferredWidthIn(20.dp,100.dp))
          Text(text = "测试", modifier = Modifier.preferredHeightIn(20.dp,100.dp))
          Text(text = "测试,测试,测试,测试,测试,测试,测试,测试,测试,测试,测试,测试", modifier = Modifier.preferredHeightIn(20.dp,100.dp))
      }
    }
    
  • matchParentSize 如果您希望将子布局的尺寸设置为与父 Box 相同,但不影响 Box 的尺寸,请使用 matchParentSize 修饰符。请注意,matchParentSize 仅在 Box 作用域内可用,这意味着它仅适用于 Box 可组合项的直接子项
    @Composable
    fun MatchParentSizeComposable() {
      Box {
          Spacer(Modifier.matchParentSize().background(Color.Green))
          Text("Hello World")
      }
    }
    
  • paddingFromBaseline(top,bottom) paddingFromBaseline如果top值,表示基准线距离父项的顶部是多少,bottom值是表示基准线距离父项的底部是多少
    @Preview()
    @Composable
    fun paddingFromBaseLineTest(){
      Box(Modifier.background(Color.Yellow)) {
          Text(text = "Hi there",Modifier.paddingFromBaseline(top = 40.dp,bottom = 20.dp))
      }
    }
    
  • offset(x,y) 设置偏移量. x是相对于x轴上的偏移,y是相对于y轴上的偏移。
  • absoluteOffset(x,y)设置偏移量
@Preview()
@Composable
fun offsetTest(){
  Column(Modifier.background(Color.Yellow).size(width = 150.dp, height = 70.dp)) {
      Text(text = "Hi here",Modifier.offset(x=10.dp,y = 20.dp))
      Text(text = "Hi here two",Modifier.absoluteOffset(x=10.dp,y = 20.dp))
  }
}

offset 修饰符根据布局方向水平应用。在从左到右的上下文中,正 offset 会将元素向右移,而在从右到左的上下文中,它会将元素向左移,如果您需要设置偏移量,而不考虑布局方向,请参阅 absoluteOffset 修饰符,其中,正偏移值始终将元素向右移

  • align 设置子元素在垂直方向如何对齐。(只能在Row中使用)
    @Preview()
    @Composable
    fun alignTest() {
      Row(Modifier.width(210.dp).height(100.dp)) {
          Box(Modifier.align(alignment = Alignment.CenterVertically).width(20.dp).height(50.dp).background(Color.Blue))
      }
    }
    
  • weight 按占比去设置(只能在Row和Column中使用),类似于LinearLayout的layout_weight 比如内部两个 Box 可组合项的 Row 为例。第一个框的 weight 是第二个框的两倍,由于 Row 的宽度为 210.dp,因此第一个 Box 的宽度为 140.dp,第二个的宽度为 70.dp
    @Preview()
    @Composable
    fun FlexibleComposable() {
      Row(Modifier.width(210.dp)) {
          Box(Modifier.weight(2f).height(50.dp).background(Color.Blue))
          Box(Modifier.weight(1f).height(50.dp).background(Color.Red))
      }
    }
    
  • aspectRatio 按照宽高比例进行设置,比如宽设置了80dp,ratio是2f,那么高自动会为40
@Preview()
@Composable
fun modifierAspectRadioTest(){
  Box(modifier = Modifier.width(80.dp).aspectRatio(ratio = 2f,         matchHeightConstraintsFirst = false).background(Color.Yellow))
}
  • border 设置边框。比如在column里面设置个圆角的矩形,border的第一个参数是边框的宽度,第二个是颜色,第三个是shape。
@Preview()
@Composable
fun borderTest(){
  Column(modifier = Modifier.size(200.dp),horizontalAlignment= Alignment.CenterHorizontally,verticalArrangement = Arrangement.Center) {
      Box(modifier = Modifier.size(30.dp).border(width = 1.dp,color = Color.Blue,shape = RoundedCornerShape(4.dp)).background(Color.Yellow)) {}
  }
}
  • clickable 设置事件。可以设置点击,长按,双击等事件。举个简单的例子。Column点击的时候提示toast
@Composable
fun tabItem(){
  val context = ContextAmbient.current
  Column(modifier = Modifier.clickable(
          enabled = true,
          onClickLabel = "点击的lable",
          onClick = {
              Toast.makeText(context, "点击了", Toast.LENGTH_SHORT).show()
          },
          onLongClickLabel = "长按的lable",
          onLongClick = {
              Toast.makeText(context, "长按了", Toast.LENGTH_SHORT).show()
          },
          onDoubleClick = {
              Toast.makeText(context, "双击了", Toast.LENGTH_SHORT).show()
          }
  )){
    Icon(vectorResource(id = R.drawable.ic_launcher_background))
    Text(text = "哈哈哈")
  }
}
  • alpha 设置不透明度,范围是从0-1。
  • rotate 设置视频围绕其中心点旋转的角度
  • scale 设置视图的缩放比例
  • clip(shape) 设置裁剪。根据传入的shape,可以裁剪成对应的图行。比如头像裁剪成圆角的例子
  • shadow 绘制阴影效果,第一个参数是阴影大佬,第二个是shape
 @Preview()
 @Composable
 fun clipTest(){
   val image = imageResource(id = R.drawable.icon_head)
   val imageModifier = Modifier
           .size(40.dp)
           .alpha(0.5f) //设置透明度
           .scale(0.5f) //设置缩放
           .rotate(90f) //设置旋转角度
           .clip(RoundedCornerShape(8.dp))
   Column{
       Image(bitmap = image, modifier = imageModifier, contentScale = ContentScale.Crop)
       Box(modifier = Modifier.size(50.dp).shadow(elevation=2.dp,shape = CircleShape).clip(CircleShape).background(Color.Yellow))
   }
 }
  • horizontalScroll 设置水平滚动
    // 首先我们定义一个Student类
    class Student(var name:String=""){}
    
    @Composable
    fun studentRowItem(student:Student){
      Text(text = student.name,modifier = Modifier
          .fillMaxHeight()
          .width(60.dp),fontSize = 14.sp,textAlign = TextAlign.Center)
    }
    
    @Preview
    @Composable
    fun rowScrollTest(){
      val list = ArrayList<Student>()
      for(i in 0..20){
          list.add(Student(i.toString()))
      }
      Row(modifier = Modifier
          .fillMaxWidth()
          .height(100.dp)
          .horizontalScroll(rememberScrollState())
      ) {
          list.forEach {
              studentRowItem(student = it)
          }
      }
    }
    
  • verticalScroll 设置竖直滚动
    class Student(var name:String=""){}
    
    @Preview
    @Composable
    fun columnScrollTest(){
      val list = ArrayList<Student>()
      for(i in 0..20){
          list.add(Student(i.toString()))
      }
      studentList(students = list)
    }
    
    @Composable
    fun studentList(students:List<Student>){
      Column(modifier = Modifier
          .fillMaxWidth()
          .height(100.dp)
          .verticalScroll(rememberScrollState())
      ) {
          students.forEach {
              studentItem(student = it)
          }
      }
    }
    
    @Composable
    fun studentItem(student:Student){
      Text(text = student.name,modifier = Modifier.fillMaxWidth().height(30.dp),fontSize = 14.sp,textAlign = TextAlign.Center)
    }