Halaman ini membahas contoh cara menggunakan berbagai API haptik untuk membuat efek kustom di aplikasi Android. Karena sebagian besar informasi di halaman ini mengandalkan pengetahuan yang baik tentang cara kerja aktuator getaran, sebaiknya baca Primer aktuator getaran.
Halaman ini menyertakan contoh berikut.
- Pola getaran kustom
- Pola peningkatan: Pola yang dimulai dengan lancar.
- Pola berulang: Pola tanpa akhir.
- Pola dengan penggantian: Demonstrasi penggantian.
- Komposisi getaran
Untuk contoh tambahan, lihat Menambahkan respons haptic ke peristiwa, dan selalu ikuti prinsip desain haptic.
Menggunakan penggantian untuk menangani kompatibilitas perangkat
Saat menerapkan efek kustom, pertimbangkan hal berikut:
- Kemampuan perangkat yang diperlukan untuk efek
- Yang harus dilakukan jika perangkat tidak dapat memutar efek
Referensi API haptik Android memberikan detail tentang cara memeriksa dukungan untuk komponen yang terlibat dalam haptik Anda, sehingga aplikasi Anda dapat memberikan pengalaman keseluruhan yang konsisten.
Bergantung pada kasus penggunaan, Anda mungkin ingin menonaktifkan efek kustom atau memberikan efek kustom alternatif berdasarkan berbagai potensi kemampuan.
Buat rencana untuk kelas kemampuan perangkat tingkat tinggi berikut:
Jika Anda menggunakan primitif haptik: perangkat yang mendukung primitif tersebut yang diperlukan oleh efek kustom. (Lihat bagian berikutnya untuk mengetahui detail primitif.)
Perangkat dengan kontrol amplitudo.
Perangkat dengan dukungan getaran dasar (aktif/nonaktif)—dengan kata lain, perangkat yang tidak memiliki kontrol amplitudo.
Jika pilihan efek haptik aplikasi Anda memperhitungkan kategori ini, pengalaman pengguna haptiknya harus tetap dapat diprediksi untuk setiap perangkat.
Penggunaan primitif haptic
Android menyertakan beberapa primitif haptik yang bervariasi dalam amplitudo dan frekuensi. Anda dapat menggunakan satu primitif saja atau beberapa primitif secara bersamaan untuk mendapatkan efek haptic yang kaya.
- Gunakan penundaan 50 md atau lebih lama untuk celah yang dapat dilihat antara dua primitif, juga dengan mempertimbangkan durasi primitif jika memungkinkan.
- Gunakan skala yang berbeda dengan rasio 1,4 atau lebih sehingga perbedaan intensitas lebih mudah dirasakan.
Gunakan skala 0,5, 0,7, dan 1,0 untuk membuat versi primitif intensitas rendah, sedang, dan tinggi.
Membuat pola getaran kustom
Pola getaran sering digunakan dalam haptik perhatian, seperti notifikasi
dan nada dering. Layanan Vibrator
dapat memutar pola getaran panjang yang
mengubah amplitudo getaran dari waktu ke waktu. Efek tersebut disebut bentuk gelombang.
Efek bentuk gelombang dapat dengan mudah dirasakan, tetapi getaran panjang yang tiba-tiba dapat mengejutkan pengguna jika diputar di lingkungan yang tenang. Meningkatkan amplitudo target terlalu cepat juga dapat menghasilkan suara dengung yang terdengar. Rekomendasi untuk mendesain pola bentuk gelombang adalah dengan memperlancar transisi amplitudo untuk membuat efek peningkatan dan penurunan.
Contoh: Pola peningkatan
Waveform direpresentasikan sebagai VibrationEffect
dengan tiga parameter:
- Pengaturan waktu: array durasi, dalam milidetik, untuk setiap segmen bentuk gelombang.
- Amplitudo: amplitudo getaran yang diinginkan untuk setiap durasi yang ditentukan dalam argumen pertama, diwakili oleh nilai bilangan bulat dari 0 hingga 255, dengan 0 mewakili "nonaktif" vibrator dan 255 adalah amplitudo maksimum perangkat.
- Indeks ulangi: indeks dalam array yang ditentukan dalam argumen pertama untuk mulai mengulangi bentuk gelombang, atau -1 jika hanya akan memutar pola sekali.
Berikut adalah contoh bentuk gelombang yang berdenyut dua kali dengan jeda 350 md di antara denyut. Pulsa pertama adalah peningkatan yang halus hingga amplitudo maksimum, dan puls kedua adalah peningkatan cepat untuk mempertahankan amplitudo maksimum. Berhenti di akhir ditentukan oleh nilai indeks pengulangan negatif.
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));
Contoh: Pola berulang
Waveform juga dapat diputar berulang kali hingga dibatalkan. Cara membuat bentuk gelombang berulang adalah dengan menetapkan parameter 'repeat' yang non-negatif. Saat Anda memutar bentuk gelombang berulang, getaran akan berlanjut hingga dibatalkan secara eksplisit dalam layanan:
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(); }
Hal ini sangat berguna untuk peristiwa intermiten yang memerlukan tindakan pengguna untuk mengonfirmasinya. Contoh peristiwa tersebut mencakup panggilan telepon masuk dan alarm yang dipicu.
Contoh: Pola dengan penggantian
Mengontrol amplitudo getaran adalah kemampuan yang bergantung pada hardware. Memutar bentuk gelombang di perangkat kelas bawah tanpa kemampuan ini akan menyebabkannya bergetar pada amplitudo maksimum untuk setiap entri positif dalam array amplitudo. Jika aplikasi Anda perlu menampung perangkat tersebut, sebaiknya pastikan pola Anda tidak menghasilkan efek dengung saat diputar dalam kondisi tersebut, atau desain pola AKTIF/NONAKTIF yang lebih sederhana yang dapat diputar sebagai pengganti.
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)); }
Membuat komposisi getaran
Bagian ini menyajikan cara menyusunnya menjadi efek kustom yang lebih lama dan lebih kompleks, serta melampauinya untuk menjelajahi haptik yang kaya menggunakan kemampuan hardware yang lebih canggih. Anda dapat menggunakan kombinasi efek yang memvariasikan amplitudo dan frekuensi untuk membuat efek haptik yang lebih kompleks di perangkat dengan aktuator haptik yang memiliki bandwidth frekuensi yang lebih luas.
Proses untuk membuat pola getaran kustom, yang dijelaskan sebelumnya di halaman ini, menjelaskan cara mengontrol amplitudo getaran untuk membuat efek yang halus saat menaikkan dan menurunkan. Haptik yang kaya meningkatkan konsep ini dengan menjelajahi rentang frekuensi yang lebih luas dari vibrator perangkat untuk membuat efeknya lebih halus. Gelombang ini sangat efektif dalam menciptakan efek crescendo atau diminuendo.
Primitif komposisi, yang dijelaskan sebelumnya di halaman ini, diterapkan oleh produsen perangkat. Alat ini memberikan getaran yang jernih, singkat, dan menyenangkan yang selaras dengan Prinsip haptic untuk haptic yang jelas. Untuk mengetahui detail selengkapnya tentang kemampuan ini dan cara kerjanya, lihat Panduan aktuator getaran.
Android tidak menyediakan penggantian untuk komposisi dengan primitif yang tidak didukung. Sebaiknya lakukan langkah-langkah berikut:
Sebelum mengaktifkan haptik lanjutan, pastikan perangkat tertentu mendukung semua primitif yang Anda gunakan.
Nonaktifkan kumpulan pengalaman konsisten yang tidak didukung, bukan hanya efek yang tidak memiliki primitif. Informasi selengkapnya tentang cara memeriksa dukungan perangkat ditampilkan sebagai berikut.
Anda dapat membuat efek getaran yang disusun dengan VibrationEffect.Composition
.
Berikut adalah contoh efek yang naik perlahan diikuti dengan efek klik yang tajam:
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());
Komposisi dibuat dengan menambahkan primitif yang akan diputar secara berurutan. Setiap primitif juga skalabel, sehingga Anda dapat mengontrol amplitudo getaran yang dihasilkan oleh setiap primitif. Skala ditentukan sebagai nilai antara 0 dan 1, dengan 0 sebenarnya dipetakan ke amplitudo minimum saat primitif ini dapat (hampir) dirasakan oleh pengguna.
Jika Anda ingin membuat versi primitif yang lemah dan kuat, sebaiknya skalanya berbeda dengan rasio 1,4 atau lebih, sehingga perbedaan intensitas dapat dengan mudah dirasakan. Jangan mencoba membuat lebih dari tiga tingkat intensitas primitif yang sama, karena primitif tersebut tidak berbeda secara persepsi. Misalnya, gunakan skala 0,5, 0,7, dan 1,0 untuk membuat versi primitif intensitas rendah, sedang, dan tinggi.
Komposisi juga dapat menentukan penundaan yang akan ditambahkan di antara primitif berurutan. Penundaan ini dinyatakan dalam milidetik sejak akhir primitif sebelumnya. Secara umum, jeda 5 hingga 10 md antara dua primitif terlalu singkat untuk terdeteksi. Pertimbangkan untuk menggunakan jeda sekitar 50 md atau lebih lama jika Anda ingin membuat jeda yang dapat dilihat antara dua primitif. Berikut adalah contoh komposisi dengan penundaan:
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());
API berikut dapat digunakan untuk memverifikasi dukungan perangkat untuk primitif tertentu:
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. }
Anda juga dapat memeriksa beberapa primitif, lalu memutuskan primitif mana yang akan dikomposisi berdasarkan tingkat dukungan perangkat:
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);
Contoh: Resist (dengan tick rendah)
Anda dapat mengontrol amplitudo getaran primitif untuk menyampaikan masukan yang berguna ke tindakan yang sedang berlangsung. Nilai skala yang berjarak rapat dapat digunakan untuk membuat efek crescendo primitif yang halus. Penundaan antara primitif berturut-turut juga dapat ditetapkan secara dinamis berdasarkan interaksi pengguna. Hal ini diilustrasikan dalam contoh animasi tampilan berikut yang dikontrol oleh gestur tarik dan ditambah dengan haptik.

