Na tej stronie znajdziesz przykłady korzystania z różnych interfejsów API dotykowych do tworzenia efektów niestandardowych w aplikacji na Androida. Większość informacji na tej stronie wymaga dobrej znajomości działania siłownika wibracyjnego. Zalecamy zapoznanie się z informacjami o siłowniku wibracyjnym.
Na tej stronie znajdziesz te przykłady:
- Niestandardowe wzorce wibracji
- Wzrost głośności: wzór, który zaczyna się płynnie.
- Powtarzający się wzór: wzór bez końca.
- Wzór z zastępczą wersją: demonstracja zastępczej wersji.
- Kompozycje wibracyjne
- Resist: efekt oporu z dynamiczną intensywnością.
- Rozwiń: efekt wznoszenia się i opadania.
- Wobble: efekt kołysania za pomocą prymitywu
SPIN
. - Odbijanie: efekt odbijania za pomocą prymitywu
THUD
.
Więcej przykładów znajdziesz w artykule Dodawanie wibracji do zdarzeń. Pamiętaj, aby zawsze przestrzegać zasad projektowania haptycznego.
Zastosowanie rozwiązań alternatywnych do obsługi zgodności urządzeń
Podczas wdrażania efektu niestandardowego weź pod uwagę te kwestie:
- Które funkcje urządzenia są wymagane do działania
- Co zrobić, gdy urządzenie nie może odtworzyć efektu
W dokumentacji interfejsu API haptycznych funkcji Androida znajdziesz szczegółowe informacje o tym, jak sprawdzić, czy komponenty używane w Twoich funkcjach haptycznych są obsługiwane, aby aplikacja mogła zapewnić spójne wrażenia.
W zależności od przypadku użycia możesz wyłączyć efekty niestandardowe lub udostępnić alternatywne efekty niestandardowe na podstawie różnych potencjalnych możliwości.
Zaplanuj te ogólne klasy możliwości urządzeń:
Jeśli używasz haptycznych elementów: urządzeń obsługujących te elementy potrzebne do efektów niestandardowych. (szczegółowe informacje o elementach znajdziesz w następnej sekcji).
Urządzenia z kontrolą amplitudy.
Urządzenia z podstawową obsługą wibracji (włącz/wyłącz) – innymi słowy, takie, które nie mają kontroli amplitudy.
Jeśli w aplikacji uwzględniono te kategorie, użytkownik powinien mieć przewidywalne wrażenia haptyczne na każdym urządzeniu.
Używanie prymitywnych elementów haptycznych
Android zawiera kilka prostych efektów haptycznych, które różnią się amplitudą i częstotliwością. Aby uzyskać bogate efekty haptyczne, możesz użyć jednego prymitywu lub wielu prymitywów w połączeniu.
- Używaj opóźnień wynoszących co najmniej 50 ms w przypadku zauważalnych przerw między dwoma elementami, biorąc pod uwagę czas trwania elementu (jeśli to możliwe).
- Używaj skal, które różnią się o współczynnik 1,4 lub więcej, aby lepiej odróżnić intensywność.
Użyj skali 0,5, 0,7 i 1,0, aby utworzyć wersję prymitywu o niskiej, średniej i wysokiej intensywności.
Tworzenie niestandardowych wzorców wibracji
W reakcji haptycznej na zwrócenie uwagi często stosuje się wzorce wibracji, np. w przypadku powiadomień i dzwonów. Usługa Vibrator
może odtwarzać długie wzorce wibracji, które zmieniają amplitudę wibracji w czasie. Takie efekty nazywamy formami fali.
Efekty fal są łatwo dostrzegalne, ale nagłe, długie wibracje mogą przestraszyć użytkownika, jeśli zostaną odtworzone w cichym otoczeniu. Zbyt szybkie zwiększanie amplitudy docelowej może też powodować słyszalne szumy. Zalecamy, aby przy projektowaniu wzorów fali wygładzić przejścia amplitudy, aby uzyskać efekt przyspieszania i spowolniania.
Przykład: wzór ramp-up
Faloprzebiegi są reprezentowane jako VibrationEffect
z 3 parametrami:
- Timings: tablica z czasami trwania (w milisekundach) poszczególnych segmentów fali dźwiękowej.
- Amplitudy: żądana amplituda wibracji dla każdego czasu określonego w pierwszym argumencie, reprezentowana przez wartość całkowitą z zakresu 0–255, przy czym 0 oznacza „wyłączony” wibrator, a 255 to maksymalna amplituda urządzenia.
- Indeks powtórzeń: indeks w tablicy podany w pierwszym argumencie, aby rozpocząć powtarzanie fali dźwiękowej lub -1, jeśli ma ona być odtwarzana tylko raz.
Oto przykład fali, która pulsuje dwukrotnie z przerwą 350 ms. Pierwszy impuls to płynne zwiększanie amplitudy do maksimum, a drugi to szybkie zwiększanie amplitudy do jej maksymalnej wartości. Zatrzymanie na końcu jest zdefiniowane przez ujemną wartość indeksu powtórzenia.
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));
Przykład: powtarzający się wzór
Falowniki można też odtwarzać wielokrotnie, dopóki nie zostaną anulowane. Aby utworzyć powtarzającą się krzywą, ustaw nieujemny parametr „repeat”. Podczas odtwarzania powtarzającej się fali wibracyjnej wibracje będą trwać, dopóki nie zostaną wyraźnie anulowane w usłudze:
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(); }
Jest to bardzo przydatne w przypadku sporadycznych zdarzeń, które wymagają potwierdzenia przez użytkownika. Przykłady takich zdarzeń to na przykład połączenia przychodzące i wyzwalane alarmy.
Przykład: wzorzec z zastępczym
Regulowanie amplitudy wibracji to funkcja zależna od sprzętu. Odtwarzanie fali na urządzeniu niskobudżetowym bez tej funkcji powoduje wibrowanie przy maksymalnej amplitudzie dla każdego dodatniego wpisu w tablicy amplitudy. Jeśli Twoja aplikacja musi obsługiwać takie urządzenia, zalecamy, aby wzór nie generował efektu brzęczenia podczas odtwarzania w takich warunkach. Możesz też zaprojektować prostszy wzór włączania/wyłączania, który będzie odtwarzany jako alternatywa.
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)); }
Tworzenie kompozycji wibracji
W tej sekcji znajdziesz sposoby na tworzenie dłuższych i bardziej złożonych efektów niestandardowych. Dowiesz się też, jak korzystać z zaawansowanych funkcji sprzętowych, aby tworzyć bardziej zaawansowane efekty haptyczne. Aby tworzyć bardziej złożone efekty haptyczne na urządzeniach z silnikami haptycznymi o szerszym paśmie częstotliwości, możesz używać kombinacji efektów, które zmieniają amplitudę i częstotliwość.
Proces tworzenia niestandardowych wzorców wibracji, opisany wcześniej na tej stronie, wyjaśnia, jak kontrolować amplitudę wibracji, aby uzyskać płynne efekty wzmacniania i osłabiania wibracji. Ulepszona haptyka udoskonala tę koncepcję, wykorzystując szerszy zakres częstotliwości wibratora urządzenia, aby efekt był jeszcze bardziej płynny. Te przebiegi fali są szczególnie skuteczne w tworzeniu efektu crescendo lub diminuendo.
Primitive komponentów, opisane wcześniej na tej stronie, są implementowane przez producenta urządzenia. Zapewniają wyraźne, krótkie i przyjemne wibracje, które są zgodne z zasadami haptyki zapewniającymi wyraźne działanie. Więcej informacji o tych funkcjach i sposobie ich działania znajdziesz w artykule Wprowadzenie do siłowników wibracyjnych.
Android nie zapewnia alternatywnych rozwiązań w przypadku kompozycji z nieobsługiwanymi prymitywami. Wykonaj te czynności:
Zanim aktywujesz zaawansowane funkcje haptyczne, sprawdź, czy dane urządzenie obsługuje wszystkie używane przez Ciebie prymitywy.
Wyłącz spójny zestaw funkcji, które nie są obsługiwane, a nie tylko efekty, w których brakuje prymitywu. Więcej informacji o tym, jak sprawdzić, czy urządzenie jest obsługiwane, znajdziesz poniżej.
Za pomocą VibrationEffect.Composition
możesz tworzyć złożone efekty wibracji.
Oto przykład efektu powolnego wzrostu, po którym następuje efekt głośnego kliknięcia:
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());
Kompozycja jest tworzona przez dodawanie prymitywów, które mają być odtwarzane sekwencyjnie. Każdy obiekt prosty jest też skalowalny, więc możesz kontrolować amplitudę wibracji generowanych przez każdy z nich. Skala jest zdefiniowana jako wartość od 0 do 1, gdzie 0 odpowiada minimalnej amplitudzie, przy której użytkownik może (ledwie) odczuć ten prymityw.
Jeśli chcesz utworzyć słabszą i silniejszą wersję tego samego prymitywu, zalecamy, aby skale różniły się o 1,4 lub więcej, aby różnica w intensywności była łatwo zauważalna. Nie próbuj tworzyć więcej niż 3 poziomów intensywności tego samego prymitywu, ponieważ nie są one od siebie wizualnie odróżnialne. Użyj na przykład skali 0,5, 0,7 i 1,0, aby utworzyć wersję prymitywu o niskiej, średniej i wysokiej intensywności.
Kompozycja może też określać opóźnienia dodawane między kolejnymi elementami. To opóźnienie jest wyrażone w milisekundach od końca poprzedniego prymitywu. Zazwyczaj odstęp między dwoma prymitywami wynoszący 5–10 ms jest zbyt krótki, aby można go było wykryć. Jeśli chcesz utworzyć wyraźną przerwę między dwoma prymitywami, rozważ użycie przerwy o długości około 50 ms lub dłuższej. Oto przykład kompozycji z opóźnieniami:
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());
Aby sprawdzić, czy dane urządzenie obsługuje określone prymitywy, możesz użyć tych interfejsów 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. }
Możesz też sprawdzić wiele prymitywów, a potem zdecydować, które z nich użyć na podstawie poziomu obsługi przez urządzenie:
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);
Przykład: opór (z niskim poziomem mocy)
Możesz kontrolować amplitudę wibracji prymitywnych, aby przekazywać przydatne informacje o działaniu w trakcie. Wartości skali o zbliżonych wartościach można stosować do tworzenia płynnego efektu crescendo prymity. Opóźnienie między kolejnymi elementami prostymi można też ustawiać dynamicznie na podstawie interakcji z użytkownikiem. Pokazuje to poniższy przykład animacji widoku sterowanej gestem przeciągania i wzbogaconej funkcją haptyczną.

