In this article, we will take a look at how Compose can be used to customize the layout and the use of Intrinsic Measurement.

One: custom layout

Custom layout can be handled in two ways, one is to use the layout Modifier Modifier Modifier. Layout, one is to use layout to create a custom layout. Let’s first talk about the Modifier. Layout

1.1 Use layout modifiers to implement custom layouts

We customize a Modifier extension function, Modifier. CustomCornerPosLayout. And what this method does is you can use the CornerPosition that we pass in and put the view in the top left corner, the bottom left corner, the top right corner, and the bottom right corner. The code is as follows:

enum class CornerPosition{
    TopLeft,
    TopRight,
    BottomLeft,
    BottomRight
}

fun Modifier.customCornerPosLayout(pos:CornerPosition) = layout { measurable, constraints ->
    // Measure the composable
    val placeable = measurable.measure(constraints)
    layout(constraints.maxWidth, constraints.maxHeight) {
        // Where the composable gets placed
        when(pos){
            CornerPosition.TopLeft->{
                placeable.placeRelative(0.0)
            }
            CornerPosition.TopRight->{
                placeable.placeRelative(constraints.maxWidth-placeable.width, 0)
            }
            CornerPosition.BottomLeft->{
                placeable.placeRelative(0. constraints.maxHeight-placeable.height) } CornerPosition.BottomRight->{ placeable.placeRelative(constraints.maxWidth-placeable.width, constraints.maxHeight-placeable.height) } } } }Copy the code
  • First we declare an enumeration. It’s called a CornerPosition, and you have four different values, so TopLeft is the TopLeft, TopRight, BottomLeft, BottomRight is the bottom corner.
  • Next we define a Modifier extension function customCornerPosLayout with the entry pos:CornerPosition to our enumerated class
  • CustomCornerPosLayout is implemented by lamda of layout. In fact, call Modifier. Layout to achieve a custom layout. There are two parameters: one is Measurable, and the other is constraints to the parent control
  • The first step in custom layout val placeable = Measurable. Measure is based on the constraints of the Measurable parent control. Generate a placeable class by using the method Measurable. Measure. This class has the width and height of the child controls
  • The second part of the custom layout calls layout(w, H) to set the width and height that the view can use. We use layout(constraints. MaxWidth, constraints. MaxHeight) and pass in the maximum width that the parent controls can use. MaxHeight The parent control provides the maximum height that can be used
  • Then the third step is to place the element in layout{} by placeable. PlaceRelative. We are according to the incoming parameter is the top left corner CornerPosition. The TopLeft, or the bottom left corner CornerPosition. BottomLeft, or the upper right corner CornerPosition. The TopRight, Or the bottom right hand corner CornerPosition. BottomRight to place the position of the interface element.
  • PlaceRelative (0, 0) the top left corner is 0, 0
  • In the upper right x is the maximum width you want to subtract the width of your view and y is 0
  • The x in the lower left corner is 0 and y is the maximum height you want to subtract from your own height
  • placeable.placeRelative(constraints.maxWidth-placeable.width, X is the maximum width you want to subtract the width of your view, and y is the maximum height you want to subtract the height of your view

Example: After the customization is complete, let’s take an example placed in the bottom right corner

@Preview
@Composable
fun customCornerPosLayoutTest(a){
    Box(modifier = Modifier.size(100.dp)
        .background(color = Color.Red)) {
        Box(modifier = Modifier
            .customCornerPosLayout(CornerPosition.BottomRight)
            .size(10.dp)
            .background(color = Color.Blue, shape = CircleShape))
    }
}
Copy the code

The effect is as follows:

1.2 Layout Create a custom Layout

The Modifier. Layout Modifier changes only the composable item of the call. If you want to measure and place multiple composable items, use Layout instead. In View system, to create custom layout we will extend ViewGroup and implement onMeasure and onLayout functions. In Compose, we simply write a function using the Layout composable. Let’s use Layout to customize a oriented Column Layout. Go straight to code

@Composable
fun CustomColumnView(
    layoutDirection:LayoutDirection,
    modifier: Modifier = Modifier,
    content: @Composable() () - >Unit){
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->
       var totalHeight = 0
       var maxWidth = 0
       val placeables = measurables.map {
           val placeable = it.measure(constraints)
           totalHeight+=placeable.height
           if(placeable.width>maxWidth){
               maxWidth = placeable.width
           }
           placeable
       }
       layout(maxWidth,totalHeight){
           if(layoutDirection == LayoutDirection.Ltr){
               var y = 0
               placeables.forEach {
                   it.place(0,y)
                   y+=it.height
               }
           }else{
               var y = totalHeight
               placeables.forEach {
                   y-=it.height
                   it.place(0,y)
               }
           }
       }
    }
}
Copy the code
  • We define a composable function for CustomColumnView
  • Defines three parameters, layoutDirection to control the direction, modifier modifier, content can be combined with the function content.
  • Internal implementation we are through Layout to customize the Layout
  • Measuring the width and height of each child view Val placeable = it. The first step in a custom layout is to perform measure measurements through the constraints and measurements given by the parent control. Get the Placeable object. The object can obtain the width and height of each subitem measured.
  • CustomColumnView CustomColumnView CustomColumnView CustomColumnView CustomColumnView CustomColumnView CustomColumnView CustomColumnView CustomColumnView
  • TotalHeight +=placeable. Height We sum the height of each child View to be the height of the CustomColumnView control.
  • If (placeable. Width >maxWidth){maxWidth = placeable. Width} We use the maximum width of the subview as the width of the CustomColumnView.
  • After calculating the width and height of CustomColumnView, we need to call Layout (maxWidth,totalHeight) to set the width and height of CustomColumnView
  • You also need to call placeable. Place (x, y) in the Layout method to place the child View
    layout(maxWidth,totalHeight){
             if(layoutDirection == LayoutDirection.Ltr){
                 var y = 0
                 placeables.forEach {
                     it.place(0,y)
                     y+=it.height
                 }
             }else{
                 var y = totalHeight
                 placeables.forEach {
                     y-=it.height
                     it.place(0,y)
                 }
             }
         }
      }
    Copy the code
  • For example, in the example above we are placing the child View from top to bottom by layoutDirection == layoutDirection.ltr. It. Place (0,y) y starts at 0, and y is the height of the previous subview
  • Place (0,y) y initial value is totalHeight (CustomColumnView height at the bottom) and y is always subtracting the current columnView height. So you can put it from the bottom up

