Auf dieser Seite finden Sie Beispiele dafür, wie Sie mit verschiedenen Haptik-APIs benutzerdefinierte Effekte in einer Android-Anwendung erstellen. Da viele der Informationen auf dieser Seite auf einem guten Verständnis der Funktionsweise eines Vibrationsaktors beruhen, empfehlen wir Ihnen, den Einstieg in Vibrationsaktoren zu lesen.
Auf dieser Seite finden Sie die folgenden Beispiele.
- Benutzerdefinierte Vibrationsmuster
- Anlaufmuster: Ein Muster, das reibungslos beginnt.
- Wiederkehrendes Muster: Ein Muster ohne Ende.
- Muster mit Fallback: Eine Fallback-Demonstration.
- Vibrationszusammensetzungen
- Widerstand: Ein Zugeffekt mit dynamischer Intensität.
- Expand: Ein Effekt, bei dem der Ton ansteigt und dann wieder abfällt.
- Wobble: Ein wackelnder Effekt mit dem
SPIN
-Primitiven. - Bounce: Ein Sprungeffekt mit der
THUD
-Primitivform.
Weitere Beispiele finden Sie unter Ereignissen haptisches Feedback hinzufügen. Beachten Sie außerdem immer die Designprinzipien für Haptik.
Fallbacks zur Gerätekompatibilität verwenden
Beachten Sie bei der Implementierung benutzerdefinierter Effekte Folgendes:
- Welche Gerätefunktionen für den Effekt erforderlich sind
- Was tun, wenn das Gerät den Effekt nicht abspielen kann
In der Referenz für die Android-Haptik API erfahren Sie, wie Sie die Unterstützung für Komponenten prüfen, die für die Haptik Ihrer App relevant sind, damit sie eine einheitliche Nutzererfahrung bietet.
Je nach Anwendungsfall können Sie benutzerdefinierte Effekte deaktivieren oder alternative benutzerdefinierte Effekte basierend auf verschiedenen potenziellen Funktionen bereitstellen.
Berücksichtigen Sie die folgenden allgemeinen Klassen von Gerätefunktionen:
Bei Verwendung von haptischen Primitiven: Geräte, die die für die benutzerdefinierten Effekte erforderlichen Primitives unterstützen. Weitere Informationen zu Primitiven finden Sie im nächsten Abschnitt.
Geräte mit Amplitudenregelung
Geräte mit grundlegender Vibrationsunterstützung (An/Aus), d. h. ohne Amplitudenregelung.
Wenn die Auswahl der haptischen Effekte Ihrer App diese Kategorien berücksichtigt, sollte die haptische Nutzererfahrung für jedes einzelne Gerät vorhersehbar bleiben.
Haptische Primitive verwenden
Android bietet mehrere Haptik-Primitive, die sich sowohl in Amplitude als auch in Frequenz unterscheiden. Sie können eine Primitive allein oder mehrere Primitives in Kombination verwenden, um ausdrucksstarke haptische Effekte zu erzielen.
- Verwenden Sie Verzögerungen von mindestens 50 ms für erkennbare Lücken zwischen zwei Elementen. Berücksichtigen Sie dabei nach Möglichkeit auch die Dauer des Elements.
- Verwenden Sie Skalen, die sich um ein Verhältnis von mindestens 1,4 unterscheiden, damit der Unterschied in der Intensität besser wahrgenommen wird.
Verwenden Sie die Skalen 0,5, 0,7 und 1,0, um eine Version mit niedriger, mittlerer und hoher Intensität eines Primitives zu erstellen.
Benutzerdefinierte Vibrationsmuster erstellen
Vibrationsmuster werden häufig für haptische Warnungen verwendet, z. B. für Benachrichtigungen und Klingeltöne. Der Dienst Vibrator
kann lange Vibrationsmuster abspielen, bei denen sich die Vibrationsamplitude im Laufe der Zeit ändert. Solche Effekte werden als Wellenformen bezeichnet.
Wellenformeffekte sind leicht wahrnehmbar, aber plötzliche, lange Vibrationen können Nutzer erschrecken, wenn sie in einer ruhigen Umgebung abgespielt werden. Wenn die Zielamplitude zu schnell erreicht wird, kann es auch zu hörbaren Brummgeräuschen kommen. Beim Entwerfen von Wellenformmustern wird empfohlen, die Amplitudenübergänge zu glätten, um Ein- und Ausblendeffekte zu erzeugen.
Beispiel: Steigerungsmuster
Wellenformen werden als VibrationEffect
mit drei Parametern dargestellt:
- Timings:ein Array mit Dauern in Millisekunden für jedes Wellenformsegment.
- Amplituden:Die gewünschte Vibrationsamplitude für jede im ersten Argument angegebene Dauer, dargestellt als Ganzzahl zwischen 0 und 255. Dabei steht 0 für „Vibrator aus“ und 255 für die maximale Amplitude des Geräts.
- Wiederholindex:Der Index im im ersten Argument angegebenen Array, ab dem die Wellenform wiederholt werden soll, oder -1, wenn das Muster nur einmal wiedergegeben werden soll.
Hier ist eine Beispielwellenform, die zweimal mit einer Pause von 350 ms zwischen den Impulsen pulsiert. Der erste Impuls ist eine glatte Steigerung bis zur maximalen Amplitude und der zweite ist eine schnelle Steigerung, um die maximale Amplitude aufrechtzuerhalten. Das Ende wird durch den negativen Wert des Wiederholungsindexes definiert.
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));
Beispiel: Sich wiederholendes Muster
Sie können auch wiederholt abgespielt werden, bis sie abgebrochen werden. Um eine sich wiederholende Wellenform zu erstellen, müssen Sie einen nicht negativen Parameter „repeat“ festlegen. Wenn Sie eine sich wiederholende Wellenform abspielen, wird die Vibration so lange fortgesetzt, bis sie im Dienst explizit abgebrochen wird:
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(); }
Das ist sehr nützlich für gelegentliche Ereignisse, die eine Nutzeraktion erfordern, um sie zu bestätigen. Beispiele für solche Ereignisse sind eingehende Anrufe und ausgelöste Wecker.
Beispiel: Muster mit Fallback
Die Steuerung der Amplitude einer Vibration ist eine hardwareabhängige Funktion. Wenn Sie eine Wellenform auf einem Low-End-Gerät ohne diese Funktion abspielen, vibriert es für jeden positiven Eintrag im Amplitudenarray mit der maximalen Amplitude. Wenn Ihre App solche Geräte unterstützen muss, sollten Sie darauf achten, dass Ihr Muster in diesem Zustand keinen Brummeffekt erzeugt, oder ein einfacheres EIN/AUS-Muster entwerfen, das stattdessen als Fallback wiedergegeben werden kann.
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)); }
Vibrationskompositionen erstellen
In diesem Abschnitt erfahren Sie, wie Sie sie zu längeren und komplexeren benutzerdefinierten Effekten zusammenstellen. Außerdem werden erweiterte Hardwarefunktionen für reichhaltige Haptik vorgestellt. Sie können Kombinationen von Effekten verwenden, die Amplitude und Frequenz variieren, um komplexere haptische Effekte auf Geräten mit haptischen Aktuatoren mit einer breiteren Frequenzbandbreite zu erzeugen.
Im Abschnitt zum Erstellen benutzerdefinierter Vibrationsmuster auf dieser Seite wird beschrieben, wie Sie die Vibrationsamplitude steuern, um eine gleichmäßige Steigerung und Abnahme der Vibration zu erzielen. Die Funktion „Rich Haptics“ verbessert dieses Konzept, indem der breitere Frequenzbereich des Vibrationsmotors genutzt wird, um den Effekt noch weicher zu machen. Diese Wellenformen eignen sich besonders gut, um einen Crescendo- oder Diminuendo-Effekt zu erzeugen.
Die Kompositionsgrundlagen, die weiter oben auf dieser Seite beschrieben wurden, werden vom Gerätehersteller implementiert. Sie bieten eine knackige, kurze und angenehme Vibration, die den Haptikprinzipien für eine klare Haptik entspricht. Weitere Informationen zu diesen Funktionen und ihrer Funktionsweise finden Sie im Einführungsleitfaden für Vibrationsaktoren.
Android bietet keine Fallbacks für Kompositionen mit nicht unterstützten Primitiven. Wir empfehlen Folgendes:
Bevor Sie die erweiterten Haptikfunktionen aktivieren, prüfen Sie, ob ein bestimmtes Gerät alle von Ihnen verwendeten Primitive unterstützt.
Deaktivieren Sie alle nicht unterstützten Funktionen, nicht nur die Effekte, für die ein Primitive fehlt. Weitere Informationen dazu, wie du die Unterstützung deines Geräts prüfen kannst, findest du unten.
Mit VibrationEffect.Composition
können Sie zusammengesetzte Vibrationseffekte erstellen.
Hier ein Beispiel für einen langsam ansteigenden Effekt, gefolgt von einem scharfen Klickeffekt:
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());
Eine Komposition wird erstellt, indem Primitive hinzugefügt werden, die in einer bestimmten Reihenfolge abgespielt werden sollen. Jede Primitive ist außerdem skalierbar, sodass Sie die Amplitude der von ihnen erzeugten Vibration steuern können. Die Skala wird als Wert zwischen 0 und 1 definiert, wobei 0 einer minimalen Amplitude entspricht, bei der dieses Element vom Nutzer (kaum) wahrgenommen werden kann.
Wenn Sie eine schwache und eine starke Version desselben Primitives erstellen möchten, sollten sich die Skalen im Verhältnis von mindestens 1,4 unterscheiden, damit der Unterschied in der Intensität leicht wahrgenommen werden kann. Versuchen Sie nicht, mehr als drei Intensitätsstufen desselben Primitives zu erstellen, da sie nicht wahrnehmbar voneinander zu unterscheiden sind. Verwenden Sie beispielsweise die Skalen 0,5, 0,7 und 1,0, um eine Version mit niedriger, mittlerer und hoher Intensität eines Primitives zu erstellen.
In der Komposition können auch Verzögerungen zwischen aufeinanderfolgenden Primitiven angegeben werden. Diese Verzögerung wird in Millisekunden seit dem Ende des vorherigen Primitives angegeben. Im Allgemeinen ist eine Lücke von 5 bis 10 ms zwischen zwei Primitiven zu kurz, um erkannt zu werden. Verwenden Sie eine Lücke von etwa 50 ms oder länger, wenn Sie zwischen zwei Elementen eine deutlich sichtbare Lücke schaffen möchten. Hier ein Beispiel für eine Komposition mit Verzögerungen:
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());
Mit den folgenden APIs können Sie die Geräteunterstützung für bestimmte Primitive prüfen:
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. }
Es ist auch möglich, mehrere Primitive zu prüfen und dann basierend auf der Geräteunterstützungsebene zu entscheiden, welche davon erstellt werden sollen:
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);
Beispiel: Widerstand (mit wenigen Zählern)
Sie können die Amplitude der primitiven Vibration steuern, um nützliches Feedback zu einer laufenden Aktion zu geben. Mit eng beieinander liegenden Skalenwerten lässt sich ein glatter Crescendo-Effekt eines Primitives erzeugen. Die Verzögerung zwischen aufeinanderfolgenden Primitiven kann auch dynamisch basierend auf der Nutzerinteraktion festgelegt werden. Das folgende Beispiel einer Ansichtsanimation, die durch eine Ziegesten gesteuert und mit Haptik ergänzt wird, veranschaulicht dies.

