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 const val POP_GRACE_MS = 500L
|
||||||
private val SCRUB_TRACK_HEIGHT_DP = 4.dp
|
private val SCRUB_TRACK_HEIGHT_DP = 4.dp
|
||||||
private val SCRUB_TRACK_CORNER_DP = 2.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
|
// Vertical drag-down threshold (in pixels) past which the gesture
|
||||||
// pops the player. Matches Flutter's 80px threshold in spirit;
|
// 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(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors = sliderColors,
|
colors = sliderColors,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
// Plain filled circle to match the web client's `<input
|
// Thin vertical pill (4dp wide × 18dp tall, fully rounded).
|
||||||
// type="range" accent-color>` thumb — flat, no state-layer
|
// A small circle on a thin horizontal bar reads as visually
|
||||||
// ring, no border. M3's SliderDefaults.Thumb paints a state
|
// off-center even when geometrically aligned — the eye
|
||||||
// layer halo on press; we drop it for visual parity. Slider's
|
// expects the bar to bisect the thumb but a 14dp circle's
|
||||||
// 48dp hit slop still applies, so tapability is unchanged.
|
// 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 = {
|
thumb = {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(14.dp)
|
.size(width = SCRUB_THUMB_WIDTH_DP, height = SCRUB_THUMB_HEIGHT_DP)
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.background(accent),
|
.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
|
* Custom 4dp rounded scrubber track. M3's default Track is 16dp tall
|
||||||
* and reads as a heavy pill rather than a measurement line; making
|
* and reads as a heavy pill rather than a measurement line; the
|
||||||
* the thumb visibly taller than the track (14dp thumb on a 4dp bar)
|
* pill-thumb-on-thin-track pairing restores the "handle on a string"
|
||||||
* restores the "handle on a string" cue your eye reads as "tool,
|
* cue your eye reads as "tool, draggable." Also drops M3's stop
|
||||||
* draggable." Also drops M3's stop indicator dot, which the web
|
* indicator dot, which the web scrubber doesn't have. Extracted so
|
||||||
* scrubber doesn't have. Extracted so [ScrubberRow] stays under
|
* [ScrubberRow] stays under detekt's LongMethod ceiling.
|
||||||
* detekt's LongMethod ceiling.
|
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
private fun ScrubTrack(fraction: Float, accent: Color) {
|
private fun ScrubTrack(fraction: Float, accent: Color) {
|
||||||
|
|||||||
Reference in New Issue
Block a user