2

Current code

@Composable
fun TextDemo() {
    var selectedIndex by remember {
        mutableIntStateOf(0)
    }
    Row {
        TabText(
            text = "First",
            isSelected = selectedIndex == 0,
            onClick = {
                selectedIndex = 0
            },
        )
        TabText(
            text = "Second",
            isSelected = selectedIndex == 1,
            onClick = {
                selectedIndex = 1
            },
        )
    }
}

@Composable
fun TabText(
    text: String,
    isSelected: Boolean,
    onClick: () -> Unit,
) {
    val tabTextColor by animateColorAsState(
        targetValue = if (isSelected) {
            Color.Red
        } else {
            Color.Black
        },
        animationSpec = tween(
            easing = LinearEasing,
        ),
        label = "tab_text_color",
    )

    Text(
        modifier = Modifier
            .padding(8.dp)
            .clickable {
                onClick()
            },
        text = text,
        color = tabTextColor,
    )
}

UI for reference Two Text in a Row

UI Screenshot

Layout Inspector recompositions

Screenshot of Layout inspector

Question

How to reduce the recompositions when text color changes?

For properties like alpha, transition, etc, It is possible to avoid recompositions when animating using Modifier.graphicsLayer {}

The same code with alpha animation instead of color is recomposed only once per selection change.

Layout inspector screenshot

Code

@Composable
fun TabText(
    text: String,
    isSelected: Boolean,
    onClick: () -> Unit,
) {
    val alphaValue by animateFloatAsState(
        targetValue = if (isSelected) {
            1F
        } else {
            0.5F
        },
        animationSpec = tween(
            easing = LinearEasing,
        ),
        label = "tab_text_color",
    )

    Text(
        modifier = Modifier
            .graphicsLayer {
                alpha = alphaValue
            }
            .padding(8.dp)
            .clickable {
                onClick()
            },
        text = text,
    )
}

3 Answers 3

6

First of all when you log recomposition that reads a State it should better be done inside SideEffect otherwise it's possible to get false positives, because logging itself also counts as a State read.

To have one recomposition for text color change you can use Canvas or any draw Modifier inside Tab and call only draw phase while Color changes using TextMeasurer and drawText function of DrawScope.

Second option is to use BlendModes with Modifier.drawContent{} to change color with one recompostion as

@Preview
@Composable
private fun TextAnimationTest() {

    var isSelected by remember {
        mutableStateOf(false)
    }

    SideEffect {
        println("Recomposing...")
    }

    val tabTextColor by animateColorAsState(
        targetValue = if (isSelected) {
            Color.Red
        } else {
            Color.Black
        },
        animationSpec = tween(
            easing = LinearEasing,
        ),
        label = "tab_text_color",
    )


  Column {
      Button(
          onClick = {
              isSelected = isSelected.not()
          }
      ) {
          Text("Selected: $isSelected")
      }

      Text("Some Text", modifier = Modifier
          .graphicsLayer {
              compositingStrategy = CompositingStrategy.Offscreen
          }
          .drawWithContent {
              drawContent()

              drawRect(
                  color = tabTextColor,
                  blendMode = BlendMode.SrcIn
              )
          }
      )
  }
}
Sign up to request clarification or add additional context in comments.

6 Comments

😕 So complicated solution for such a simple use-case. 😔
All you have to do is set graphicsLayer for blend mode and draw a rectangle above text with a BlendMode.SrcIn. Other option is drawing inside Canvas. But bottomline is you need to do it in draw phase if you don't want to trigger recompositions
Also found that BasicText has a lambda modifier for this use-case. Material Text does not use lambda. kotlinlang.slack.com/archives/CJLTWPH7S/…
That's great then. Lambdas are the way to defer state reads from composition to layout or draw phases. developer.android.com/jetpack/compose/performance/…
Raised an issue for the color provider in Text. issuetracker.google.com/issues/305542850
|
1

Given I am able to replace my Text usage with BasicText for my use-case, I can use the ColorProvider provided by BasicText.

Source.


This may not be possible in all scenarios and for such use cases, this solution by Thracian can be used.


Created an issue to provide support for color provider in Text as well. https://issuetracker.google.com/issues/305542850

Comments

0

material3:1.5.0-alpha04+ allows the use of the ColorProducer lambda for the Text composable function.

Sample;

@Composable
fun ToggleColorText() {
    var isRed by remember { mutableStateOf(true) }

    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = "Foo Bar",
            color = { if (isRed) Color.Red else Color.Blue }, // ColorProducer Lambda
            fontSize = 20.sp,
            fontWeight = FontWeight.Bold
        )

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

        Button(onClick = { isRed = !isRed }) {
            Text("Switch")
        }
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.