Abbildung 1: Diese Wellenform stellt die Ausgabebeschleunigung der Vibration auf einem Gerät dar.
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); } }
Beispiel: Maximieren (mit Steigerung und Abfall)
Es gibt zwei Primitive, mit denen die wahrgenommene Vibrationsintensität erhöht werden kann: PRIMITIVE_QUICK_RISE
und PRIMITIVE_SLOW_RISE
.
Beide erreichen dasselbe Ziel, aber mit unterschiedlicher Dauer. Es gibt nur ein Primitive für die Abnahme, das PRIMITIVE_QUICK_FALL
.
Diese Primitive eignen sich gut, um ein Wellenformsegment zu erstellen, das an Intensität zunimmt und dann wieder abnimmt. Sie können skalierte Primitive ausrichten, um plötzliche Amplitudensprünge zwischen ihnen zu vermeiden. Das eignet sich auch gut, um die Gesamtdauer des Effekts zu verlängern. Menschen nehmen den steigenden Teil immer stärker wahr als den fallenden Teil. Wenn Sie den steigenden Teil kürzer als den fallenden Teil gestalten, können Sie den Schwerpunkt auf den fallenden Teil verlagern.
Hier sehen Sie ein Beispiel für die Anwendung dieser Komposition zum Maximieren und Minimieren eines Kreises. Der Aufwärtseffekt kann das Gefühl der Ausdehnung während der Animation verstärken. Die Kombination aus Auf- und Abstiegseffekten unterstreicht das Zusammenfallen am Ende der Animation.

