Na tej stronie znajdziesz przykłady korzystania z różnych interfejsów API dotykowych w celu tworzenia niestandardowych efektów poza standardowymi falami wibracji w aplikacji na Androida.
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 opcją zastępczą: demonstracja opcji zastępczej.
- Kompozycje wibracyjne
- Resist: efekt oporu z dynamiczną intensywnością.
- Rozwiń: efekt wznoszący się, a następnie opadająco.
- Wobble: efekt kołysania za pomocą prymitywu
SPIN
. - Odbijanie: efekt odbijania za pomocą prymitywu
THUD
.
Dodatkowe przykłady znajdziesz w artykule Dodawanie wibracji do zdarzeń. Pamiętaj, aby zawsze przestrzegać zasad projektowania haptycznego.
Zastosowanie rozwiązań zastępczych do obsługi zgodności urządzeń
Podczas wdrażania efektu niestandardowego weź pod uwagę te kwestie:
- Funkcje urządzenia wymagane do działania efektu
- 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 haptycznych funkcjach 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, urządzenia bez kontroli amplitudy.
Jeśli w aplikacji uwzględniono te kategorie, użytkownik powinien mieć przewidywalne wrażenia haptyczne na każdym urządzeniu.
Korzystanie z 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 kilku prymitywów w połączeniu.
- W przypadku zauważalnych przerw między dwoma elementami należy stosować opóźnienia co najmniej 50 ms, 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 przebiegami.
Efekty fali są zwykle wyczuwalne, ale nagłe, długie wibracje mogą przestraszyć użytkownika, jeśli odtwarzanie odbywa się w cichym otoczeniu. Zbyt szybkie zwiększanie amplitudy docelowej może też powodować słyszalne brzęczenie. Projektowanie wzorów fali w celu wygładzania przejść między amplitudami i tworzenia efektów wzmacniania i ściszania.
Przykłady wzorów wibracji
W poniższych sekcjach znajdziesz kilka przykładów wzorów wibracji:
Wzór zwiększania wyświetlania
Faloformy są reprezentowane jako VibrationEffect
z 3 parametrami:
- Timings: tablica z czasem trwania (w milisekundach) każdego segmentu 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 „stan wyłączony”, a 255 to maksymalna amplituda urządzenia.
- Indeks powtórzeń: indeks w tablicy określony w pierwszym argumencie, aby rozpocząć powtarzanie fali dźwiękowej lub -1, jeśli ma ona być odtworzona 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 // Don't 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; // Don't repeat.
vibrator.vibrate(VibrationEffect.createWaveform(
timings, amplitudes, repeatIndex));
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ładami takich zdarzeń są przychodzące połączenia telefoniczne i wyzwalane alarmy.
Wzór z zastępczym
Regulowanie amplitudy wibracji to funkcja zależna od sprzętu. Odtwarzanie fali na urządzeniu niskiego poziomu bez tej funkcji powoduje wibrowanie urządzenia z maksymalną amplitudą dla każdego dodatniego wpisu w tablicy amplitudy. Jeśli Twoja aplikacja musi obsługiwać takie urządzenia, użyj wzoru, który nie generuje efektu brzęczenia podczas odtwarzania w takich warunkach, lub zaprojektuj prostszy wzór włączania/wyłączania, który można odtwarzać jako alternatywę.
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 wibracji w dłuższe i bardziej złożone efekty niestandardowe. Oprócz tego dowiesz się, jak korzystać z zaawansowanych funkcji sprzętowych, aby tworzyć bogate wibracje. Możesz używać kombinacji efektów, które zmieniają amplitudę i częstotliwość, aby tworzyć bardziej złożone efekty haptyczne na urządzeniach z silnikami haptycznymi o szerszym paśmie częstotliwości.
Proces tworzenia niestandardowych wzorów wibracji, opisany wcześniej na tej stronie, wyjaśnia, jak kontrolować amplitudę wibracji, aby uzyskać płynne efekty zwiększania i zmniejszania intensywności 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’y kompozycji, 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. Więcej informacji o tych funkcjach i o sposobie ich działania znajdziesz w artykule Wprowadzenie do siłowników wibracyjnych.
Android nie zapewnia alternatywnych rozwiązań dla kompozycji z nieobsługiwanymi prymitywami. Dlatego 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.
W kolejnych sekcjach znajdziesz więcej informacji o tym, jak sprawdzić, czy urządzenie jest obsługiwane.
Tworzenie złożonych efektów wibracji
Za pomocą VibrationEffect.Composition
możesz tworzyć złożone efekty wibracji. Oto przykład efektu powolnego wzrostu, a następnie nagłego 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) wyczuć ten prymityw.
Tworzenie wariantów w przypadku wibracji prostych
Jeśli chcesz utworzyć słabszą i silniejszą wersję tego samego prymitywu, utwórz współczynniki siły 1,4 i wyższe, 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 wyraźnie odróżnialne. Użyj na przykład skali 0,5, 0,7 i 1,0, aby utworzyć wersje prymitywu o niskiej, średniej i wysokiej intensywności.
Dodawanie przerw między wibracjami
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 5–10 ms między dwoma prymitywami jest zbyt krótki, aby można go było wykryć. Jeśli chcesz utworzyć wyraźną przerwę między dwoma prymitywami, użyj przerwy o długości co najmniej 50 ms. 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());
Sprawdzanie obsługiwanych prymitywów
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łady kompozycji wibracji
W następnych sekcjach znajdziesz kilka przykładów kompozycji wibracji pochodzących z przykładowej aplikacji haptycznej na GitHubie.
Resist (with low ticks)
Możesz kontrolować amplitudę wibracji prymitywnych, aby przekazywać przydatne informacje o działaniu w toku. 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 użytkownika. Przykład animacji widoku sterowanej gestem przeciągania i wzbogaconej funkcją haptyczną przedstawia poniższy obraz.

