使用条件Modifier构建 Compose 应用

775 阅读4分钟

本短文将讨论如何使用糖法语有条件地添加Modifier. 要做到这一点, 自定义扩展是唯一的方法. 首先, 我将演示在没有扩展的情况下可以做些什么, 然后探讨拥有这些扩展可以如何简化你的生活.

目录

  1. 没有扩展
  2. 使用扩展
  3. 扩展源代码
  4. 奖奖
  5. 最后的想法

1. 无扩展

我将以 alpha Modifier为例. 这种方法可用于所有Modifier, 尽管有些Modifier可能不容易适应 if-else 结构.

Box(
  modifier = Modifier
    .size(60.dp)
    .background(Color.Blue)
    .alpha(
      if (my_condition) my_true_value
      else my_false_value
    )
)

因此, 我们需要使用 if-else 结构. 虽然这种方法运行良好, 但如果我们偶尔需要包含 alpha Modifier, 有时又不需要, 该怎么办呢?有两种方法:

Box(
  modifier = Modifier
    .size(60.dp)
    .background(Color.Blue)
    .alpha(
      if (my_condition) my_true_value
      else 1.0f // we have to add the alpha modifier at 1.0f even if we don't need it
    )
)

我们的真正目的是:

val modifier = Modifier
    .size(60.dp)
    .background(Color.Blue)

if (my_condition) modifier.alpha(my_true_value)

Box(modifier = modifier)

使用这种方法, 我们只在必要时添加alpha Modifier. 不过, 我并不喜欢这种方法, 因为它需要在方框上方创建一个变量.

如前所述, 我在示例中使用了 alpha Modifier, 因为它很容易适应 if-else 结构. 不过, 在讨论其他Modifier时, 我们还是要力求清晰.

Box(
    modifier = Modifier
        .size(60.dp)
        .background(Color.Blue, 
            if (my_condition) CircleShape
            else RectangleShape // You must specify the Rectangle shape explicitly.
        )
)

Box(
    modifier = Modifier
        .size(60.dp)
        .background(Color.Blue)
        .padding(
            if (my_condition) 12.dp
            else 0.dp // You must specify 0.dp explicitly.
        )
)

它迫使我们在可以省略Modifier的情况下使用一些默认值. 但是, 如前所述, 另一种方法是不可接受的.

2. 使用扩展

让我们重温一下上面提到的同样的情况, 但其方式要符合我们喜欢的写作风格.

Box(
  modifier = Modifier
    .size(60.dp)
    .background(Color.Blue)
    .thenOnTrue(my_condition) {
      alpha(my_true_value) // Here alpha is added only when needed
    }
)

Box(
  modifier = Modifier
    .size(60.dp)
    .background(Color.Blue)
    .thenOnTrue(my_condition) {
        clip(CircleShape) // Here the shape is applied through clip only when needed
    }
)

Box(
  modifier = Modifier
    .size(60.dp)
    .background(Color.Blue)
    .thenOnTrue(my_condition) {
      padding(12.dp) // Here padding is added only when needed
    }
)

我非常喜欢这种写法. 我们也可以将其应用于假条件, 甚至在真条件块中包含多个Modifier.

// Our extension must manage also false condition alone.
Box(
  modifier = Modifier
    .size(60.dp)
    .background(Color.Blue)
    .thenOnFalse(my_condition) {
      alpha(my_false_value) // Here alpha is added only when needed
      .padding(start = 12.dp) // Yes we can chain modifier inside the block condition!
    }
)

// Our extension must be able to manage true and false condition in one shot
Box(
  modifier = Modifier
    .size(60.dp)
    .background(Color.Blue)
    .then(
        my_condition,
        onTrue = {
            padding(vertical = 12.dp)
            clip(CircleShape)
        },
        onFalse = {
            alpha(0.5)
            .shadow(2.dp)
        }
    )
)

// What about null ?  I did it too, I'll show you the source code of 
// theses extensions in the next section

3. 扩展源代码

因此, 初始部分由我们的三个函数签名组成:thenOnTrue, thenOnFalsethen.

@Composable
fun Modifier.thenOnTrue(
    condition: Boolean,
    block: @Composable Modifier.() -> Modifier
) = thenInternal(condition, onTrue = block)

@Composable
fun Modifier.thenOnFalse(
    condition: Boolean,
    block: @Composable Modifier.() -> Modifier
) = thenInternal(condition, onFalse = block)

