En esta página, se incluyen ejemplos de cómo usar diferentes APIs de tecnología táctil para crear efectos personalizados en una aplicación para Android. Como gran parte de la información de esta página se basa en un buen conocimiento del funcionamiento de un actuador de vibración, te recomendamos que leas el Manual de accionadores de vibración.
En esta página, se incluyen los siguientes ejemplos.
- Patrones de vibración personalizados
- Patrón de aumento gradual: Es un patrón que comienza de forma fluida.
- Patrón repetitivo: Es un patrón sin fin.
- Patrón con resguardo: Una demostración de resguardo.
- Composiciones de vibración
- Resist: Es un efecto de arrastre con intensidad dinámica.
- Expandir: Es un efecto de aumento y disminución.
- Oscilación: Un efecto de oscilación que usa la primitiva
SPIN
. - Bounce: Es un efecto de rebote que usa la primitiva
THUD
.
Para obtener ejemplos adicionales, consulta Cómo agregar respuestas táctiles a eventos y sigue siempre los principios de diseño táctil.
Usa resguardos para controlar la compatibilidad de los dispositivos
Cuando implementes cualquier efecto personalizado, ten en cuenta lo siguiente:
- Qué capacidades del dispositivo se requieren para el efecto
- Qué hacer cuando el dispositivo no puede reproducir el efecto
La referencia de la API de la tecnología táctil de Android proporciona detalles sobre cómo verificar la compatibilidad con los componentes involucrados en la tecnología táctil para que tu app pueda proporcionar una experiencia general coherente.
Según tu caso de uso, es posible que desees inhabilitar los efectos personalizados o proporcionar efectos personalizados alternativos según diferentes capacidades potenciales.
Planifica las siguientes clases de alto nivel de capacidad del dispositivo:
Si usas primitivas táctiles, dispositivos compatibles con esas primitivas que necesitan los efectos personalizados (consulta la siguiente sección para obtener detalles sobre las primitivas).
Dispositivos con control de amplitud
Dispositivos con compatibilidad básica con vibración (encendido/apagado); en otras palabras, aquellos que no tienen control de amplitud
Si la elección de efectos táctiles de tu app tiene en cuenta estas categorías, su experiencia táctil del usuario debería seguir siendo predecible para cualquier dispositivo individual.
Uso de primitivas táctiles
Android incluye varias primitivas táctiles que varían en amplitud y frecuencia. Puedes usar una primitiva sola o varias en combinación para lograr efectos táctiles enriquecidos.
- Usa demoras de 50 ms o más para obtener intervalos discernibles entre dos primitivas, y ten en cuenta la duración de la primitiva si es posible.
- Usa escalas que difieran en una proporción de 1.4 o más para que la diferencia en la intensidad se perciba mejor.
Usa escalas de 0.5, 0.7 y 1.0 para crear una versión de intensidad baja, media y alta de una primitiva.
Crea patrones de vibración personalizados
Los patrones de vibración suelen usarse en la tecnología táctil de atención, como las notificaciones y los tonos de llamada. El servicio Vibrator
puede reproducir patrones de vibración largos que cambian la amplitud de vibración con el tiempo. Estos efectos se denominan formas de onda.
Los efectos de forma de onda se pueden percibir fácilmente, pero las vibraciones largas y repentinas pueden sorprender al usuario si se reproducen en un entorno tranquilo. El aumento demasiado rápido a una amplitud objetivo también puede producir ruidos audibles. La recomendación para diseñar patrones de forma de onda es suavizar las transiciones de amplitud para crear efectos de aumento y disminución.
Ejemplo: Patrón de aumento
Las formas de onda se representan como VibrationEffect
con tres parámetros:
- Timings: Es un array de duraciones, en milisegundos, para cada segmento de forma de onda.
- Amplitudes: Es la amplitud de vibración deseada para cada duración especificada en el primer argumento, representada por un valor entero de 0 a 255, en el que 0 representa el vibrador "desactivado" y 255 es la amplitud máxima del dispositivo.
- Índice de repetición: Es el índice en el array especificado en el primer argumento para comenzar a repetir la forma de onda, o -1 si debe reproducir el patrón solo una vez.
Este es un ejemplo de una forma de onda que emite pulsos dos veces con una pausa de 350 ms entre los pulsos. El primer pulso es una rampa suave hasta la amplitud máxima, y el segundo es una rampa rápida para mantener la amplitud máxima. La detención al final se define por el valor negativo del índice de repetición.
Kotlin
val timings: LongArray = longArrayOf(50, 50, 50, 50, 50, 100, 350, 25, 25, 25, 25, 200) val amplitudes: IntArray = intArrayOf(33, 51, 75, 113, 170, 255, 0, 38, 62, 100, 160, 255) val repeatIndex = -1 // Do not repeat. vibrator.vibrate(VibrationEffect.createWaveform(timings, amplitudes, repeatIndex))
Java
long[] timings = new long[] { 50, 50, 50, 50, 50, 100, 350, 25, 25, 25, 25, 200 }; int[] amplitudes = new int[] { 33, 51, 75, 113, 170, 255, 0, 38, 62, 100, 160, 255 }; int repeatIndex = -1; // Do not repeat. vibrator.vibrate(VibrationEffect.createWaveform(timings, amplitudes, repeatIndex));
Ejemplo: Patrón repetitivo
Las formas de onda también se pueden reproducir de forma repetida hasta que se cancelen. La forma de crear una forma de onda repetida es establecer un parámetro "repeat" no negativo. Cuando reproduces una forma de onda repetida, la vibración continúa hasta que se cancela de forma explícita en el servicio:
Kotlin
void startVibrating() { val timings: LongArray = longArrayOf(50, 50, 100, 50, 50) val amplitudes: IntArray = intArrayOf(64, 128, 255, 128, 64) val repeat = 1 // Repeat from the second entry, index = 1. VibrationEffect repeatingEffect = VibrationEffect.createWaveform(timings, amplitudes, repeat) // repeatingEffect can be used in multiple places. vibrator.vibrate(repeatingEffect) } void stopVibrating() { vibrator.cancel() }
Java
void startVibrating() { long[] timings = new long[] { 50, 50, 100, 50, 50 }; int[] amplitudes = new int[] { 64, 128, 255, 128, 64 }; int repeat = 1; // Repeat from the second entry, index = 1. VibrationEffect repeatingEffect = VibrationEffect.createWaveform(timings, amplitudes, repeat); // repeatingEffect can be used in multiple places. vibrator.vibrate(repeatingEffect); } void stopVibrating() { vibrator.cancel(); }
Esto es muy útil para eventos intermitentes que requieren una acción del usuario para confirmarlos. Algunos ejemplos de estos eventos incluyen llamadas telefónicas entrantes y alarmas activadas.
Ejemplo: Patrón con resguardo
El control de la amplitud de una vibración es una función que depende del hardware. Reproducir una forma de onda en un dispositivo de gama baja sin esta función hace que vibre a la amplitud máxima para cada entrada positiva en el array de amplitud. Si tu app necesita adaptarse a esos dispositivos, la recomendación es asegurarse de que tu patrón no genere un efecto de zumbido cuando se reproduzca en esa condición o diseñar un patrón de encendido/apagado más simple que se pueda reproducir como resguardo.
Kotlin
if (vibrator.hasAmplitudeControl()) { vibrator.vibrate(VibrationEffect.createWaveform(smoothTimings, amplitudes, smoothRepeatIdx)) } else { vibrator.vibrate(VibrationEffect.createWaveform(onOffTimings, onOffRepeatIdx)) }
Java
if (vibrator.hasAmplitudeControl()) { vibrator.vibrate(VibrationEffect.createWaveform(smoothTimings, amplitudes, smoothRepeatIdx)); } else { vibrator.vibrate(VibrationEffect.createWaveform(onOffTimings, onOffRepeatIdx)); }
Crea composiciones de vibración
En esta sección, se presentan formas de componerlos en efectos personalizados más largos y complejos, y va más allá para explorar la tecnología táctil enriquecida con capacidades de hardware más avanzadas. Puedes usar combinaciones de efectos que varían la amplitud y la frecuencia para crear efectos táctiles más complejos en dispositivos con actuadores táctiles que tienen un ancho de banda de frecuencia más amplio.
En el proceso para crear patrones de vibración personalizados, que se describió anteriormente en esta página, se explica cómo controlar la amplitud de vibración para crear efectos suaves de aumento y disminución. La tecnología Rich Haptics mejora este concepto explorando el rango de frecuencia más amplio del vibrador del dispositivo para que el efecto sea aún más fluido. Estas formas de onda son especialmente eficaces para crear un efecto crescendo o diminuendo.
El fabricante del dispositivo implementa las primitivas de composición, que se describieron anteriormente en esta página. Proporcionan una vibración nítida, breve y agradable que se alinea con los principios de la tecnología táctil para lograr una tecnología táctil clara. Para obtener más detalles sobre estas funciones y cómo funcionan, consulta el Instructivo sobre actuadores de vibración.
Android no proporciona resguardos para composiciones con primitivas no compatibles. Te recomendamos que sigas estos pasos:
Antes de activar la tecnología táctil avanzada, verifica que un dispositivo determinado admita todas las primitivas que usas.
Inhabilita el conjunto coherente de experiencias que no se admiten, no solo los efectos a los que les falta una primitiva. A continuación, se muestra más información para verificar la compatibilidad del dispositivo.
Puedes crear efectos de vibración compuestos con VibrationEffect.Composition
.
Este es un ejemplo de un efecto de aumento lento seguido de un efecto de clic nítido:
Kotlin
vibrator.vibrate( VibrationEffect.startComposition().addPrimitive( VibrationEffect.Composition.PRIMITIVE_SLOW_RISE ).addPrimitive( VibrationEffect.Composition.PRIMITIVE_CLICK ).compose() )
Java
vibrator.vibrate( VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) .compose());
Para crear una composición, se agregan primitivas que se reproducirán en secuencia. Cada primitiva también es escalable, por lo que puedes controlar la amplitud de la vibración que genera cada una de ellas. La escala se define como un valor entre 0 y 1, en el que 0 se asigna a una amplitud mínima a la que el usuario puede sentir (apenas) esta primitiva.
Si deseas crear una versión débil y una fuerte de la misma primitiva, se recomienda que las escalas difieran en una proporción de 1.4 o más, de modo que se pueda percibir fácilmente la diferencia de intensidad. No intentes crear más de tres niveles de intensidad de la misma primitiva, ya que no son distintos a nivel perceptual. Por ejemplo, usa escalas de 0.5, 0.7 y 1.0 para crear una versión de intensidad baja, media y alta de una primitiva.
La composición también puede especificar demoras que se agregarán entre primitivas consecutivas. Esta demora se expresa en milisegundos desde el final de la primitiva anterior. En general, una brecha de 5 a 10 ms entre dos primitivas es demasiado corta para ser detectable. Considera usar una brecha del orden de 50 ms o más si deseas crear una brecha discernible entre dos primitivas. Este es un ejemplo de una composición con retrasos:
Kotlin
val delayMs = 100 vibrator.vibrate( VibrationEffect.startComposition().addPrimitive( VibrationEffect.Composition.PRIMITIVE_SPIN, 0.8f ).addPrimitive( VibrationEffect.Composition.PRIMITIVE_SPIN, 0.6f ).addPrimitive( VibrationEffect.Composition.PRIMITIVE_THUD, 1.0f, delayMs ).compose() )
Java
int delayMs = 100; vibrator.vibrate( VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.8f) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.6f) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD, 1.0f, delayMs) .compose());
Las siguientes APIs se pueden usar para verificar la compatibilidad del dispositivo con primitivas específicas:
Kotlin
val primitive = VibrationEffect.Composition.PRIMITIVE_LOW_TICK if (vibrator.areAllPrimitivesSupported(primitive)) { vibrator.vibrate(VibrationEffect.startComposition().addPrimitive(primitive).compose()) } else { // Play a predefined effect or custom pattern as a fallback. }
Java
int primitive = VibrationEffect.Composition.PRIMITIVE_LOW_TICK; if (vibrator.areAllPrimitivesSupported(primitive)) { vibrator.vibrate(VibrationEffect.startComposition().addPrimitive(primitive).compose()); } else { // Play a predefined effect or custom pattern as a fallback. }
También es posible verificar varias primitivas y, luego, decidir cuáles compilar según el nivel de compatibilidad del dispositivo:
Kotlin
val effects: IntArray = intArrayOf( VibrationEffect.Composition.PRIMITIVE_LOW_TICK, VibrationEffect.Composition.PRIMITIVE_TICK, VibrationEffect.Composition.PRIMITIVE_CLICK ) val supported: BooleanArray = vibrator.arePrimitivesSupported(primitives);
Java
int[] primitives = new int[] { VibrationEffect.Composition.PRIMITIVE_LOW_TICK, VibrationEffect.Composition.PRIMITIVE_TICK, VibrationEffect.Composition.PRIMITIVE_CLICK }; boolean[] supported = vibrator.arePrimitivesSupported(effects);
Ejemplo: Resistencia (con marcas bajas)
Puedes controlar la amplitud de la vibración primitiva para transmitir comentarios útiles a una acción en curso. Los valores de escala espaciados pueden usarse para crear un efecto crescendo suave de una primitiva. La demora entre primitivos consecutivos también se puede establecer de forma dinámica según la interacción del usuario. Esto se ilustra en el siguiente ejemplo de una animación de vista controlada por un gesto de arrastre y mejorada con tecnología táctil.

Figura 1: Esta forma de onda representa la aceleración de salida de la vibración en un dispositivo.
Kotlin
@Composable fun ResistScreen() { // Control variables for the dragging of the indicator. var isDragging by remember { mutableStateOf(false) } var dragOffset by remember { mutableStateOf(0f) } // Only vibrates while the user is dragging if (isDragging) { LaunchedEffect(Unit) { // Continuously run the effect for vibration to occur even when the view // is not being drawn, when user stops dragging midway through gesture. while (true) { // Calculate the interval inversely proportional to the drag offset. val vibrationInterval = calculateVibrationInterval(dragOffset) // Calculate the scale directly proportional to the drag offset. val vibrationScale = calculateVibrationScale(dragOffset) delay(vibrationInterval) vibrator.vibrate( VibrationEffect.startComposition().addPrimitive( VibrationEffect.Composition.PRIMITIVE_LOW_TICK, vibrationScale ).compose() ) } } } Screen() { Column( Modifier .draggable( orientation = Orientation.Vertical, onDragStarted = { isDragging = true }, onDragStopped = { isDragging = false }, state = rememberDraggableState { delta -> dragOffset += delta } ) ) { // Build the indicator UI based on how much the user has dragged it. ResistIndicator(dragOffset) } } }
Java
class DragListener implements View.OnTouchListener { // Control variables for the dragging of the indicator. private int startY; private int vibrationInterval; private float vibrationScale; @Override public boolean onTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: startY = event.getRawY(); vibrationInterval = calculateVibrationInterval(0); vibrationScale = calculateVibrationScale(0); startVibration(); break; case MotionEvent.ACTION_MOVE: float dragOffset = event.getRawY() - startY; // Calculate the interval inversely proportional to the drag offset. vibrationInterval = calculateVibrationInterval(dragOffset); // Calculate the scale directly proportional to the drag offset. vibrationScale = calculateVibrationScale(dragOffset); // Build the indicator UI based on how much the user has dragged it. updateIndicator(dragOffset); break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: // Only vibrates while the user is dragging cancelVibration(); break; } return true; } private void startVibration() { vibrator.vibrate( VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, vibrationScale) .compose()); // Continuously run the effect for vibration to occur even when the view // is not being drawn, when user stops dragging midway through gesture. handler.postDelayed(this::startVibration, vibrationInterval); } private void cancelVibration() { handler.removeCallbacksAndMessages(null); } }
Ejemplo: Expandir (con aumento y disminución)
Hay dos primitivos para aumentar la intensidad de vibración percibida: PRIMITIVE_QUICK_RISE
y PRIMITIVE_SLOW_RISE
.
Ambos alcanzan el mismo objetivo, pero con duraciones diferentes. Solo hay una primitiva para reducir la velocidad, PRIMITIVE_QUICK_FALL
.
Estas primitivas funcionan mejor juntas para crear un segmento de forma de onda que crece en intensidad y, luego, desaparece. Puedes alinear primitivas ajustadas para evitar saltos repentinos en la amplitud entre ellas, lo que también funciona bien para extender la duración general del efecto. Perceptualmente, las personas siempre notan la parte ascendente más que la descendente, por lo que hacer que la parte ascendente sea más corta que la descendente se puede usar para cambiar el énfasis hacia la parte descendente.
Este es un ejemplo de una aplicación de esta composición para expandir y contraer un círculo. El efecto de aumento puede mejorar la sensación de expansión durante la animación. La combinación de efectos de aumento y disminución ayuda a enfatizar el colapso al final de la animación.

Figura 2: Esta forma de onda representa la aceleración de salida de la vibración en un dispositivo.
Kotlin
enum class ExpandShapeState { Collapsed, Expanded } @Composable fun ExpandScreen() { // Control variable for the state of the indicator. var currentState by remember { mutableStateOf(ExpandShapeState.Collapsed) } // Animation between expanded and collapsed states. val transitionData = updateTransitionData(currentState) Screen() { Column( Modifier .clickable( { if (currentState == ExpandShapeState.Collapsed) { currentState = ExpandShapeState.Expanded vibrator.vibrate( VibrationEffect.startComposition().addPrimitive( VibrationEffect.Composition.PRIMITIVE_SLOW_RISE, 0.3f ).addPrimitive( VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, 0.3f ).compose() ) } else { currentState = ExpandShapeState.Collapsed vibrator.vibrate( VibrationEffect.startComposition().addPrimitive( VibrationEffect.Composition.PRIMITIVE_SLOW_RISE ).compose() ) } ) ) { // Build the indicator UI based on the current state. ExpandIndicator(transitionData) } } }
Java
class ClickListener implements View.OnClickListener { private final Animation expandAnimation; private final Animation collapseAnimation; private boolean isExpanded; ClickListener(Context context) { expandAnimation = AnimationUtils.loadAnimation(context, R.anim.expand); expandAnimation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { vibrator.vibrate( VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE, 0.3f) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, 0.3f) .compose()); } }); collapseAnimation = AnimationUtils.loadAnimation(context, R.anim.collapse); collapseAnimation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { vibrator.vibrate( VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE) .compose()); } }); } @Override public void onClick(View view) { view.startAnimation(isExpanded ? collapseAnimation : expandAnimation); isExpanded = !isExpanded; } }
Ejemplo: Oscilación (con giros)
Uno de los principios táctiles clave es deleitar a los usuarios. Una forma divertida de introducir un efecto de vibración inesperado y agradable es usar PRIMITIVE_SPIN
.
Esta primitiva es más eficaz cuando se llama más de una vez. Varios giros concatenados pueden crear un efecto inestable y tambaleante, que se puede mejorar aún más aplicando una escala algo aleatoria en cada primitiva. También puedes experimentar con la brecha entre primitivas de giro sucesivas. Dos giros sin ningún espacio (0 ms de diferencia) crean una sensación de giro ajustado. Aumentar el intervalo entre giros de 10 a 50 ms genera una sensación de giro más suelta y se puede usar para que coincida con la duración de un video o una animación.
No recomendamos usar una brecha de más de 100 ms, ya que los giros sucesivos ya no se integran bien y comienzan a parecer efectos individuales.
Este es un ejemplo de una forma elástica que rebota después de arrastrarse hacia abajo y luego soltarse. La animación se mejora con un par de efectos de giro, que se reproducen con diferentes intensidades proporcionales al desplazamiento del rebote.

