Halaman ini membahas contoh cara menggunakan berbagai API haptics untuk membuat efek kustom di aplikasi Android. Begitu banyak informasi tentang halaman ini mengandalkan pengetahuan yang baik tentang cara kerja aktuator getaran, sebaiknya baca Pratur aktuator getaran.
Halaman ini menyertakan contoh berikut.
- Pola getaran kustom
- Pola peningkatan: Pola yang dimulai dengan lancar.
- Pola berulang: Pola tanpa ujung.
- Pola dengan penggantian: Penggantian demonstrasi.
- Komposisi getaran
Untuk contoh lainnya, lihat Menambahkan respons haptic ke peristiwa, dan selalu mengikuti prinsip desain haptic.
Menggunakan penggantian untuk menangani kompatibilitas perangkat
Ketika menerapkan efek kustom, pertimbangkan hal berikut:
- Kemampuan perangkat mana yang diperlukan untuk penerapan
- Apa yang harus dilakukan jika perangkat tidak dapat memutar efek
Referensi Android haptics API memberikan detail tentang cara memeriksa untuk komponen yang terlibat dalam haptic, sehingga aplikasi Anda dapat menyediakan konsisten secara keseluruhan.
Tergantung pada kasus penggunaan, Anda mungkin ingin menonaktifkan efek khusus atau menyediakan efek kustom alternatif berdasarkan kemampuan potensial yang berbeda.
Rencanakan kelas kemampuan perangkat tingkat tinggi berikut:
Jika Anda menggunakan primitif haptic: perangkat yang mendukung primitif tersebut yang dibutuhkan 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 kurang memiliki kontrol amplitudo.
Jika pilihan efek haptic aplikasi Anda memperhitungkan kategori ini, maka pengalaman pengguna haptic harus tetap dapat diprediksi untuk setiap perangkat.
Penggunaan primitif haptic
Android menyertakan beberapa primitif haptic yang bervariasi baik amplitudo maupun frekuensi. Anda dapat menggunakan satu primitif saja atau beberapa primitif sekaligus untuk mendapatkan efek haptik yang kaya.
- Gunakan penundaan selama 50 md atau lebih untuk jarak yang jelas antara dua standar, dengan tetap memperhitungkan prinsip durasi jika memungkinkan.
- Gunakan skala yang berbeda dengan rasio 1,4 atau lebih, sehingga perbedaan intensitas warna dapat menjadi lebih baik.
Gunakan skala 0,5, 0,7 dan 1,0 untuk membuat rendah, sedang dan tinggi versi intensitas primitif.
Membuat pola getaran kustom
Pola getaran sering digunakan dalam haptic atensi, seperti notifikasi
dan nada dering. Layanan Vibrator
dapat memutar pola getaran lama yang
mengubah amplitudo getaran dari waktu ke waktu. Efek tersebut dinamakan bentuk gelombang.
Efek bentuk gelombang mudah dilihat, tetapi getaran panjang yang tiba-tiba mengejutkan pengguna jika bermain di lingkungan yang tenang. Ramping ke amplitudo target terlalu cepat dapat menghasilkan suara dengungan yang terdengar. Rekomendasi untuk merancang pola bentuk gelombang adalah untuk menghaluskan transisi amplitudo untuk menciptakan efek peningkatan dan penurunan.
Contoh: Pola peningkatan
Bentuk gelombang direpresentasikan sebagai VibrationEffect
dengan tiga parameter:
- Waktu: array durasi, dalam milidetik, untuk setiap bentuk gelombang segmen data.
- Amplitud: amplitudo getaran yang diinginkan untuk setiap durasi yang ditentukan dalam argumen pertama, diwakili oleh nilai bilangan bulat dari 0 hingga 255, dengan 0 mewakili vibrator "nonaktif" dan 255 adalah kapasitas maksimum perangkat amplitudo.
- Ulangi indeks: indeks dalam array yang ditentukan dalam argumen pertama untuk mulai pengulangan bentuk gelombang, atau -1 jika seharusnya memainkan pola sekali saja.
Berikut adalah contoh bentuk gelombang yang berdenyut dua kali dengan jeda 350 md di antaranya berkedip. Pulsa pertama adalah peningkatan mulus hingga ke amplitudo maksimum, dan yang kedua adalah jalur cepat untuk menahan amplitudo maksimum. Berhenti di akhir sudah 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
Bentuk gelombang juga dapat diputar berulang kali sampai dibatalkan. Cara untuk membuat bentuk gelombang berulang adalah untuk mengatur parameter 'repeat' non-negatif. Saat Anda memutar bentuk gelombang berulang, getarannya berlanjut sampai dibatalkan secara eksplisit di 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(); }
Ini sangat berguna untuk peristiwa yang terputus-putus yang memerlukan tindakan pengguna untuk mengakuinya. Contoh dari peristiwa tersebut mencakup panggilan telepon masuk dan memicu alarm.
Contoh: Pola dengan penggantian
Mengontrol amplitudo getaran adalah kemampuan yang bergantung pada hardware. Memutar bentuk gelombang pada perangkat kelas bawah tanpa kemampuan ini akan membuatnya bergetar maksimal amplitudo untuk setiap entri positif dalam array amplitudo. Jika aplikasi Anda perlu mengakomodasi perangkat tersebut, maka rekomendasinya adalah untuk memastikan bahwa tidak menghasilkan efek berdengung saat diputar dalam kondisi itu, atau untuk merancang pola ON/NONAKTIF yang lebih sederhana yang dapat dimainkan sebagai penggantian.
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 untuk menyusunnya ke dalam efek kustom yang lebih panjang dan lebih kompleks, serta mengeksplorasi haptic menggunakan kemampuan perangkat keras yang lebih canggih. Anda dapat menggunakan kombinasi efek yang memvariasikan amplitudo dan frekuensi untuk membuat efek haptik yang lebih kompleks pada perangkat dengan aktuator haptik yang memiliki bandwidth frekuensi lebih luas.
Proses untuk membuat getaran kustom yang sama, yang dijelaskan sebelumnya di halaman ini, menjelaskan cara mengontrol amplitudo vibrasi untuk menciptakan efek meningkatkan dan menurunkannya. Haptic yang kaya meningkatkan konsep ini dengan menjelajahi rentang frekuensi yang lebih luas dari vibrator perangkat untuk membuat efek lebih mulus. Bentuk gelombang ini sangat efektif dalam membuat crescendo atau diminuendo pengaruh tersebut.
Dasar komposisi, yang dijelaskan sebelumnya di halaman ini, diterapkan oleh produsen perangkat. Fitur ini memberikan getaran yang tajam, singkat, dan menyenangkan yang sesuai dengan prinsip haptik untuk haptic yang jelas. Untuk selengkapnya detail tentang kemampuan ini dan cara kerjanya, lihat Aktuator getaran dasar.
Android tidak menyediakan penggantian untuk komposisi dengan primitif. Sebaiknya Anda melakukan langkah-langkah berikut:
Sebelum mengaktifkan haptic lanjutan, pastikan perangkat tertentu mendukung semua primitif yang Anda gunakan.
Menonaktifkan rangkaian pengalaman konsisten yang tidak didukung, bukan hanya efek yang tidak memiliki primitif. Informasi selengkapnya tentang cara memeriksa yang didukung perangkat ditampilkan sebagai berikut.
Anda dapat membuat efek getaran yang tenang dengan VibrationEffect.Composition
.
Berikut contoh efek yang meningkat perlahan, diikuti dengan efek klik 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. Masing-masing juga dapat diskalakan, sehingga Anda dapat mengontrol amplitudo getaran yang dihasilkan oleh masing-masing {i>database<i}. Skala didefinisikan sebagai nilai antara 0 dan 1, dengan 0 sebenarnya memetakan ke amplitudo minimum yang memungkinkan primitif ini (hampir) dirasakan oleh pengguna.
Jika Anda ingin membuat versi yang lemah dan kuat dari primitif yang sama, menyarankan agar timbangan berbeda dengan rasio 1,4 atau lebih, sehingga perbedaannya intensitasnya dapat dengan mudah dipahami. Jangan membuat lebih dari tiga tingkat intensitas primitif yang sama, karena mereka tidak secara persepsi berbeda. Misalnya, gunakan skala 0,5, 0,7, dan 1,0 untuk membuat skala rendah, sedang, dan intensitas tinggi dari primitif.
Komposisi juga dapat menentukan penundaan untuk ditambahkan di antara pemutaran berturut-turut primitif. Penundaan ini dinyatakan dalam milidetik sejak akhir primitif sebelumnya. Secara umum, jarak 5 hingga 10 md antara dua primitif terlalu singkat agar dapat dideteksi. Pertimbangkan untuk menggunakan jeda dengan urutan 50 md atau lebih jika Anda ingin membuat kesenjangan yang jelas antara dua primitif. Berikut adalah contoh komposisi yang tertunda:
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:
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 {i>primitif<i} dan kemudian memutuskan mana yang akan tulis 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: Menolak (dengan tanda centang rendah)
Anda dapat mengontrol amplitudo getaran primitif untuk menyampaikan masukan yang berguna untuk tindakan yang sedang berlangsung. Nilai skala dengan jarak dekat bisa digunakan untuk membuat efek crescendo halus dari sebuah primitif. Keterlambatan antara primitif berurutan juga dapat ditetapkan secara dinamis berdasarkan pengguna interaksi. Hal ini diilustrasikan dalam contoh animasi tampilan berikut dikontrol dengan gestur tarik dan ditambahkan dengan haptic.
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: Perluas (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 peningkatan,
PRIMITIVE_QUICK_FALL
Primitif ini bekerja sama lebih baik untuk
membuat segmen bentuk gelombang yang tumbuh di
lalu mereda. Anda dapat menyelaraskan dasar yang diskalakan untuk mencegah
melompati amplitudo di antara keduanya, yang juga berfungsi dengan baik untuk memperluas
durasi efek. Secara persepsi, orang-orang selalu memperhatikan
bagian yang meningkat lebih dari
yang turun, sehingga membuat bagian naik lebih pendek dari
digunakan untuk menggeser penekanan
ke bagian yang turun.
Berikut contoh penerapan komposisi ini untuk perluasan dan menciutkan lingkaran. Efek kenaikan dapat meningkatkan perasaan perluasan selama animasinya. Kombinasi efek naik dan turun membantu menekankan menciut di akhir animasi.
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: Wobble (dengan putaran)
Salah satu prinsip haptic utama adalah untuk menyenangkan pengguna. Cara yang menyenangkan
untuk memperkenalkan efek getaran tak terduga yang menyenangkan adalah dengan menggunakan
PRIMITIVE_SPIN
Primitif ini paling efektif jika dipanggil lebih dari sekali. Beberapa
putaran yang digabungkan dapat menciptakan efek
bergoyang-goyang dan tidak stabil, yang dapat
ditingkatkan lagi dengan menerapkan penskalaan
yang agak acak pada setiap primitif. Anda
juga dapat bereksperimen dengan kesenjangan antara primitif putaran yang berurutan. Dua putaran
tanpa jeda (0 md di antaranya) menciptakan sensasi berputar ketat. Meningkat
gap antar-spin dari 10 hingga 50 ms menyebabkan sensasi berputar yang lebih longgar, dan
dapat digunakan untuk mencocokkan
durasi video atau animasi.
Kami tidak menyarankan penggunaan jeda yang lebih dari 100 milidetik, karena putaran tidak lagi terintegrasi dengan baik dan mulai terasa seperti efek individual.
Berikut ini contoh bentuk elastis yang memantul kembali setelah ditarik ke bawah lalu dilepaskan. Animasi ditingkatkan dengan sepasang efek putaran, yang diputar dengan berbagai intensitas yang sebanding dengan perpindahan pantulan.
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 bunyi denting)
Penerapan canggih dari efek vibrasi lainnya
adalah untuk melakukan simulasi
interaksi. Tujuan
PRIMITIVE_THUD
dapat menciptakan efek yang kuat dan menggema, yang dapat dipasangkan dengan
visualisasi dampak, misalnya dalam video atau animasi, untuk meningkatkan
pengalaman pengguna secara keseluruhan.
Berikut contoh animasi pelepasan bola sederhana yang ditingkatkan dengan efek thud yang diputar setiap kali bola memantul dari bagian bawah layar:
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; } } }); } }