1

I'm trying to create a ruler view entirely with draw commands. The ruler should be faded towards the edges.

I can use a linear gradient for this, but the effect seems to be applied way to strongly

In the code sample below, the colorStops and brush variables are most relevant. Providing full code for completeness' sake. Note I applied a graphicsLayer to add an alpha channel to my composable. I have applied the DstIn BlendMode successfully for this purpose in another Composable. The red box is only for debugging purposes. It appears to me as if the alpha values in the colorStops are multiplied incorrectly with the 100% (or 99%) alpha of the underlying content.

Actual result:

actual result

Desired result:

  • I expect the ruler to be fully visible in the center (this is already the case)
  • I expect the ruler to be visible with 60% opacity at 25% of the image's width. This is currently much closer to 0. Although I don't care about the red box, I expect the same for it's visibility

Result wit the last drawRect call commented out shows both the red box and result of other draw calls. The right half of the image having no ruler is expected.

Result without gradient brush applied

BoxWithConstraints(
            modifier = Modifier
                .fillMaxWidth()
                .height(rulerHeightDp)
                .drag(state = state, onValueChanged = onValueChanged),
            contentAlignment = Alignment.TopCenter,
        ) {
            val textMeasurer = rememberTextMeasurer(cacheSize = numTicks)

            Box(modifier = Modifier
                .width(maxWidth)
                .height(rulerHeightDp)
                .graphicsLayer { alpha = 0.99f }
                .drawWithCache {
                    val barWidthPx = TickWidth.toPx()
                    val minorTickHeightPx = MinorTickHeight.toPx()
                    val majorTickHeightPx = MajorTickHeight.toPx()
                    val textOffsetVerticalPx = majorTickHeightPx + 4.dp.toPx()

                    val stepWidthPx = scale
                        .stepWidthDp()
                        .toPx()
                    val halfStepsPerScreen = (constraints.maxWidth / stepWidthPx / 2f).toInt()
                    val middleStep = state.currentValue / scale.pointsPerTick
                    val startStep = (middleStep - halfStepsPerScreen)
                        .toInt()
                        .coerceAtLeast(0)
                    val endStep = (middleStep + halfStepsPerScreen)
                        .toInt()
                        .coerceAtMost(maxPoints / scale.pointsPerTick)

                    val centerOffset = constraints.maxWidth / 2f

                    // content is visible where White, invisible where Transparent
                    val colorStops = arrayOf(
                        0.0f to Color.White.copy(alpha = 0.15f),
                        0.25f to Color.White.copy(alpha = 0.6f),
                        0.5f to Color.White.copy(alpha = 1f),
                        0.75f to Color.White.copy(alpha = 0.6f),
                        1f to Color.White.copy(alpha = 0.15f),
                    )
                    val brush = Brush.horizontalGradient(*colorStops)

                    onDrawWithContent {
                        drawContent()

                        for (step in startStep..endStep) {
                            val tickValue = step * scale.pointsPerTick
                            val offsetX = (step - middleStep) * stepWidthPx

                            val tickHeightPx = if (tickValue % scale.pointsPerMajorTick == 0) {
                                majorTickHeightPx
                            } else {
                                minorTickHeightPx
                            }
                            drawRect(
                                color = Color.White,
                                topLeft = Offset(offsetX, 0f),
                                size = Size(barWidthPx, tickHeightPx),
                            )


                            if (tickValue % scale.pointsPerLabel == 0) {
                                // label
                                val textLayoutResult = textMeasurer.measure(
                                    // textMeasurer caches the result
                                    text = tickValue.toString(),
                                    style = labelStyle,
                                )
                                drawText(
                                    textLayoutResult = textLayoutResult,
                                    color = Color.White,
                                    topLeft = Offset(
                                        offsetX - textLayoutResult.size.width / 2,
                                        textOffsetVerticalPx,
                                    ),
                                )
                            }

                            drawRect(
                                brush = brush,
                                blendMode = BlendMode.DstIn,
                            )
                        }
                    }
                }) {
                Box(Modifier
                    .background(Color.Red)
                    .fillMaxSize()
                    ) {

                }
            }
        }
Nino van Hooff
  • 3,677
  • 1
  • 36
  • 52

0 Answers0