Skip to content

Commit

Permalink
Add more control options for the hide/show animation.
Browse files Browse the repository at this point in the history
There are now 4 parameters you can control:
  - hideDelayMillis
  - hideDisplacement
  - hideEasingAnimation
  - durationAnimationMillis
  • Loading branch information
nanihadesuka committed May 5, 2024
1 parent 257bf93 commit 5406b96
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 141 deletions.
64 changes: 34 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,37 +88,41 @@ indicatorContent = { index, isThumbSelected ->
*/
@Stable
data class ScrollbarSettings(
val enabled: Boolean = Default.enabled,
val side: ScrollbarLayoutSide = Default.side,
val alwaysShowScrollbar: Boolean = Default.alwaysShowScrollbar,
val scrollbarPadding: Dp = Default.scrollbarPadding,
val thumbThickness: Dp = Default.thumbThickness,
val thumbShape: Shape = Default.thumbShape,
val thumbMinLength: Float = Default.thumbMinLength,
val thumbUnselectedColor: Color = Default.thumbUnselectedColor,
val thumbSelectedColor: Color = Default.thumbSelectedColor,
val selectionMode: ScrollbarSelectionMode = Default.selectionMode,
val selectionActionable: ScrollbarSelectionActionable = Default.selectionActionable,
val hideDelayMillis: Int = Default.hideDelayMillis,
val durationAnimationMillis: Int = Default.durationAnimationMillis
val enabled: Boolean = Default.enabled,
val side: ScrollbarLayoutSide = Default.side,
val alwaysShowScrollbar: Boolean = Default.alwaysShowScrollbar,
val scrollbarPadding: Dp = Default.scrollbarPadding,
val thumbThickness: Dp = Default.thumbThickness,
val thumbShape: Shape = Default.thumbShape,
val thumbMinLength: Float = Default.thumbMinLength,
val thumbUnselectedColor: Color = Default.thumbUnselectedColor,
val thumbSelectedColor: Color = Default.thumbSelectedColor,
val selectionMode: ScrollbarSelectionMode = Default.selectionMode,
val selectionActionable: ScrollbarSelectionActionable = Default.selectionActionable,
val hideDelayMillis: Int = Default.hideDelayMillis,
val hideDisplacement: Dp = Default.hideDisplacement,
val hideEasingAnimation: Easing = Default.hideEasingAnimation,
val durationAnimationMillis: Int = Default.durationAnimationMillis,
) {
companion object {
val Default = ScrollbarSettings(
enabled = true,
side = ScrollbarLayoutSide.End,
alwaysShowScrollbar = false,
thumbThickness = 6.dp,
scrollbarPadding = 8.dp,
thumbMinLength = 0.1f,
thumbUnselectedColor = Color(0xFF2A59B6),
thumbSelectedColor = Color(0xFF5281CA),
thumbShape = CircleShape,
selectionMode = ScrollbarSelectionMode.Thumb,
selectionActionable = ScrollbarSelectionActionable.Always,
hideDelayMillis = 400,
durationAnimationMillis = 500,
)
}
companion object {
val Default = ScrollbarSettings(
enabled = true,
side = ScrollbarLayoutSide.End,
alwaysShowScrollbar = false,
thumbThickness = 6.dp,
scrollbarPadding = 8.dp,
thumbMinLength = 0.1f,
thumbUnselectedColor = Color(0xFF2A59B6),
thumbSelectedColor = Color(0xFF5281CA),
thumbShape = CircleShape,
selectionMode = ScrollbarSelectionMode.Thumb,
selectionActionable = ScrollbarSelectionActionable.Always,
hideDelayMillis = 400,
hideDisplacement = 14.dp,
hideEasingAnimation = FastOutSlowInEasing,
durationAnimationMillis = 500,
)
}
}
```

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package my.nanihadesuka.compose

import androidx.compose.animation.core.Easing
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Stable
import androidx.compose.ui.graphics.Color
Expand All @@ -24,7 +26,9 @@ data class ScrollbarSettings(
val selectionMode: ScrollbarSelectionMode = Default.selectionMode,
val selectionActionable: ScrollbarSelectionActionable = Default.selectionActionable,
val hideDelayMillis: Int = Default.hideDelayMillis,
val durationAnimationMillis: Int = Default.durationAnimationMillis
val hideDisplacement: Dp = Default.hideDisplacement,
val hideEasingAnimation: Easing = Default.hideEasingAnimation,
val durationAnimationMillis: Int = Default.durationAnimationMillis,
) {
companion object {
val Default = ScrollbarSettings(
Expand All @@ -40,6 +44,8 @@ data class ScrollbarSettings(
selectionMode = ScrollbarSelectionMode.Thumb,
selectionActionable = ScrollbarSelectionActionable.Always,
hideDelayMillis = 400,
hideDisplacement = 14.dp,
hideEasingAnimation = FastOutSlowInEasing,
durationAnimationMillis = 500,
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package my.nanihadesuka.compose.foundation

import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
Expand All @@ -13,10 +10,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
Expand All @@ -25,7 +18,6 @@ import androidx.compose.ui.layout.Layout
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay
import my.nanihadesuka.compose.ScrollbarLayoutSide
import my.nanihadesuka.compose.ScrollbarSelectionActionable
import my.nanihadesuka.compose.TestTagsScrollbar
Expand All @@ -41,43 +33,10 @@ internal fun HorizontalScrollbarLayout(
indicator: (@Composable () -> Unit)?,
modifier: Modifier = Modifier,
) {
val isInActionSelectable = remember { mutableStateOf(thumbIsInAction) }
LaunchedEffect(thumbIsInAction) {
if (thumbIsInAction) {
isInActionSelectable.value = true
} else {
delay(timeMillis = settings.durationAnimationMillis.toLong() + settings.hideDelayMillis.toLong())
isInActionSelectable.value = false
}
}

val activeDraggableModifier = when (settings.selectionActionable) {
ScrollbarSelectionActionable.Always -> true
ScrollbarSelectionActionable.WhenVisible -> isInActionSelectable.value
}

val thumbColor by animateColorAsState(
targetValue = if (thumbIsSelected) settings.thumbUnselectedColor else settings.thumbUnselectedColor,
animationSpec = tween(durationMillis = 50),
label = "scrollbar thumb color value"
)

val hideAlpha by animateFloatAsState(
targetValue = if (thumbIsInAction) 1f else 0f,
animationSpec = tween(
durationMillis = if (thumbIsInAction) 75 else settings.durationAnimationMillis,
delayMillis = if (thumbIsInAction) 0 else settings.hideDelayMillis
),
label = "scrollbar alpha value"
)

val hideDisplacement by animateDpAsState(
targetValue = if (thumbIsInAction) 0.dp else 14.dp,
animationSpec = tween(
durationMillis = if (thumbIsInAction) 75 else settings.durationAnimationMillis,
delayMillis = if (thumbIsInAction) 0 else settings.hideDelayMillis
),
label = "scrollbar displacement value"
val state = rememberScrollbarLayoutState(
thumbIsInAction = thumbIsInAction,
thumbIsSelected = thumbIsSelected,
settings = settings,
)

Layout(
Expand All @@ -90,18 +49,18 @@ internal fun HorizontalScrollbarLayout(
top = if (settings.side == ScrollbarLayoutSide.Start) settings.scrollbarPadding else 0.dp,
bottom = if (settings.side == ScrollbarLayoutSide.End) settings.scrollbarPadding else 0.dp,
)
.alpha(hideAlpha)
.alpha(state.hideAlpha.value)
.clip(settings.thumbShape)
.height(settings.thumbThickness)
.background(thumbColor)
.background(state.thumbColor.value)
.testTag(TestTagsScrollbar.scrollbarThumb)
)
when (indicator) {
null -> Box(Modifier)
else -> Box(
Modifier
.testTag(TestTagsScrollbar.scrollbarIndicator)
.alpha(hideAlpha)
.alpha(state.hideAlpha.value)
) {
indicator()
}
Expand All @@ -110,7 +69,7 @@ internal fun HorizontalScrollbarLayout(
modifier = Modifier
.fillMaxWidth()
.height(settings.scrollbarPadding * 2 + settings.thumbThickness)
.run { if (activeDraggableModifier) then(draggableModifier) else this }
.run { if (state.activeDraggableModifier.value) then(draggableModifier) else this }
.testTag(TestTagsScrollbar.scrollbarContainer)
)
},
Expand All @@ -125,8 +84,8 @@ internal fun HorizontalScrollbarLayout(
val offset = (constraints.maxWidth.toFloat() * thumbOffsetNormalized).toInt()

val hideDisplacementPx = when (settings.side) {
ScrollbarLayoutSide.Start -> -hideDisplacement.roundToPx()
ScrollbarLayoutSide.End -> +hideDisplacement.roundToPx()
ScrollbarLayoutSide.Start -> -state.hideDisplacement.value.roundToPx()
ScrollbarLayoutSide.End -> +state.hideDisplacement.value.roundToPx()
}

placeableThumb.placeRelative(
Expand Down Expand Up @@ -179,7 +138,9 @@ private fun LayoutPreview() {
thumbUnselectedColor = Color.Green,
thumbSelectedColor = Color.Red,
side = ScrollbarLayoutSide.End,
selectionActionable = ScrollbarSelectionActionable.Always
selectionActionable = ScrollbarSelectionActionable.Always,
hideEasingAnimation = FastOutSlowInEasing,
hideDisplacement = 14.dp,
),
draggableModifier = Modifier,
thumbIsInAction = true,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package my.nanihadesuka.compose.foundation

import androidx.compose.animation.core.Easing
import androidx.compose.runtime.Stable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
Expand All @@ -9,13 +10,15 @@ import my.nanihadesuka.compose.ScrollbarSelectionActionable

@Stable
internal data class ScrollbarLayoutSettings(
val durationAnimationMillis: Int,
val hideDelayMillis: Int,
val scrollbarPadding: Dp,
val thumbShape: Shape,
val thumbThickness: Dp,
val thumbUnselectedColor: Color,
val thumbSelectedColor: Color,
val side: ScrollbarLayoutSide,
val selectionActionable: ScrollbarSelectionActionable,
val hideDisplacement: Dp,
val hideDelayMillis: Int,
val hideEasingAnimation: Easing,
val durationAnimationMillis: Int
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package my.nanihadesuka.compose.foundation

import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay
import my.nanihadesuka.compose.ScrollbarSelectionActionable


@Composable
internal fun rememberScrollbarLayoutState(
thumbIsInAction: Boolean,
thumbIsSelected: Boolean,
settings: ScrollbarLayoutSettings,
): ScrollbarLayoutState {
val settingsUpdated by rememberUpdatedState(settings)
val thumbIsInActionUpdated by rememberUpdatedState(thumbIsInAction)

val isInActionSelectable = remember { mutableStateOf(thumbIsInAction) }

LaunchedEffect(thumbIsInAction) {
if (thumbIsInAction) {
isInActionSelectable.value = true
} else {
delay(timeMillis = settingsUpdated.durationAnimationMillis.toLong() + settingsUpdated.hideDelayMillis.toLong())
isInActionSelectable.value = false
}
}

val activeDraggableModifier = remember {
derivedStateOf {
when (settingsUpdated.selectionActionable) {
ScrollbarSelectionActionable.Always -> true
ScrollbarSelectionActionable.WhenVisible -> isInActionSelectable.value
}
}
}

val thumbColor = animateColorAsState(
targetValue = if (thumbIsSelected) settingsUpdated.thumbSelectedColor else settingsUpdated.thumbUnselectedColor,
animationSpec = tween(durationMillis = 50),
label = "scrollbar thumb color value"
)

val currentDurationMillis = remember {
derivedStateOf {
val reductionRatio: Int = if (thumbIsInActionUpdated) 4 else 1
settingsUpdated.durationAnimationMillis / reductionRatio
}
}

val hideAlpha = animateFloatAsState(
targetValue = if (thumbIsInActionUpdated) 1f else 0f,
animationSpec = tween(
durationMillis = currentDurationMillis.value,
delayMillis = if (thumbIsInActionUpdated) 0 else settingsUpdated.hideDelayMillis,
easing = settingsUpdated.hideEasingAnimation
),
label = "scrollbar alpha value"
)

val hideDisplacement = animateDpAsState(
targetValue = if (thumbIsInActionUpdated) 0.dp else settingsUpdated.hideDisplacement,
animationSpec = tween(
durationMillis = currentDurationMillis.value,
delayMillis = if (thumbIsInActionUpdated) 0 else settingsUpdated.hideDelayMillis,
easing = settingsUpdated.hideEasingAnimation
),
label = "scrollbar displacement value"
)

return remember {
ScrollbarLayoutState(
activeDraggableModifier = activeDraggableModifier,
thumbColor = thumbColor,
hideDisplacement = hideDisplacement,
hideAlpha = hideAlpha
)
}

}

internal data class ScrollbarLayoutState(
val activeDraggableModifier: State<Boolean>,
val thumbColor: State<Color>,
val hideAlpha: State<Float>,
val hideDisplacement: State<Dp>
)
Loading

0 comments on commit 5406b96

Please sign in to comment.