@Composable
fun Modifier.then(
    condition: Boolean,
    onTrue: @Composable Modifier.() -> Modifier,
    onFalse: @Composable Modifier.() -> Modifier
) = thenInternal(condition, onTrue, onFalse)

由于一切都可因式分解, 我只创建了一个名为thenInternal的函数扩展, 并在三个签名调用中使用了它. 以下是thenInternal的代码:

@Composable
private fun Modifier.thenInternal(
    condition: Boolean,
    onTrue: (@Composable Modifier.() -> Modifier)? = null,
    onFalse: (@Composable Modifier.() -> Modifier)? = null
) = (if (condition) {
    onTrue?.let { then(Modifier.it()) }
} else {
    onFalse?.let { then(Modifier.it()) }
}) ?: this

这就是它的全部内容. 你可以将它与任何Modifier一起使用, 并通过连锁多个条件Modifier来实现你想要的任何效果!

所有Modifier的作用域都使用了* @Composable, 因为我希望能够在 onFalseonTrue 代码块中使用组成的作用域变量.

Box(
  modifier = Modifier
    .background(Color.Blue)
    .thenOnTrue(my_condition) {
        size(MyLocalTheme.smallBox) 
        // You can also use composable scoped variables 
        // like LocalContext.current
        // or even "val alpha by  animateFloatAsState(if (enable) 0.5f else 1.0f)"
    }
)

在Compose的早期时日

以前, 不建议在Modifier扩展中使用@Composable. 建议使用composed块来代替. 因此, 第一个版本是这样的:

// *** Old way *** Not recommended anymore

// Composable annotation here was not recommended
private fun Modifier.thenInternal(
    condition: Boolean,
    onTrue: (@Composable Modifier.() -> Modifier)? = null,
    onFalse: (@Composable Modifier.() -> Modifier)? = null
) = (if (condition) {
    onTrue?.let { composed { then(Modifier.it()) } } // so we used composed block
} else {
    onFalse?.let { composed { then(Modifier.it()) } }
}) ?: this

4.奖励

作为奖励, 我们还可以使用完全相同的方法来处理空条件值.

@Composable
fun <T : Any> Modifier.thenOnNotNull(
    condition: T?,
    block: @Composable Modifier.(T) -> Modifier
) = thenInternal(condition, onNotNull = block)

@Composable
fun <T : Any> Modifier.thenOnNull(
    condition: T?,
    block: @Composable Modifier.() -> Modifier
) = thenInternal(condition, onNull = block)

@Composable
fun <T : Any> Modifier.then(
    condition: T?,
    onNotNull: @Composable Modifier.(T) -> Modifier,
    onNull: @Composable Modifier.() -> Modifier
) = thenInternal(condition, onNotNull, onNull)

private fun <T : Any> Modifier.thenInternal(
    condition: T?,
    onNotNull: (@Composable Modifier.(T) -> Modifier)? = null,
    onNull: (@Composable Modifier.() -> Modifier)? = null
) = (condition?.let {
    onNotNull?.let { then(Modifier.it(condition))}
} ?: run {
    onNull?.let { then(Modifier.it()) }
}) ?: this

充分利用以上函数:

Box(
  modifier = Modifier
    .size(60.dp)
    .background(Color.Blue)
    .thenOnNull(my_object_which_can_be_null) {
        alpha(0.8f) // Here alpha is added only when needed
        .otherModifier()
    }
)

Box(
  modifier = Modifier
    .size(60.dp)
    .background(Color.Blue)
    .thenOnNotNull(my_object_which_can_be_null) {
         padding(12.dp) // Here padding is added only when needed
        .otherModifier()
    }
)

Box(
  modifier = Modifier
    .size(60.dp)
    .background(Color.Blue)
    .then(
        my_object_which_can_be_null,
        onNotNull = {
            padding(vertical = 12.dp)
            clip(CircleShape)
        },
        onNull = {
            alpha(0.5)
            .shadow(2.dp)
        }
    )
)

5. 最后的想法

这一次, 我不会提供源代码库. 我认为这是不必要的, 因为所有代码都在这里展示, 而且仅由扩展组成.

我坚信, 像这样实用的扩展可以大大简化你的生活并提高可读性. 请随意尝试使用它们, 甚至分享你的经验!

如果你注意到本文的结构, 我首先确定了我想要的友好书写类型, 然后将其制作成扩展. 这种方法几乎适用于所有情况下的设计 -- 首先设想你想要什么, 然后执行它! 当然, 途中可能会有意想不到的发现, 尤其是在尝试新事物时. ^^