feat(android): scrubber thumb pill — fixes off-center perception
android / Build + lint + test (push) Successful in 4m0s
android / Build + lint + test (push) Successful in 4m0s
User report: the round dot didn't read as vertically centered on the 4dp track even though geometrically it was (M3's SliderLayout centers the track slot within the thumb's height). A small circle on a thin horizontal bar is a known perceptual offset — the eye expects the bar to bisect the circle, but the circle's mass extends above and below in equal amounts the brain reads as a lift. Swap the 14dp circle for a 4dp x 18dp vertical pill (CircleShape on a non-square Box renders as a stadium). Same width as the track, clearly taller — the bar visibly passes through the pill's horizontal axis with no ambiguity. Also aligns with M3 expressive's new vertical-handle slider direction. Updates the ScrubTrack docstring that still referenced the prior 14dp-on-4dp pairing.
This commit is contained in:
@@ -88,6 +88,8 @@ private const val PLAY_PAUSE_ICON_DP = 56
|
||||
private const val POP_GRACE_MS = 500L
|
||||
private val SCRUB_TRACK_HEIGHT_DP = 4.dp
|
||||
private val SCRUB_TRACK_CORNER_DP = 2.dp
|
||||
private val SCRUB_THUMB_WIDTH_DP = 4.dp
|
||||
private val SCRUB_THUMB_HEIGHT_DP = 18.dp
|
||||
|
||||
// Vertical drag-down threshold (in pixels) past which the gesture
|
||||
// pops the player. Matches Flutter's 80px threshold in spirit;
|
||||
@@ -478,15 +480,22 @@ private fun ScrubberRow(positionMs: Long, durationMs: Long, onSeek: (Long) -> Un
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = sliderColors,
|
||||
interactionSource = interactionSource,
|
||||
// Plain filled circle to match the web client's `<input
|
||||
// type="range" accent-color>` thumb — flat, no state-layer
|
||||
// ring, no border. M3's SliderDefaults.Thumb paints a state
|
||||
// layer halo on press; we drop it for visual parity. Slider's
|
||||
// 48dp hit slop still applies, so tapability is unchanged.
|
||||
// Thin vertical pill (4dp wide × 18dp tall, fully rounded).
|
||||
// A small circle on a thin horizontal bar reads as visually
|
||||
// off-center even when geometrically aligned — the eye
|
||||
// expects the bar to bisect the thumb but a 14dp circle's
|
||||
// mass extends above and below in equal amounts that the
|
||||
// brain perceives as offset. A vertical pill the same width
|
||||
// as the track removes the ambiguity: the bar passes through
|
||||
// the pill's horizontal axis cleanly. Also matches M3's
|
||||
// expressive-slider handle direction. State-layer halo is
|
||||
// still dropped for visual parity with the web scrubber.
|
||||
// Slider's 48dp hit slop still applies, so tapability is
|
||||
// unchanged.
|
||||
thumb = {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(14.dp)
|
||||
.size(width = SCRUB_THUMB_WIDTH_DP, height = SCRUB_THUMB_HEIGHT_DP)
|
||||
.clip(CircleShape)
|
||||
.background(accent),
|
||||
)
|
||||
@@ -513,12 +522,11 @@ private fun ScrubberRow(positionMs: Long, durationMs: Long, onSeek: (Long) -> Un
|
||||
|
||||
/**
|
||||
* Custom 4dp rounded scrubber track. M3's default Track is 16dp tall
|
||||
* and reads as a heavy pill rather than a measurement line; making
|
||||
* the thumb visibly taller than the track (14dp thumb on a 4dp bar)
|
||||
* restores the "handle on a string" cue your eye reads as "tool,
|
||||
* draggable." Also drops M3's stop indicator dot, which the web
|
||||
* scrubber doesn't have. Extracted so [ScrubberRow] stays under
|
||||
* detekt's LongMethod ceiling.
|
||||
* and reads as a heavy pill rather than a measurement line; the
|
||||
* pill-thumb-on-thin-track pairing restores the "handle on a string"
|
||||
* cue your eye reads as "tool, draggable." Also drops M3's stop
|
||||
* indicator dot, which the web scrubber doesn't have. Extracted so
|
||||
* [ScrubberRow] stays under detekt's LongMethod ceiling.
|
||||
*/
|
||||
@Composable
|
||||
private fun ScrubTrack(fraction: Float, accent: Color) {
|
||||
|
||||
Reference in New Issue
Block a user