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:
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.
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()
) {
}
}
}