Figura 3: Esta forma de onda representa la aceleración de salida de la vibración en un dispositivo.
Kotlin
@Composable fun WobbleScreen() { // Control variables for the dragging and animating state of the elastic. var dragDistance by remember { mutableStateOf(0f) } var isWobbling by remember { mutableStateOf(false) } // Use drag distance to create an animated float value behaving like a spring. val dragDistanceAnimated by animateFloatAsState( targetValue = if (dragDistance > 0f) dragDistance else 0f, animationSpec = spring( dampingRatio = Spring.DampingRatioHighBouncy, stiffness = Spring.StiffnessMedium ), ) if (isWobbling) { LaunchedEffect(Unit) { while (true) { val displacement = dragDistanceAnimated / MAX_DRAG_DISTANCE // Use some sort of minimum displacement so the final few frames // of animation don't generate a vibration. if (displacement > SPIN_MIN_DISPLACEMENT) { vibrator.vibrate( VibrationEffect.startComposition().addPrimitive( VibrationEffect.Composition.PRIMITIVE_SPIN, nextSpinScale(displacement) ).addPrimitive( VibrationEffect.Composition.PRIMITIVE_SPIN, nextSpinScale(displacement) ).compose() ) } // Delay the next check for a sufficient duration until the current // composition finishes. Note that you can use // Vibrator.getPrimitiveDurations API to calculcate the delay. delay(VIBRATION_DURATION) } } } Box( Modifier .fillMaxSize() .draggable( onDragStopped = { isWobbling = true dragDistance = 0f }, orientation = Orientation.Vertical, state = rememberDraggableState { delta -> isWobbling = false dragDistance += delta } ) ) { // Draw the wobbling shape using the animated spring-like value. WobbleShape(dragDistanceAnimated) } } // Calculate a random scale for each spin to vary the full effect. fun nextSpinScale(displacement: Float): Float { // Generate a random offset in [-0.1,+0.1] to be added to the vibration // scale so the spin effects have slightly different values. val randomOffset: Float = Random.Default.nextFloat() * 0.2f - 0.1f return (displacement + randomOffset).absoluteValue.coerceIn(0f, 1f) }
Java
class AnimationListener implements DynamicAnimation.OnAnimationUpdateListener { private final Random vibrationRandom = new Random(seed); private final long lastVibrationUptime; @Override public void onAnimationUpdate(DynamicAnimation animation, float value, float velocity) { // Delay the next check for a sufficient duration until the current // composition finishes. Note that you can use // Vibrator.getPrimitiveDurations API to calculcate the delay. if (SystemClock.uptimeMillis() - lastVibrationUptime < VIBRATION_DURATION) { return; } float displacement = calculateRelativeDisplacement(value); // Use some sort of minimum displacement so the final few frames // of animation don't generate a vibration. if (displacement < SPIN_MIN_DISPLACEMENT) { return; } lastVibrationUptime = SystemClock.uptimeMillis(); vibrator.vibrate( VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN, nextSpinScale(displacement)) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN, nextSpinScale(displacement)) .compose()); } // Calculate a random scale for each spin to vary the full effect. float nextSpinScale(float displacement) { // Generate a random offset in [-0.1,+0.1] to be added to the vibration // scale so the spin effects have slightly different values. float randomOffset = vibrationRandom.nextFloat() * 0.2f - 0.1f return MathUtils.clamp(displacement + randomOffset, 0f, 1f) } }
Ejemplo: Rebote (con golpes)
Otra aplicación avanzada de los efectos de vibración es simular interacciones físicas. El elemento PRIMITIVE_THUD
puede crear un efecto fuerte y reverberante, que se puede combinar con la visualización de un impacto, por ejemplo, en un video o una animación, para mejorar la experiencia general.
A continuación, se muestra un ejemplo de una animación simple de caída de una pelota mejorada con un efecto de golpe que se reproduce cada vez que la pelota rebota en la parte inferior de la pantalla:

Figura 4: Esta forma de onda representa la aceleración de salida de la vibración en un dispositivo.
Kotlin
enum class BallPosition { Start, End } @Composable fun BounceScreen() { // Control variable for the state of the ball. var ballPosition by remember { mutableStateOf(BallPosition.Start) } var bounceCount by remember { mutableStateOf(0) } // Animation for the bouncing ball. var transitionData = updateTransitionData(ballPosition) val collisionData = updateCollisionData(transitionData) // Ball is about to contact floor, only vibrating once per collision. var hasVibratedForBallContact by remember { mutableStateOf(false) } if (collisionData.collisionWithFloor) { if (!hasVibratedForBallContact) { val vibrationScale = 0.7.pow(bounceCount++).toFloat() vibrator.vibrate( VibrationEffect.startComposition().addPrimitive( VibrationEffect.Composition.PRIMITIVE_THUD, vibrationScale ).compose() ) hasVibratedForBallContact = true } } else { // Reset for next contact with floor. hasVibratedForBallContact = false } Screen() { Box( Modifier .fillMaxSize() .clickable { if (transitionData.isAtStart) { ballPosition = BallPosition.End } else { ballPosition = BallPosition.Start bounceCount = 0 } }, ) { // Build the ball UI based on the current state. BouncingBall(transitionData) } } }
Java
class ClickListener implements View.OnClickListener { @Override public void onClick(View view) { view.animate() .translationY(targetY) .setDuration(3000) .setInterpolator(new BounceInterpolator()) .setUpdateListener(new AnimatorUpdateListener() { boolean hasVibratedForBallContact = false; int bounceCount = 0; @Override public void onAnimationUpdate(ValueAnimator animator) { boolean valueBeyondThreshold = (float) animator.getAnimatedValue() > 0.98; if (valueBeyondThreshold) { if (!hasVibratedForBallContact) { float vibrationScale = (float) Math.pow(0.7, bounceCount++); vibrator.vibrate( VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD, vibrationScale) .compose()); hasVibratedForBallContact = true; } } else { // Reset for next contact with floor. hasVibratedForBallContact = false; } } }); } }