[go: nahoru, domu]

blob: 54ecff3b076413a69627d688c9c9c565af14bed0 [file] [log] [blame]
/*
* Copyright 2019 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.
*/
@file:Suppress("Deprecation")
package androidx.ui.material.ripple
import androidx.animation.AnimationClockObservable
import androidx.compose.CompositionLifecycleObserver
import androidx.compose.StructurallyEqual
import androidx.compose.frames.modelListOf
import androidx.compose.getValue
import androidx.compose.mutableStateOf
import androidx.compose.remember
import androidx.compose.setValue
import androidx.ui.animation.asDisposableClock
import androidx.ui.animation.transitionsEnabled
import androidx.ui.core.AnimationClockAmbient
import androidx.ui.core.Constraints
import androidx.ui.core.ContentDrawScope
import androidx.ui.core.DensityAmbient
import androidx.ui.core.DrawModifier
import androidx.ui.core.LayoutDirection
import androidx.ui.core.LayoutModifier
import androidx.ui.core.Measurable
import androidx.ui.core.MeasureScope
import androidx.ui.core.Modifier
import androidx.ui.core.composed
import androidx.ui.core.gesture.pressIndicatorGestureFilter
import androidx.ui.graphics.Color
import androidx.ui.graphics.useOrElse
import androidx.ui.unit.Density
import androidx.ui.unit.Dp
import androidx.ui.unit.IntPxSize
import androidx.ui.unit.PxPosition
import androidx.ui.unit.center
import androidx.ui.unit.ipx
import androidx.ui.unit.toPxSize
import androidx.ui.util.fastForEach
/**
* Ripple is a [Modifier] which draws the visual indicator for a pressed state.
*
* Ripple responds to a tap by starting a new [RippleEffect] animation.
* For creating an effect it uses the [RippleTheme.factory].
*
* @sample androidx.ui.material.samples.RippleSample
*
* @param bounded If true, ripples are clipped by the bounds of the target layout. Unbounded
* ripples always animate from the target layout center, bounded ripples animate from the touch
* position.
* @param radius Effects grow up to this size. If null is provided the size would be calculated
* based on the target layout size.
* @param color The Ripple color is usually the same color used by the text or iconography in the
* component. If [Color.Unset] is provided the color will be calculated by
* [RippleTheme.defaultColor].
* @param clock The animation clock observable that will drive this ripple effect
* @param enabled The ripple effect will not start if false is provided.
*/
fun Modifier.ripple(
bounded: Boolean = true,
radius: Dp? = null,
color: Color = Color.Unset,
enabled: Boolean = true,
clock: AnimationClockObservable? = null
): Modifier = composed {
@Suppress("NAME_SHADOWING") // don't allow usage of the parameter clock, only the disposable
val clock = (clock ?: AnimationClockAmbient.current).asDisposableClock()
val density = DensityAmbient.current
val rippleModifier = remember { RippleModifier() }
val theme = RippleThemeAmbient.current
rippleModifier.color = (color.useOrElse { theme.defaultColor() }).copy(alpha = theme.opacity())
val pressIndicator = Modifier.pressIndicatorGestureFilter(
onStart = { position ->
if (enabled && transitionsEnabled) {
rippleModifier.handleStart(position, theme.factory, density, bounded, radius, clock)
}
},
onStop = { rippleModifier.handleFinish(false) },
onCancel = { rippleModifier.handleFinish(true) }
)
pressIndicator + rippleModifier
}
private class RippleModifier : DrawModifier, LayoutModifier, CompositionLifecycleObserver {
var color: Color by mutableStateOf(Color.Transparent, StructurallyEqual)
private var size: IntPxSize = IntPxSize(0.ipx, 0.ipx)
private var effects = modelListOf<RippleEffect>()
private var currentEffect: RippleEffect? = null
override fun MeasureScope.measure(
measurable: Measurable,
constraints: Constraints,
layoutDirection: LayoutDirection
): MeasureScope.MeasureResult {
val placeable = measurable.measure(constraints)
size = IntPxSize(placeable.width, placeable.height)
return layout(placeable.width, placeable.height) {
placeable.place(0.ipx, 0.ipx)
}
}
fun handleStart(
touchPosition: PxPosition,
factory: RippleEffectFactory,
density: Density,
bounded: Boolean,
radius: Dp?,
clock: AnimationClockObservable
) {
val position = if (bounded) touchPosition else size.toPxSize().center()
val onAnimationFinished = { effect: RippleEffect ->
effects.remove(effect)
if (currentEffect == effect) {
currentEffect = null
}
}
val effect = factory.create(
size,
position,
density,
radius,
bounded,
clock,
onAnimationFinished
)
effects.add(effect)
currentEffect = effect
}
fun handleFinish(canceled: Boolean) {
currentEffect?.finish(canceled)
currentEffect = null
}
override fun ContentDrawScope.draw() {
drawContent()
effects.fastForEach { it.draw(this, this@RippleModifier.size, color) }
}
override fun onEnter() {
// do nothing
}
override fun onLeave() {
effects.fastForEach { it.dispose() }
effects.clear()
currentEffect = null
}
}