Use custom Modifier.Node instead of background

pull/1063/head
Ben Trengrove 12 months ago
parent d8880e98f0
commit 72da8e2e56

@ -16,6 +16,7 @@
package com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar
import android.annotation.SuppressLint
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.SpringSpec
@ -38,17 +39,31 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorProducer
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.drawOutline
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.node.invalidateDraw
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar.ThumbState.Active
import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar.ThumbState.Dormant
import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar.ThumbState.Inactive
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.launch
/**
* The time period for showing the scrollbar thumb after interacting with it, before it fades away
@ -130,12 +145,7 @@ private fun ScrollableState.DraggableScrollbarThumb(
Horizontal -> height(12.dp).fillMaxWidth()
}
}
.background(
color = scrollbarThumbColor(
interactionSource = interactionSource,
),
shape = RoundedCornerShape(16.dp),
),
.scrollThumb(this, interactionSource),
)
}
@ -155,31 +165,72 @@ private fun ScrollableState.DecorativeScrollbarThumb(
Horizontal -> height(2.dp).fillMaxWidth()
}
}
.background(
color = scrollbarThumbColor(
interactionSource = interactionSource,
),
shape = RoundedCornerShape(16.dp),
),
.scrollThumb(this, interactionSource),
)
}
// TODO: This lint is removed in 1.6 as the recommendation has changed
// remove when project is upgraded
@SuppressLint("ComposableModifierFactory")
@Composable
private fun Modifier.scrollThumb(
scrollableState: ScrollableState,
interactionSource: InteractionSource
): Modifier {
val colorState = scrollbarThumbColor(scrollableState, interactionSource)
return this then ScrollThumbElement { colorState.value }
}
private data class ScrollThumbElement(val colorProducer: ColorProducer)
: ModifierNodeElement<ScrollThumbNode>() {
override fun create(): ScrollThumbNode = ScrollThumbNode(colorProducer)
override fun update(node: ScrollThumbNode) {
node.colorProducer = colorProducer
node.invalidateDraw()
}
}
private class ScrollThumbNode(var colorProducer: ColorProducer): DrawModifierNode, Modifier.Node() {
private val shape = RoundedCornerShape(16.dp)
// naive cache outline calculation if size is the same
private var lastSize: Size? = null
private var lastLayoutDirection: LayoutDirection? = null
private var lastOutline: Outline? = null
override fun ContentDrawScope.draw() {
val color = colorProducer()
val outline =
if (size == lastSize && layoutDirection == lastLayoutDirection) {
lastOutline!!
} else {
shape.createOutline(size, layoutDirection, this)
}
if (color != Color.Unspecified) drawOutline(outline, color = color)
lastOutline = outline
lastSize = size
lastLayoutDirection = layoutDirection
}
}
/**
* The color of the scrollbar thumb as a function of its interaction state.
* @param interactionSource source of interactions in the scrolling container
*/
@Composable
private fun ScrollableState.scrollbarThumbColor(
interactionSource: InteractionSource,
): Color {
private fun scrollbarThumbColor(
scrollableState: ScrollableState,
interactionSource: InteractionSource
): State<Color> {
var state by remember { mutableStateOf(Dormant) }
val pressed by interactionSource.collectIsPressedAsState()
val hovered by interactionSource.collectIsHoveredAsState()
val dragged by interactionSource.collectIsDraggedAsState()
val active = (canScrollForward || canScrollForward) &&
(pressed || hovered || dragged || isScrollInProgress)
val active = (scrollableState.canScrollForward || scrollableState.canScrollForward) &&
(pressed || hovered || dragged || scrollableState.isScrollInProgress)
val color by animateColorAsState(
val color = animateColorAsState(
targetValue = when (state) {
Active -> MaterialTheme.colorScheme.onSurface.copy(0.5f)
Inactive -> MaterialTheme.colorScheme.onSurface.copy(alpha = 0.2f)

Loading…
Cancel
Save