Questa pagina illustra gli esempi di utilizzo di diverse API di aptica per creare effetti personalizzati in un'applicazione per Android. Poiché gran parte delle informazioni su questa pagina si basa su una buona conoscenza del funzionamento di un attuatore a vibrazione, consigliamo di leggere la guida introduttiva agli attuatori a vibrazione.
Questa pagina include i seguenti esempi.
- Modelli di vibrazione personalizzati
- Schema di aumento graduale: uno schema che inizia in modo graduale.
- Pattern ripetuto: un pattern senza fine.
- Pattern con elemento di riserva: una dimostrazione di un elemento di riserva.
- Composizioni di vibrazioni
- Resistenza: un effetto di trascinamento con intensità dinamica.
- Espandi: un effetto di aumento e poi di calo.
- Dondolio: un effetto tremolo che utilizza la primitiva
SPIN
. - Bounce: un effetto di rimbalzo che utilizza la primitiva
THUD
.
Per altri esempi, vedi Aggiungere il feedback aptico agli eventi e segui sempre i principi di progettazione aptica.
Utilizzare i valori di riserva per gestire la compatibilità dei dispositivi
Quando implementi un effetto personalizzato, tieni presente quanto segue:
- Quali funzionalità del dispositivo sono richieste per l'effetto
- Cosa fare quando il dispositivo non è in grado di riprodurre l'effetto
La pagina di riferimento dell'API Android Haptics fornisce dettagli su come verificare il supporto dei componenti coinvolti nel feedback aptico, in modo che la tua app possa offrire un'esperienza complessiva coerente.
A seconda del caso d'uso, potresti voler disattivare gli effetti personalizzati o fornire effetti personalizzati alternativi in base a potenziali funzionalità diverse.
Pianifica le seguenti classi di funzionalità dei dispositivi di alto livello:
Se utilizzi primitive aptica: i dispositivi che supportano queste primitive necessarie per gli effetti personalizzati. (Per dettagli sulle primitive, consulta la sezione successiva).
Dispositivi con controllo dell'ampiezza.
Dispositivi con supporto di base della vibrazione (on/off), in altre parole quelli che non dispongono del controllo dell'ampiezza.
Se la scelta dell'effetto aptico della tua app tiene conto di queste categorie, la sua esperienza utente aptica dovrebbe rimanere prevedibile per ogni singolo dispositivo.
Utilizzo di primitive aptica
Android include diverse primitive di aptica che variano sia in termini di ampiezza che di frequenza. Puoi utilizzare una primitiva da sola o più primitive in combinazione per ottenere effetti tattili avanzati.
- Utilizza ritardi di almeno 50 ms per intervalli distinguibili tra due primitive, tenendo conto anche della durata della primitiva, se possibile.
- Utilizza scale che differiscono per un rapporto di 1,4 o più, in modo che la differenza di intensità sia percepita meglio.
Utilizza le scale 0,5, 0,7 e 1,0 per creare una versione di intensità bassa, media e alta di una primitiva.
Creare pattern di vibrazione personalizzati
Gli schemi di vibrazione vengono spesso utilizzati nel feedback aptico per attirare l'attenzione, ad esempio per le notifiche e le suonerie. Il servizio Vibrator
può riprodurre pattern di vibrazione lunghi che cambiano l'ampiezza della vibrazione nel tempo. Questi effetti sono chiamati forme d'onda.
Gli effetti di forma d'onda possono essere facilmente percepiti, ma vibrazioni lunghe e improvvise possono sorprendere l'utente se riprodotti in un ambiente silenzioso. Anche un aumento troppo rapido dell'ampiezza target può produrre ronzii udibili. Per la progettazione di pattern di forme d'onda, ti consigliamo di smussare le transizioni di ampiezza per creare effetti di aumento e diminuzione.
Esempio: modello di applicazione graduale
Le forme d'onda sono rappresentate come VibrationEffect
con tre parametri:
- Tempi:un array di durate, in millisecondi, per ogni segmento della forma d'onda.
- Ampitùdi:l'ampiezza della vibrazione desiderata per ogni durata specificata nel primo argomento, rappresentata da un valore intero compreso tra 0 e 255, dove 0 rappresenta il vibratore "off" e 255 è l'ampiezza massima del dispositivo.
- Indice di ripetizione:l'indice nell'array specificato nel primo argomento per iniziare a ripetere la forma d'onda o -1 se il pattern deve essere riprodotto una sola volta.
Ecco un esempio di forma d'onda che pulsa due volte con una pausa di 350 ms tra un impulso e l'altro. Il primo impulso è una rampa graduale fino all'ampiezza massima, mentre il secondo è una rampa rapida per mantenere l'ampiezza massima. L'interruzione alla fine è definita dal valore dell'indice di ripetizione negativo.
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));
Esempio: schema ripetitivo
Le forme d'onda possono anche essere riprodotte ripetutamente finché non vengono annullate. Per creare un'oscillazione ripetuta, imposta un parametro "repeat" non negativo. Quando riproduci una forma d'onda ripetuta, la vibrazione continua finché non viene annullata esplicitamente nel servizio:
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(); }
Questa opzione è molto utile per gli eventi intermittenti che richiedono un'azione dell'utente per confermarli. Esempi di questi eventi includono chiamate in arrivo e allarmi attivati.
Esempio: pattern con elemento di riserva
Il controllo dell'ampiezza di una vibrazione è una funzionalità dipendente dall'hardware. La riproduzione di una forma d'onda su un dispositivo di fascia bassa senza questa funzionalità fa sì che vibri all'ampiezza massima per ogni voce positiva nell'array di ampiezza. Se la tua app deve supportare questi dispositivi, ti consigliamo di assicurarti che il pattern non generi un effetto di ronzio quando viene riprodotto in queste condizioni oppure di progettare un pattern ON/OFF più semplice che possa essere riprodotto come opzione di riserva.
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)); }
Creare composizioni di vibrazioni
Questa sezione illustra i modi per combinarli in effetti personalizzati più lunghi e complessi e va oltre per esplorare la tecnologia haptics avanzata utilizzando funzionalità hardware più avanzate. Puoi utilizzare combinazioni di effetti che variano l'ampiezza e la frequenza per creare effetti aptico più complessi su dispositivi con attuatori aptico che hanno una larghezza di banda di frequenza più ampia.
La procedura per la creazione di pattern di vibrazione personalizzati, descritta in precedenza in questa pagina, spiega come controllare l'ampiezza della vibrazione per creare effetti graduali di aumento e diminuzione. La tecnologia Rich Haptics perfeziona questo concetto esplorando la gamma di frequenza più ampia del vibratore del dispositivo per rendere l'effetto ancora più fluido. Queste forme d'onda sono particolarmente efficaci per creare un effetto di crescendo o diminuendo.
Le primitive di composizione, descritte in precedenza in questa pagina, vengono implementate dal produttore del dispositivo. Offrono una vibrazione chiara, breve e piacevole in linea con i principi di aptica per un'esperienza aptica chiara. Per maggiori dettagli su queste funzionalità e sul loro funzionamento, consulta la guida introduttiva agli attuatori vibranti.
Android non fornisce soluzioni alternative per le composizioni con primitive non supportate. Ti consigliamo di eseguire i seguenti passaggi:
Prima di attivare la tecnologia aptica avanzata, verifica che un determinato dispositivo supporti tutte le primitive che stai utilizzando.
Disattiva l'insieme coerente di esperienze non supportate, non solo gli effetti a cui manca un elemento primitivo. Di seguito sono riportate ulteriori informazioni su come controllare l'assistenza del dispositivo.
Puoi creare effetti di vibrazione composti con VibrationEffect.Composition
.
Ecco un esempio di un effetto che aumenta lentamente seguito da un effetto clic netto:
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());
Una composizione viene creata aggiungendo primitive da riprodurre in sequenza. Ogni primitivo è inoltre scalabile, quindi puoi controllare l'ampiezza della vibrazione generata da ciascuno. La scala è definita come un valore compreso tra 0 e 1, dove 0 corrisponde a un'ampiezza minima alla quale questa primitiva può essere percepita (a malapena) dall'utente.
Se vuoi creare una versione debole e una forte della stessa primitiva, è consigliabile che le scale differiscano di una proporzione pari o superiore a 1,4, in modo che la differenza di intensità sia facilmente percepibile. Non provare a creare più di tre livelli di intensità della stessa primitiva, perché non sono distinguibili percettivamente. Ad esempio, utilizza scale di 0,5, 0,7 e 1,0 per creare una versione a bassa, media e alta intensità di una primitiva.
La composizione può anche specificare i ritardi da aggiungere tra le primitive consecutive. Questo ritardo è espresso in millisecondi dalla fine del primitivo precedente. In genere, un intervallo di 5-10 ms tra due primitive è troppo breve per essere rilevato. Valuta la possibilità di utilizzare un intervallo dell'ordine di 50 ms o più se vuoi creare un intervallo distinguibile tra due primitive. Ecco un esempio di composizione con ritardi:
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());
Per verificare il supporto del dispositivo per primitive specifiche, puoi utilizzare le seguenti API:
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. }
È anche possibile controllare più primitive e decidere quali comporre in base al livello di supporto 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);
Esempio: resist (con tick bassi)
Puoi controllare l'ampiezza della vibrazione primitiva per trasmettere un feedback utile su un'azione in corso. I valori di scala con spaziatura ravvicinata possono essere utilizzati per creare un effetto di crescendo graduale di una primitiva. Anche il ritardo tra primitive consecutive può essere impostato in modo dinamico in base all'interazione dell'utente. Questo è illustrato nell'esempio seguente di animazione della vista controllata da un gesto di trascinamento e aumentata con la tecnologia aptica.