After wrapping the custom CustomColumnView, let’s use the example CustomColumnView from top to bottom

@Preview
@Composable
fun bottomToTopCustomColumnTest(a){
    CustomColumnView(
        layoutDirection = LayoutDirection.Ltr,
        modifier = Modifier.background(color = Color.Red)
    ){
        Text(text = "First Text, first Text, first Text, first Text.")
        Text(text = "Second Text")
        Text(text = "The third Text")
        Text(text = "Fourth Text")
        Text(text = "Fifth Text, fifth Text")}}Copy the code

The effect is as follows:CustomColumnView from the bottom up

@Preview
@Composable
fun bottomToTopCustomColumnTest(a){
    CustomColumnView(
        layoutDirection = LayoutDirection.Rtl,
        modifier = Modifier.background(color = Color.Red)
    ){
        Text(text = "First Text, first Text, first Text, first Text.")
        Text(text = "Second Text")
        Text(text = "The third Text")
        Text(text = "Fourth Text")
        Text(text = "Fifth Text, fifth Text")}}Copy the code

Intrinsic Measurement of Intrinsic characteristics

Previously in The Android View system, there were multiple measurements. So in View system we need to reduce the nesting of layout levels in order to optimize performance. In Compose, the subitems can only be measured once, and two measurements will raise runtime exceptions, so the various nested subitems in Compose do not affect performance. Because of the Intrinsic Measurement in Compose, we can eliminate the need for multiple measurements.

Intrinsic Measurement refers to allowing the child to measure its own maximum and minimum size before allowing the parent to measure the child. For example, we want to achieve the following effectOur code looks like this:

@Preview
@Composable
fun twoViewTest(a) {
    Row() {
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(start = 4.dp)
                .wrapContentWidth(Alignment.Start),
            text = "Hi"
        )

        Divider(color = Color.Black, modifier = Modifier
            .fillMaxHeight()
            .width(1.dp))

        Text(
            modifier = Modifier
                .weight(1f)
                .padding(end = 4.dp)
                .wrapContentWidth(Alignment.End),

            text = "there")}}Copy the code

We thought the height of the row would automatically be the height of the Text. The result is this:This happens because the Row measures each subitem individually, and the height of the Text cannot be used to restrict the Divider. To do this we can use the height(intrinsicsie.min) decorator. We use intrinsicsie.min to say that the height to be used is the minimum height of the child itself. The code is modified as follows:

@Preview
@Composable
fun twoViewTest(a) {
    Row(modifier = Modifier.height(IntrinsicSize.Min)) {
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(start = 4.dp)
                .wrapContentWidth(Alignment.Start),
            text = "Hi"
        )

        Divider(color = Color.Black, modifier = Modifier
            .fillMaxHeight()
            .width(1.dp))

        Text(
            modifier = Modifier
                .weight(1f)
                .padding(end = 4.dp)
                .wrapContentWidth(Alignment.End),

            text = "there")}}Copy the code

That’s what we’re looking forThis is the use of intrinsicsies. they can provide intrinsicsies. Min and intrinsicsie.max. The maximum and minimum dimensions of the subitem itself

See article: official website