Gambar 1. Gelombang ini mewakili akselerasi output getaran di perangkat.
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); } }
Contoh: Luaskan (dengan naik dan turun)
Ada dua primitif untuk meningkatkan intensitas getaran yang dirasakan: PRIMITIVE_QUICK_RISE
dan
PRIMITIVE_SLOW_RISE
.
Keduanya mencapai target yang sama, tetapi dengan durasi yang berbeda. Hanya ada satu primitif untuk mengurangi kecepatan, yaitu PRIMITIVE_QUICK_FALL
.
Primitif ini bekerja lebih baik bersama-sama untuk membuat segmen bentuk gelombang yang intensitasnya
bertambah, lalu mati. Anda dapat menyelaraskan primitif yang diskalakan untuk mencegah
lompatan mendadak dalam amplitudo di antara keduanya, yang juga berfungsi dengan baik untuk memperpanjang durasi
efek secara keseluruhan. Secara persepsi, orang selalu lebih memperhatikan bagian yang naik daripada
bagian yang turun, sehingga membuat bagian yang naik lebih pendek daripada bagian yang turun dapat
digunakan untuk mengalihkan penekanan ke bagian yang turun.
Berikut adalah contoh penerapan komposisi ini untuk meluaskan dan menyempitkan lingkaran. Efek naik dapat meningkatkan perasaan perluasan selama animasi. Kombinasi efek naik dan turun membantu menekankan penyingkatan di akhir animasi.

