CommonRipple.kt
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.material.ripple
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.runtime.Composable
import androidx.compose.runtime.RememberObserver
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.unit.Dp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
/**
* Common Ripple implementation that directly animates and draws to the underlying canvas
* provided by [ContentDrawScope].
*
* @see Ripple
*/
@Stable
internal class CommonRipple(
bounded: Boolean,
radius: Dp,
color: State<Color>
) : Ripple(bounded, radius, color) {
@Composable
override fun rememberUpdatedRippleInstance(
interactionSource: InteractionSource,
bounded: Boolean,
radius: Dp,
color: State<Color>,
rippleAlpha: State<RippleAlpha>
): RippleIndicationInstance {
return remember(interactionSource, this) {
CommonRippleIndicationInstance(bounded, radius, color, rippleAlpha)
}
}
}
internal class CommonRippleIndicationInstance constructor(
private val bounded: Boolean,
private val radius: Dp,
private val color: State<Color>,
private val rippleAlpha: State<RippleAlpha>
) : RippleIndicationInstance(bounded, rippleAlpha), RememberObserver {
private val ripples = mutableStateMapOf<PressInteraction.Press, RippleAnimation>()
override fun ContentDrawScope.drawIndication() {
val color = color.value
drawContent()
drawStateLayer(radius, color)
drawRipples(color)
}
override fun addRipple(interaction: PressInteraction.Press, scope: CoroutineScope) {
// Finish existing ripples
ripples.forEach { (_, ripple) -> ripple.finish() }
val origin = if (bounded) interaction.pressPosition else null
val rippleAnimation = RippleAnimation(
origin = origin,
radius = radius,
bounded = bounded
)
ripples[interaction] = rippleAnimation
scope.launch {
try {
rippleAnimation.animate()
} finally {
ripples.remove(interaction)
}
}
}
override fun removeRipple(interaction: PressInteraction.Press) {
ripples[interaction]?.finish()
}
private fun DrawScope.drawRipples(color: Color) {
ripples.forEach { (_, ripple) ->
with(ripple) {
val alpha = rippleAlpha.value.pressedAlpha
if (alpha != 0f) {
draw(color.copy(alpha = alpha))
}
}
}
}
override fun onRemembered() {}
override fun onForgotten() {
ripples.clear()
}
override fun onAbandoned() {
ripples.clear()
}
}