Home > Back-end >  Create custom Modifier element in Compose
Create custom Modifier element in Compose

Time:12-22

According to Compose's Documentation, We can add custom Modifier classes like the following:

class SpacingModifier(val space: Dp) : Modifier.Element
fun Modifier.innerSpacing(space: Dp) = this.then(SpacingModifier(space))

@Composable
fun MyListOfItems(
  list: List<Model>,
  modifier: Modifier = Modifier
) {
  LazyColumn(modifier = modifier) {
    items(list) { model ->
      ViewHolder(model)
      Spacer(modifier = Modifier.height(/*I need the innerSpacing value here*/))
    }
  }
}

@Preview(showBackground = true)
@Composable
fun PreviewList() {
  MyListOfItems(
    list = listOf(
      Model(text = "Hello 1", checked = true),
      Model(text = "Hello 2", checked = false),
      Model(text = "Hello 3", checked = true),
      Model(text = "Hello 4", checked = true),
      Model(text = "Hello 5", checked = false)
    ),
    modifier = Modifier
      .background(Color.Red)
      .innerSpacing(16.dp) // <- My extension function here
  )
}

If I have the ability to declare extension functions for the Modifier, how do I access it inside a composable in the example above?

CodePudding user response:

Modifier is an ordered, immutable collection of modifier elements that decorate or add behavior to Compose UI elements. For example, backgrounds, padding and click event listeners decorate or add behavior to rows, text or buttons.

Modifiers are not to be interpreted directly by your Composable functions but Layout which is root for most composable functions such as Row, Column, Box, or SubcomposeLayout which is parent for LazyColumn, LazyRow, or BoxWithConstraints.

@Composable inline fun Layout(
    content: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    measurePolicy: MeasurePolicy
) {
    val density = LocalDensity.current
    val layoutDirection = LocalLayoutDirection.current
    val viewConfiguration = LocalViewConfiguration.current
    ReusableComposeNode<ComposeUiNode, Applier<Any>>(
        factory = ComposeUiNode.Constructor,
        update = {
            set(measurePolicy, ComposeUiNode.SetMeasurePolicy)
            set(density, ComposeUiNode.SetDensity)
            set(layoutDirection, ComposeUiNode.SetLayoutDirection)
            set(viewConfiguration, ComposeUiNode.SetViewConfiguration)
        },
        skippableUpdate = materializerOf(modifier),
        content = content
    )
}

@Composable
fun SubcomposeLayout(
    modifier: Modifier = Modifier,
    measurePolicy: SubcomposeMeasureScope.(Constraints) -> MeasureResult
) {
    SubcomposeLayout(
        state = remember { SubcomposeLayoutState() },
        modifier = modifier,
        measurePolicy = measurePolicy
    )
}

You can chain your Modifiers such as

private fun Modifier.getBadgeModifier(
    badgeState: BadgeState,
    shape: Shape
) = this
    .materialShadow(badgeState = badgeState)
    .then(
        badgeState.borderStroke?.let { borderStroke ->
            this.border(borderStroke, shape = shape)
        } ?: this
    )
    .background(
        badgeState.backgroundColor,
        shape = shape
    )

Modifier.materialShadow is also a Modifier that is chained to draw shadow based on a class called badgeState which wraps shapes, shadow and more.

class BadgeState(
    var maxNumber: Int = 99,
    var circleShapeThreshold: Int = 1,
    @IntRange(from = 0, to = 99) var roundedRadiusPercent: Int = 50,
    backgroundColor: Color,
    var horizontalPadding: Dp = 4.dp,
    var verticalPadding: Dp = 0.dp,
    textColor: Color,
    var fontSize: TextUnit,
    var fontWeight: FontWeight? = null,
    var fontFamily: FontFamily? = null,
    var fontStyle: FontStyle? = null,
    var textDecoration: TextDecoration? = null,
    var shadow: MaterialShadow? = null,
    var borderStroke: BorderStroke? = null,
    showBadgeThreshold: Int = Int.MIN_VALUE,
)

Also when you need to add multiple params to a Composable function or access these parameters you can do it as Text Composable uses TextStyle

   @Composable
    fun Text(
        text: String,
        modifier: Modifier = Modifier,
        // Other properties
        style: TextStyle = LocalTextStyle.current
    ) 

    class TextStyle(
        val color: Color = Color.Unspecified,
        val fontSize: TextUnit = TextUnit.Unspecified,
        val fontWeight: FontWeight? = null,
        val fontStyle: FontStyle? = null,
        val fontSynthesis: FontSynthesis? = null,
        val fontFamily: FontFamily? = null,
        val fontFeatureSettings: String? = null,
        val letterSpacing: TextUnit = TextUnit.Unspecified,
        val baselineShift: BaselineShift? = null,
        val textGeometricTransform: TextGeometricTransform? = null,
        val localeList: LocaleList? = null,
        val background: Color = Color.Unspecified,
        val textDecoration: TextDecoration? = null,
        val shadow: Shadow? = null,
        val textAlign: TextAlign? = null,
        val textDirection: TextDirection? = null,
        val lineHeight: TextUnit = TextUnit.Unspecified,
        val textIndent: TextIndent? = null
    )

CodePudding user response:

If you need innerspacing of child items in a LazyColumn or a LazyRow, you should use verticalArrangement = modifier.spacedBy(..) or horizontalArrangement = modifier.spacedBy(...)

  • Related