Abbildung 2: Diese Wellenform stellt die Ausgabebeschleunigung der Vibration auf einem Gerät dar.
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; } }
Beispiel: Wobble (mit Drehungen)
Eines der wichtigsten Haptikprinzipien besteht darin, die Nutzer zu begeistern. Mit der PRIMITIVE_SPIN
können Sie einen angenehmen, unerwarteten Vibrationseffekt einfügen.
Dieses Primitive ist am effektivsten, wenn es mehrmals aufgerufen wird. Mehrere zusammengesetzte Drehungen können einen wackeligen und instabilen Effekt erzeugen, der durch eine etwas zufällige Skalierung der einzelnen Primitivformen noch verstärkt werden kann. Sie können auch mit dem Abstand zwischen aufeinanderfolgenden Spin-Primitiven experimentieren. Zwei Umdrehungen ohne Lücke (0 ms dazwischen) sorgen für ein flüssiges Drehgefühl. Wenn Sie die Pause zwischen den einzelnen Umdrehungen von 10 auf 50 ms erhöhen, wirkt sich das auf die Drehung aus. Sie können die Pause auch so einstellen, dass sie der Dauer eines Videos oder einer Animation entspricht.
Wir empfehlen, keine Lücke von mehr als 100 ms zu verwenden, da sich die aufeinanderfolgenden Drehungen sonst nicht gut ineinanderfügen und wie einzelne Effekte wirken.
Hier ist ein Beispiel für eine elastische Form, die nach dem Ziehen nach unten wieder in ihre Ausgangsposition zurückkehrt. Die Animation wird durch zwei Dreheffekte ergänzt, die mit unterschiedlicher Intensität abgespielt werden, die der Sprungverschiebung proportional ist.

Abbildung 3: Diese Wellenform stellt die Ausgabebeschleunigung der Vibration auf einem Gerät dar.
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) } }
Beispiel: Nachhall (mit dumpfem Geräusch)
Eine weitere erweiterte Anwendung von Vibrationseffekten ist die Simulation physischer Interaktionen. Die PRIMITIVE_THUD
kann einen starken und nachhallenden Effekt erzeugen, der mit der Visualisierung eines Aufpralls kombiniert werden kann, z. B. in einem Video oder einer Animation, um den Gesamteindruck zu verstärken.
Hier ist ein Beispiel für eine einfache Animation, bei der ein Ball nach unten fällt und jedes Mal ein dumpfer Schlag ertönt, wenn der Ball auf den Boden des Bildschirms aufprallt:

Abbildung 4: Diese Wellenform stellt die Ausgabebeschleunigung der Vibration auf einem Gerät dar.
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; } } }); } }