Figura 1. Questa forma d'onda rappresenta l'accelerazione in uscita della vibrazione su 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); } }
Esempio: Espandi (con aumento e calo)
Esistono due primitive per aumentare l'intensità della vibrazione percepita: PRIMITIVE_QUICK_RISE
e
PRIMITIVE_SLOW_RISE
.
Entrambi raggiungono lo stesso target, ma con durate diverse. Esiste un'unica primitiva per la riduzione graduale, PRIMITIVE_QUICK_FALL
.
Queste primitive funzionano meglio insieme per creare un segmento di forma d'onda che aumenta di intensità e poi si attenua. Puoi allineare le primitive scalate per evitare salti improvvisi di ampiezza tra di loro, il che è utile anche per estendere la durata complessiva dell'effetto. Percettivamente, le persone notano sempre di più la parte in aumento rispetto alla parte in calo, quindi rendere la parte in aumento più breve di quella in calo può essere utilizzata per spostare l'attenzione verso la parte in calo.
Ecco un esempio di applicazione di questa composizione per espandere e comprimere un cerchio. L'effetto di aumento può migliorare la sensazione di espansione durante l'animazione. La combinazione di effetti di aumento e diminuzione contribuisce a sottolineare il collapse alla fine dell'animazione.

Figura 2. Questa forma d'onda rappresenta l'accelerazione in uscita della vibrazione su 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; } }
Sample: oscillazione (con rotazioni)
Uno dei principi fondamentali dell'aptica è soddisfare gli utenti. Un modo divertente per introdurre un piacevole effetto di vibrazione inaspettato è utilizzare il PRIMITIVE_SPIN
.
Questa primitiva è più efficace se viene chiamata più volte. Più spin concatenati possono creare un effetto traballante e instabile, che può essere ulteriormente migliorato applicando una scalabilità un po' casuale a ogni primitiva. Puoi anche sperimentare l'intervallo tra le primitive di spin successive. Due rotazioni senza spazi (0 ms tra una e l'altra) creano una sensazione di rotazione rapida. Aumentare
la distanza tra le rotazioni da 10 a 50 ms genera una sensazione di rotazione più lenta e
puoi essere utilizzata per adattarsi alla durata di un video o di un'animazione.
Non consigliamo di utilizzare un intervallo superiore a 100 ms, poiché le rotazioni successive non si integrano più bene e iniziano a sembrare effetti individuali.
Ecco un esempio di forma elastica che torna indietro dopo essere stata trascinata verso il basso e poi rilasciata. L'animazione è migliorata con una coppia di effetti di rotazione, riprodotti con intensità diverse proporzionali allo spostamento del rimbalzo.

Figura 3. Questa forma d'onda rappresenta l'accelerazione in uscita della vibrazione su 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) } }
Esempio: rimbalzo (con colpi sordi)
Un'altra applicazione avanzata degli effetti di vibrazione è simulare le interazioni fisiche. PRIMITIVE_THUD
può creare un effetto forte e risonante, che può essere abbinato alla visualizzazione di un impatto, ad esempio in un video o un'animazione, per migliorare l'esperienza complessiva.
Ecco un esempio di una semplice animazione di caduta di una palla migliorata con un effetto thud interpretato ogni volta che la palla rimbalza sul fondo dello schermo:

Figura 4. Questa forma d'onda rappresenta l'accelerazione in uscita della vibrazione su 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; } } }); } }