androidkotlinandroid-jetpack-compose

Reverse order of items in a Row


I have a composable component that represent a message. Each message can either be incoming or outgoing, depending on that I would like to reverse all items in my message component.

The only way I found is to force a RTL layout, however it leads to text being reversed too. Example

Is there any other way around this?

MessageView.kt

@Composable
fun MessageView(
    message: Message
) = Row(
    modifier = Modifier
        .fillMaxWidth()
        .wrapContentHeight(),
    verticalAlignment = Alignment.Bottom
) {

    val (isIncoming) = message

    val direction = if (isIncoming) {
        LayoutDirection.Ltr
    } else {
        LayoutDirection.Rtl
    }

    CompositionLocalProvider(
        LocalLayoutDirection provides direction
    ) {
        MessageViewContent(message)
    }

}

@Composable
private fun MessageViewContent(
    message: Message
) = Row(
    modifier = Modifier
        .fillMaxWidth()
        .wrapContentHeight(),
    verticalAlignment = Alignment.Bottom
) {

    val (isIncoming, text, at) = message

    val background: Color
    val textColor: Color
    val timeColor: Color
    val alignment: Alignment.Horizontal
    val textAlignment: TextAlign

    if (isIncoming) {
        background = Color(0xFFEFEFEF)
        textColor = Color(0xFF000000)
        timeColor = Color(0xFF929292)
        alignment = Alignment.End
        textAlignment = TextAlign.Start
    } else {
        background = Color(0xFFE0727F)
        textColor = Color(0xFFFEFEFE)
        timeColor = Color(0xB3FEFEFE)
        alignment = Alignment.Start
        textAlignment = TextAlign.End
    }

    Image(
        modifier = Modifier
            .size(40.dp)
            .clip(CircleShape),
        painter = painterResource(R.drawable.ic_launcher_background),
        contentDescription = null
    )

    Spacer(modifier = Modifier.width(12.dp))

    Column(
        modifier = Modifier
            .weight(1F, fill = false)
            .wrapContentWidth()
            .background(
                color = background,
                shape = RoundedCornerShape(8.dp)
            )
            .padding(4.dp),
        horizontalAlignment = alignment
    ) {

        Text(
            modifier = Modifier.padding(6.dp),
            style = TextStyle(
                fontSize = 16.sp,
                color = textColor
            ),
            text = text
        )

        Text(
            style = TextStyle(
                fontSize = 10.sp,
                color = timeColor,
                textAlign = textAlignment
            ),
            text = "${at.hour}:${at.minute}",
        )

    }

    Spacer(modifier = Modifier.width(60.dp))

}

Solution

  • So, I figured out a solution One can create a custom Horizontal Arrangement which uses code from already existing arrangements.

    To use it, the containing Row should span the width of the surface it's displayed on, and the items will simply be stacked the inverse side if the message is from the sender side.

    ConversationSide.kt

    import androidx.compose.foundation.layout.Arrangement
    import androidx.compose.ui.unit.Density
    import androidx.compose.ui.unit.LayoutDirection
    
    private sealed interface ConversationSide : Arrangement.Horizontal {
    
    
        data object Sender : ConversationSide {
    
            override fun Density.arrange(
                totalSize: Int,
                sizes: IntArray,
                layoutDirection: LayoutDirection,
                outPositions: IntArray
            ) {
                with(Arrangement.Start) {
                    arrange(
                        totalSize = totalSize,
                        sizes = sizes,
                        layoutDirection = when (layoutDirection) {
                            LayoutDirection.Ltr -> LayoutDirection.Rtl
                            LayoutDirection.Rtl -> LayoutDirection.Ltr
                        },
                        outPositions = outPositions
                    )
                }
            }
        }
    
        data object Receiver :
            ConversationSide,
            Arrangement.Horizontal by Arrangement.Start 
    }
    

    Usage:

    @Composable
    fun MessageView(
        message: Message
    ) = Row(
        modifier = Modifier
            .fillMaxWidth()
            .wrapContentHeight(),
        verticalAlignment = Alignment.Bottom,
        horizontalArrangement = when {
            message.isIncoming -> Side.Receiver
            else -> Side.Sender
        }
    ) {
    
        ...
    
    }