Z tej strony dowiesz się, jak korzystać z różnych interfejsów API haptycznych, by tworzyć niestandardowe efekty w aplikacjach na Androida. Większość informacji na temat ta strona opiera się na dobrej znajomości działania mechanizmu wibracyjnego, zalecamy przeczytanie artykułu Wprowadzenie do mechanizmu wibracyjnego.
Na tej stronie znajdują się następujące przykłady.
- Niestandardowe wzory wibracji
- Wzór rozruchu: wzór, który płynnie się zaczyna.
- Powtarzający się wzór: wzór bez końca.
- Wzorzec z wartością zastępczą: wartość zastępcza i demonstracjami.
- Kompozycje wibracyjne
- Resist (Odporność): efekt przeciągania o dynamicznej intensywności.
- Rozwinięcie: efekt wzrostu, a potem upadek.
- Charakterystyka: efekt chwiejnego ruchu korzystający z elementu podstawowego
SPIN
. - Odbijanie: efekt odsyłania przy użyciu elementu podstawowego
THUD
.
Więcej przykładów znajdziesz w sekcji Dodawanie reakcji haptycznych do zdarzeń. muszą być zawsze zgodne z zasadami projektowania haptycznego.
Obsługa kreacji zastępczych w celu obsługi zgodności urządzeń
Wdrażając efekty niestandardowe, weź pod uwagę te kwestie:
- Jakie funkcje urządzenia są wymagane do uzyskania efektu
- Co zrobić, gdy urządzenie nie może odtworzyć efektu
Dokumentacja interfejsu haptics API na Androidzie zawiera szczegółowe informacje o tym, jak sprawdzić obsługa komponentów wchodzących w skład czujnika haptycznego. Dzięki temu aplikacja może i spójny interfejs.
W zależności od zastosowania możesz wyłączyć efekty niestandardowe lub dostarczać alternatywne efekty niestandardowe oparte na różnych potencjalnych możliwościach.
Zaplanuj konfigurację pod kątem tych ogólnych klas możliwości urządzeń:
Jeśli używasz elementów głównych z obsługą dotykową: urządzenia, które je obsługują. potrzebne efekty niestandardowe. (W następnej sekcji znajdziesz szczegółowe informacje na temat elementów podstawowych).
Urządzenia z regulacją amplitudy.
urządzeń z podstawową obsługą wibracji (włączoną/wyłączoną), których nie da się kontrolować amplitudy.
Jeśli wybór efektu haptycznego aplikacji uwzględnia te kategorie, wrażenia haptyczne użytkownika powinny być przewidywalne w przypadku każdego urządzenia.
Zastosowanie elementów haptycznych
Android ma kilka elementów głównych haptycznych, które różnią się amplitudą i i częstotliwości. Możesz używać jednego elementu podstawowego oddzielnie lub kilku obiektów podstawowych w połączeniu aby uzyskać mocne efekty haptyczne.
- Użyj opóźnień co najmniej 50 ms w celu uzyskania wyraźnych przerw między 2 sekundami podstawowe, uwzględniając też funkcję podstawowa czas trwania jeśli to możliwe.
- Używaj skal, które różnią się współczynnikiem wynoszącym co najmniej 1,4, aby różnica w jest lepiej zauważalna.
Użyj skal 0,5, 0,7 i 1,0, aby ocenić niską, średnią i wysoką i wersji intensywności obiektu podstawowego.
Twórz własne wzory wibracji
Wzorce wibracji są często wykorzystywane w inteligentnych czujnikach haptycznych, np. w powiadomieniach
i dzwonki. Usługa Vibrator
może odtwarzać długie wzorce wibracji, które
zmieniać amplitudę wibracji w czasie. Efekty te są nazywane falami.
Efekty fali mogą być łatwo zauważalne, ale nagłe, długie wibracje mogą zaskoczenie użytkownika przy odtwarzaniu w cichym otoczeniu. Zmniejszenie do docelowej amplitudy Zbyt szybko może też być słychać słyszalne buczenie. Zalecenie dla projektowanie wzorców fali ma na celu wygładzenie przejść amplitudy w celu utworzenia efekty zwiększania i zmniejszania.
Przykład: Wzorzec wyświetlania
Fale są reprezentowane jako VibrationEffect
z 3 parametrami:
- Czasy: tablica czasu trwania każdej fali (w milisekundach) segmentację.
- Amplitudy: pożądana amplituda wibracji dla każdego określonego czasu trwania w pierwszym argumencie reprezentowany przez liczbę całkowitą od 0 do 255, przy czym 0 symbolizujący wyłączenie wibracji a 255 to maksymalna liczba znaków w urządzeniu. amplituda.
- Powtórz indeks: indeks w tablicy określonej w pierwszym argumencie do rozpocząć powtarzanie fali lub wartość -1, jeśli ma ona odtworzyć falę tylko raz.
Oto przykład fali, która miga dwukrotnie z przerwami wynoszącymi 350 ms pulsować. Pierwszy puls jest płynny, aż do maksymalnej amplitudy, a sekunda to szybka rampa do utrzymania maksymalnej amplitudy. Zatrzymanie na końcu jest zdefiniowane przez wartość indeksu powtarzania ujemnym.
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
Fale można też odtwarzać w taki sposób, dopóki nie zostaną anulowane. Sposób tworzenia powtarzanie fali oznacza ustawienie nieujemnego parametru „powtarzania”. Gdy zagrasz w będzie się powtarzał, dopóki nie zostanie wyraźnie anulowany usługa:
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 przejściowych zdarzeń, które wymagają działania użytkownika, aby potwierdzić. Mogą to być na przykład przychodzące rozmowy telefoniczne oraz uruchomiono alarmy.
Przykład: wzór z wartością zastępczą
Sterowanie amplitudą drgań jest funkcje zależne od sprzętu. Odtwarzanie fali na słabsze urządzenie bez tej funkcji powoduje, że wibruje z maksymalną wibracją. amplituda dla każdego dodatniego wpisu w tablicy amplitudy. Jeśli aplikacja musi do takich urządzeń, należy upewnić się, nie generuje brzęczenia przy odtwarzaniu w takich warunkach lub zaprojektować prostszy wzorzec włączania/wyłączania, który można odtworzyć jako kreację zastępczą.
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)); }
Twórz kompozycje wibracyjne
W tej sekcji przedstawiamy sposoby ich tworzenia bardziej skomplikowane i złożone efekty niestandardowe. czujnik haptyczny wykorzystujący bardziej zaawansowane możliwości sprzętowe. Możesz użyć kombinacji atrybutów efekty o różnej amplitudzie i częstotliwości, co pozwala uzyskać bardziej złożone efekty haptyczne. na urządzeniach z czujnikami haptycznymi o szerszej przepustowości.
Proces tworzenia niestandardowych wibracji omówionych wcześniej na tej stronie wyjaśnia, jak kontrolować amplitudę wibracji, aby uzyskać płynne efekty w górę i w dół. Ulepszona funkcja haptyczna ulepsza tę koncepcję, analizując poszerz zakres częstotliwości wibracji, aby efekt był jeszcze gładszy. Fale te są szczególnie skuteczne przy tworzeniu crescendo lub diminuendo efektu.
Opisane wcześniej na tej stronie elementy podstawowe są zaimplementowane przez od producenta urządzenia. Zapewniają wyraźne, krótkie i przyjemne wibracje który jest zgodny z zasadami haptyki zapewniającymi wyraźny sygnał haptyczny. Więcej szczegółowe informacje na temat tych funkcji i sposobu ich działania można znaleźć w artykule Mechanizmy wibracyjne Primer.
Android nie udostępnia kreacji zastępczych w przypadku kompozycji, które nie są obsługiwane elementów podstawowych. Zalecamy wykonanie tych czynności:
Zanim aktywujesz zaawansowane czujniki haptyczne, sprawdź, czy urządzenie obsługuje wszystkich używanych elementów podstawowych.
Wyłącz ten spójny zestaw, które nie są obsługiwane, a nie tylko w których brakuje elementu podstawowego. Więcej informacji o sprawdzaniu obsługi urządzenia jest przedstawiona w następujący sposób.
Możesz tworzyć różne efekty wibracji w VibrationEffect.Composition
.
Oto przykład efektu powoli rosnącego z efektem ostrego 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());
Kompozycję tworzy się przez dodanie elementów podstawowych, które mają być odtwarzane w sekwencji. Każdy Element podstawowy jest również skalowalny, dzięki czemu można kontrolować amplitudę wibracji generowane przez każdą z nich. Skala jest zdefiniowana jako wartość z zakresu od 0 do 1, gdzie 0 faktycznie odpowiada minimalnej amplitudzie, przy której ten element podstawowy może być odczuć użytkownika.
Jeśli chcesz utworzyć słabą i mocną wersję tego samego obiektu podstawowego, zalecana, aby skale różniły się współczynnikiem co najmniej 1,4, aby różnica może być łatwo zauważona. Nie próbuj tworzyć więcej niż trzech poziomów intensywności tego samego elementu podstawowego, ponieważ nie są one do różnych celów. Na przykład użyj skal 0,5, 0,7 i 1,0, aby stworzyć niską, średnią wartość, i o wysokiej intensywności konstrukcji obiektu podstawowego.
Kompozycja może również określać opóźnienia, które mają być dodawane między kolejnymi elementów podstawowych. Opóźnienie jest wyrażone w milisekundach od zakończenia do poprzedniego elementu podstawowego. Ogólnie przerwa 5–10 ms między 2 podstawowymi elementami też jest zbyt duża są krótkie, aby można je było wykryć. Rozważ użycie przerwy mierzącej co najmniej 50 ms. gdy chcemy stworzyć zauważalną lukę między 2 elementami podstawowymi. Oto przykład kompozycji z opóźnieniem:
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());
Za pomocą poniższych interfejsów API można zweryfikować obsługę określonych urządzeń elementy podstawowe:
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ż zaznaczyć kilka elementów podstawowych i zdecydować, które z nich tworzenie wiadomości w zależności od poziomu obsługi urządzeń:
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: Resist (niskie kreski)
Możesz kontrolować amplitudę wibracji podstawowych, które mają przekazywać przydatne informacje zwrotne dotyczące trwającego działania. Ściśle rozmieszczone wartości skali jest używany do uzyskania gładkiego efektu crescendo obiektu podstawowego. Opóźnienie między kolejne elementy podstawowe można też ustawić dynamicznie na podstawie nazwy użytkownika interakcji. Zostało to pokazane w poniższym przykładzie animacji widoku jest sterowana gestem przeciągania i włączona za pomocą reakcji haptycznych.
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 (ze wzrostem i spadkiem)
Istnieją 2 elementy podstawowe pozwalające zwiększyć intensywność wibracji:
PRIMITIVE_QUICK_RISE
.
oraz
PRIMITIVE_SLOW_RISE
Oba rodzaje są kierowane do tego samego celu, ale z różnym czasem trwania. Jest tylko jedna
dla łagodzenia różnic,
PRIMITIVE_QUICK_FALL
Te elementy podstawowe współdziałają lepiej przy tworzeniu segmentu fali, który rośnie w
i umrze. Możesz wyrównać skalowane elementy podstawowe, aby zapobiec nagłym
przeskakuje w amplitudzie między nimi, co również dobrze sprawdza się jako poszerzenie
czas trwania efektu. Zauważamy, że użytkownicy zawsze częściej obserwują rosnącą część
część opadającą, więc krótsza niż część rosnąca
można wykorzystać do przeniesienia podkreślenia na część „opadającą”.
Oto przykład zastosowania tej kompozycji do rozwijania i rozwijania, zwijania okręgu. Efekt wzrostu może wzmocnić wrażenie rozszerzania w trakcie animację. Połączenie efektów wschodu i spadu pomaga podkreślić zwijania się na końcu animacji.
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: Wahadło (z obrotami)
Jedną z kluczowych zasad haptycznych jest sprawianie wrażenia użytkowników. Zabawny sposób
w celu uzyskania przyjemnego, nieoczekiwanego efektu wibracji jest użycie
PRIMITIVE_SPIN
Ten element podstawowy jest najskuteczniejszy, gdy jest wywoływany więcej niż raz. Wiele
obroty scalone mogą powodować chwienie się i niestabilne działanie, które może być
dodatkowo ulepszona przez zastosowanie do każdego podstawowego skalowania nieco losowego. Ty
Możesz też eksperymentować z luką między kolejnymi liczbami argumentów podstawowych. 2 obroty
bez przerwy (pomiędzy 0 ms) wywołuje uczucie wirowania. Rosnący
przerwa między wirowaniami między 10 a 50 ms zapewnia luźniejsze wirowanie.
można użyć, aby dopasować czas trwania filmu lub animacji.
Nie zalecamy korzystania z przerwy dłuższej niż 100 ms, ponieważ nie będzie już dobrze integrować się z grą i zacznie się odtwarzać jak indywidualne efekty.
Oto przykład elastycznego kształtu, który odbija się po przeciągnięciu w dół a potem zwolnił. Animacja jest wzbogacona o 2 efekty obrotowe, o zróżnicowanej intensywności proporcjonalnej do przemieszczenia po odbiciu.
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: Podskok (z głośnością)
Innym zaawansowanym zastosowaniem efektów wibracji jest symulowanie
interakcje.
PRIMITIVE_THUD
mogą stworzyć mocny i pogłosowy efekt, który można połączyć
wizualizacji efektu, np. w filmie lub animacji, aby wzbogacić
ogólne wrażenia.
Oto przykład prostej animacji upuszczania piłki wzbogaconej o efekt głośnego uderzenia odtwarzane za każdym razem, gdy piłka odbija się od dolnej krawędzi ekranu:
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; } } }); } }