Rysunek 1. Ta forma fali reprezentuje przyspieszenie wyjściowe wibracji na urządzeniu.
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); } }
Przykład: rozwinięcie (z efektem wznoszenia i opadania)
Istnieją 2 elementy proste, które zwiększają odczuwaną intensywność wibracji: PRIMITIVE_QUICK_RISE
i PRIMITIVE_SLOW_RISE
.
Oba mają ten sam cel, ale różnią się czasem trwania. Do zmniejszania ścieżki jest tylko jeden element: PRIMITIVE_QUICK_FALL
.
Te prymity lepiej współpracują ze sobą, tworząc segment fali, który narasta, a potem zanika. Możesz dopasować skalowane prymity, aby zapobiec nagłym skokom amplitudy między nimi. Pozwoli to również wydłużyć czas trwania efektu. Z punktu widzenia percepcji ludzie zawsze bardziej zauważają część wznoszącą niż opadającą, więc skrócenie części wznoszącej w stosunku do części opadającej może służyć do przesunięcia akcentu na część opadającą.
Oto przykład zastosowania tej kompozycji do rozszerzania i zwijania koła. Efekt wzrostu może wzmocnić wrażenie rozszerzania się podczas animacji. Połączenie efektów wzrostu i spadku pomaga podkreślić zwężanie się na końcu animacji.