Rysunek 1. Ten przebieg falowy przedstawia 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);
}
}
Rozwiń (z podnoszeniem i opadaniem)
Aby zwiększyć odczuwaną intensywność wibracji, możesz użyć 2 elementów prymitywnych: PRIMITIVE_QUICK_RISE
i PRIMITIVE_SLOW_RISE
. Oba mają ten sam cel, ale różnią się długością. Jest tylko jeden prymityw do łagodnego zwalniania, PRIMITIVE_QUICK_FALL
. Te prymity lepiej współpracują ze sobą, tworząc segment fali, który narasta, a potem zanika. Możesz wyrównywać skalowane prymity, aby zapobiec nagłym skokom amplitudy między nimi. Takie działanie pozwala też wydłużyć czas trwania efektu.
Z punktu widzenia percepcji ludzie zawsze bardziej zauważają część rosnącą niż opadającą, więc skrócenie części rosną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. Ten przebieg fali przedstawia 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;
}
}
Wobble (z obrotami)
Jednym z kluczowych zasadniczych zasad haptycznych jest zadowolenie użytkowników. Ciekawym 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 przestrzenią między kolejnymi pierwotnymi. 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 płynniejszego obracania. Można to wykorzystać, aby dopasować czas trwania filmu lub animacji.
Nie używaj przerwy dłuższej niż 100 ms, ponieważ kolejne obroty 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, które są odtwarzane z różną intensywnością proporcjonalną do przesunięcia odbicia.

Rysunek 3. Ten przebieg falowy przedstawia 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 the range [-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 the range [-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)
}
}
Odbijanie (z uderzeniami)
Innym zaawansowanym zastosowaniem efektów wibracji jest symulowanie fizycznych interakcji. PRIMITIVE_THUD
może tworzyć silny i pogłoszysty efekt, 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 animacji spadania piłki wzbogaconej o efekt uderzenia, który jest odtwarzany za każdym razem, gdy piłka odbija się od dołu ekranu:

Rysunek 4. Ten przebieg falowy przedstawia 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;
}
}
});
}
}