Gambar 2. Gelombang ini mewakili akselerasi output getaran di perangkat.
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; } }
Contoh: Goyang (dengan putaran)
Salah satu prinsip haptic utama adalah untuk menyenangkan pengguna. Cara yang menyenangkan
untuk memperkenalkan efek getaran yang menyenangkan dan tidak terduga adalah dengan menggunakan
PRIMITIVE_SPIN
.
Primitif ini paling efektif jika dipanggil lebih dari sekali. Beberapa
putaran yang digabungkan dapat menciptakan efek goyang dan tidak stabil, yang dapat
ditingkatkan lebih lanjut dengan menerapkan penskalaan yang agak acak pada setiap primitif. Anda
juga dapat bereksperimen dengan celah antara primitif spin berturut-turut. Dua putaran
tanpa jeda (0 md di antaranya) akan menciptakan sensasi putaran yang kencang. Meningkatkan
jeda antar-putaran dari 10 menjadi 50 md akan menghasilkan sensasi putaran yang lebih longgar, dan
dapat digunakan untuk mencocokkan durasi video atau animasi.
Sebaiknya jangan gunakan jeda yang lebih dari 100 md, karena putaran berturut-turut tidak lagi terintegrasi dengan baik dan mulai terasa seperti efek individual.
Berikut adalah contoh bentuk elastis yang memantul kembali setelah ditarik ke bawah lalu dilepaskan. Animasi ditingkatkan dengan sepasang efek putaran, yang diputar dengan intensitas bervariasi yang sebanding dengan perpindahan pantulan.

Gambar 3. Gelombang ini mewakili akselerasi output getaran di perangkat.
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) } }
Contoh: Pantulan (dengan suara gemuruh)
Aplikasi lanjutan lainnya dari efek getaran adalah untuk menyimulasikan interaksi
fisik. PRIMITIVE_THUD
dapat menciptakan efek yang kuat dan bergema, yang dapat dipadukan dengan
visualisasi dampak, misalnya dalam video atau animasi, untuk meningkatkan
pengalaman secara keseluruhan.
Berikut adalah contoh animasi drop bola sederhana yang ditingkatkan dengan efek dentuman yang diputar setiap kali bola memantul dari bagian bawah layar:

Gambar 4. Gelombang ini mewakili akselerasi output getaran di perangkat.
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; } } }); } }