Rysunek 2. Ta forma fali reprezentuje przyspieszenie wyjściowe wibracji na urządzeniu.
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; } }
Przykład: kołysanie (z obrotami)
Jednym z kluczowych zasadniczych zasad haptycznych jest zadowolenie użytkowników. Zabawnym sposobem na wprowadzenie przyjemnego, nieoczekiwanego efektu wibracji jest użycie PRIMITIVE_SPIN
.
Ten typ jest najbardziej efektywny, gdy jest wywoływany więcej niż raz. Połączenie wielu obrotów może stworzyć efekt kołysania i niestabilności, który można dodatkowo wzmocnić, stosując nieco losowego skalowania dla każdego prymitywu. Możesz też eksperymentować z przedziałem między kolejnymi prymitywami. Dwa obroty bez przerwy (0 ms między nimi) dają wrażenie szybkiego obrotu. Zwiększenie przerwy między obrotami z 10 do 50 ms powoduje wrażenie luźniejszego obracania. Można to wykorzystać, aby dopasować czas trwania filmu lub animacji.
Nie zalecamy stosowania przerw dłuższych niż 100 ms, ponieważ kolejne sceny nie będą się dobrze łączyć i zaczną przypominać osobne efekty.
Oto przykład elastycznej figury, która odskakuje po przeciągnięciu w dół i puszczeniu. Animacja jest wzbogacona o parę efektów obrotu, odtwarzanych z różną intensywnością proporcjonalną do przesunięcia odbicia.

Rysunek 3. Ta forma fali reprezentuje przyspieszenie wyjściowe wibracji na urządzeniu.
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) } }
Przykład: odbicie (z odgłosem)
Innym zaawansowanym zastosowaniem efektów wibracji jest symulowanie fizycznych interakcji. PRIMITIVE_THUD
może tworzyć silny efekt pogłosu, który można połączyć z wizualizacją uderzenia, na przykład w filmie lub animacji, aby wzbogacić ogólne wrażenia.
Oto przykład prostej animacji spadania piłki wzbogaconej o efekt uderzenia, który odtwarzany jest za każdym razem, gdy piłka odbija się od dołu ekranu:

Rysunek 4. Ta forma fali reprezentuje przyspieszenie wyjściowe wibracji na urządzeniu.
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; } } }); } }